summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2012-05-03 13:01:35 +0200
committerPierre Schmitz <pierre@archlinux.de>2012-05-03 13:01:35 +0200
commitd9022f63880ce039446fba8364f68e656b7bf4cb (patch)
tree16b40fbf17bf7c9ee6f4ead25b16dd192378050a /tests
parent27cf83d177256813e2e802241085fce5dd0f3fb9 (diff)
Update to MediaWiki 1.19.0
Diffstat (limited to 'tests')
-rw-r--r--tests/TestsAutoLoader.php3
-rw-r--r--tests/jasmine/.htaccess1
-rw-r--r--tests/jasmine/SpecRunner.html42
-rw-r--r--tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE20
-rw-r--r--tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js188
-rw-r--r--tests/jasmine/lib/jasmine-1.0.1/jasmine.css166
-rw-r--r--tests/jasmine/lib/jasmine-1.0.1/jasmine.js2421
-rw-r--r--tests/jasmine/spec/mediawiki.Uri.spec.js307
-rw-r--r--tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js488
-rw-r--r--tests/jasmine/spec/mediawiki.jqueryMsg.spec.js389
-rw-r--r--tests/jasmine/spec_makers/makeJqueryMsgSpec.php114
-rw-r--r--tests/parser/README8
-rw-r--r--tests/parser/parserTest.inc124
-rw-r--r--tests/parser/parserTests.txt382
-rw-r--r--tests/parser/parserTestsParserHook.php26
-rw-r--r--tests/parser/parserTestsStaticParserHook.php58
-rw-r--r--tests/parser/preprocess/All_system_messages.expected2
-rw-r--r--tests/parser/preprocess/Factorial.expected2
-rw-r--r--tests/parserTests.php9
-rw-r--r--tests/phpunit/Makefile17
-rw-r--r--tests/phpunit/MediaWikiLangTestCase.php6
-rw-r--r--tests/phpunit/MediaWikiPHPUnitCommand.php30
-rw-r--r--tests/phpunit/MediaWikiTestCase.php113
-rw-r--r--tests/phpunit/StructureTest.php56
-rw-r--r--tests/phpunit/data/db/mysql/functions.sql12
-rw-r--r--tests/phpunit/data/db/postgres/functions.sql12
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.13.sql (renamed from tests/phpunit/includes/db/sqlite/tables-1.13.sql)2
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.15.sql (renamed from tests/phpunit/includes/db/sqlite/tables-1.15.sql)4
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.16.sql (renamed from tests/phpunit/includes/db/sqlite/tables-1.16.sql)0
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.17.sql (renamed from tests/phpunit/includes/db/sqlite/tables-1.17.sql)0
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.18.sql535
-rw-r--r--tests/phpunit/data/media/80x60-2layers.xcfbin0 -> 1162 bytes
-rw-r--r--tests/phpunit/data/media/80x60-Greyscale.xcfbin0 -> 667 bytes
-rw-r--r--tests/phpunit/data/media/80x60-RGB.xcfbin0 -> 677 bytes
-rw-r--r--tests/phpunit/data/media/Toll_Texas_1.svg150
-rw-r--r--tests/phpunit/data/media/iptc-invalid-psir.jpgbin0 -> 9574 bytes
-rw-r--r--tests/phpunit/includes/ArticleTablesTest.php31
-rw-r--r--tests/phpunit/includes/ArticleTest.php14
-rw-r--r--tests/phpunit/includes/BlockTest.php17
-rw-r--r--tests/phpunit/includes/EditPageTest.php33
-rw-r--r--tests/phpunit/includes/ExtraParserTest.php64
-rw-r--r--tests/phpunit/includes/FormOptionsInitializationTest.php4
-rw-r--r--tests/phpunit/includes/FormOptionsTest.php4
-rw-r--r--tests/phpunit/includes/GlobalFunctions/GlobalTest.php524
-rw-r--r--tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php29
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php111
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php133
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php36
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php (renamed from tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php)22
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php90
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php28
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php134
-rw-r--r--tests/phpunit/includes/HtmlTest.php277
-rw-r--r--tests/phpunit/includes/HttpTest.php556
-rw-r--r--tests/phpunit/includes/IPTest.php32
-rw-r--r--tests/phpunit/includes/LocalFileTest.php33
-rw-r--r--tests/phpunit/includes/MWNamespaceTest.php329
-rw-r--r--tests/phpunit/includes/MessageTest.php2
-rw-r--r--tests/phpunit/includes/ParserOptionsTest.php9
-rw-r--r--tests/phpunit/includes/PathRouterTest.php254
-rw-r--r--tests/phpunit/includes/Providers.php4
-rw-r--r--tests/phpunit/includes/ResourceLoaderTest.php2
-rw-r--r--tests/phpunit/includes/SanitizerTest.php43
-rw-r--r--tests/phpunit/includes/SanitizerValidateEmailTest.php (renamed from tests/phpunit/includes/UserIsValidEmailAddrTest.php)4
-rw-r--r--tests/phpunit/includes/SeleniumConfigurationTest.php24
-rw-r--r--tests/phpunit/includes/TemplateCategoriesTest.php38
-rw-r--r--tests/phpunit/includes/TitleMethodsTest.php78
-rw-r--r--tests/phpunit/includes/TitlePermissionTest.php34
-rw-r--r--tests/phpunit/includes/TitleTest.php4
-rw-r--r--tests/phpunit/includes/UserTest.php105
-rw-r--r--tests/phpunit/includes/WebRequestTest.php97
-rw-r--r--tests/phpunit/includes/XmlSelectTest.php4
-rw-r--r--tests/phpunit/includes/XmlTest.php99
-rw-r--r--tests/phpunit/includes/api/ApiBlockTest.php28
-rw-r--r--tests/phpunit/includes/api/ApiPurgeTest.php34
-rw-r--r--tests/phpunit/includes/api/ApiQueryTest.php8
-rw-r--r--tests/phpunit/includes/api/ApiTest.php36
-rw-r--r--tests/phpunit/includes/api/ApiTestCase.php51
-rw-r--r--tests/phpunit/includes/api/ApiTestCaseUpload.php40
-rw-r--r--tests/phpunit/includes/api/ApiTestUser.php2
-rw-r--r--tests/phpunit/includes/api/ApiUploadTest.php197
-rw-r--r--tests/phpunit/includes/api/ApiWatchTest.php5
-rw-r--r--tests/phpunit/includes/api/RandomImageGenerator.php124
-rw-r--r--tests/phpunit/includes/db/DatabaseSqliteTest.php14
-rw-r--r--tests/phpunit/includes/db/DatabaseTest.php32
-rw-r--r--tests/phpunit/includes/debug/MWDebugTest.php63
-rw-r--r--tests/phpunit/includes/filerepo/FileBackendTest.php1358
-rw-r--r--tests/phpunit/includes/filerepo/FileRepoTest.php41
-rw-r--r--tests/phpunit/includes/filerepo/StoreBatchTest.php122
-rw-r--r--tests/phpunit/includes/json/ServicesJsonTest.php93
-rw-r--r--tests/phpunit/includes/libs/JavaScriptMinifierTest.php43
-rw-r--r--tests/phpunit/includes/media/BitmapMetadataHandlerTest.php26
-rw-r--r--tests/phpunit/includes/media/BitmapScalingTest.php25
-rw-r--r--tests/phpunit/includes/media/ExifBitmapTest.php24
-rw-r--r--tests/phpunit/includes/media/ExifRotationTest.php59
-rw-r--r--tests/phpunit/includes/media/ExifTest.php9
-rw-r--r--tests/phpunit/includes/media/FormatMetadataTest.php39
-rw-r--r--tests/phpunit/includes/media/GIFMetadataExtractorTest.php1
-rw-r--r--tests/phpunit/includes/media/GIFTest.php31
-rw-r--r--tests/phpunit/includes/media/JpegMetadataExtractorTest.php10
-rw-r--r--tests/phpunit/includes/media/JpegTest.php18
-rw-r--r--tests/phpunit/includes/media/MediaHandlerTest.php (renamed from tests/phpunit/includes/ImageFunctionsTest.php)8
-rw-r--r--tests/phpunit/includes/media/PNGTest.php30
-rw-r--r--tests/phpunit/includes/media/SVGMetadataExtractorTest.php24
-rw-r--r--tests/phpunit/includes/media/TiffTest.php7
-rw-r--r--tests/phpunit/includes/media/XMPTest.php9
-rw-r--r--tests/phpunit/includes/media/XMPValidateTest.php47
-rw-r--r--tests/phpunit/includes/parser/MagicVariableTest.php10
-rw-r--r--tests/phpunit/includes/parser/MediaWikiParserTest.php12
-rw-r--r--tests/phpunit/includes/parser/NewParserTest.php414
-rw-r--r--tests/phpunit/includes/parser/ParserHelpers.php136
-rw-r--r--tests/phpunit/includes/parser/ParserPreloadTest.php67
-rw-r--r--tests/phpunit/includes/parser/PreprocessorTest.php102
-rw-r--r--tests/phpunit/includes/parser/TagHooksTest.php (renamed from tests/phpunit/includes/parser/TagHooks.php)0
-rw-r--r--tests/phpunit/includes/search/SearchEngineTest.php16
-rw-r--r--tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php79
-rw-r--r--tests/phpunit/includes/specials/SpecialRecentchangesTest.php (renamed from tests/phpunit/includes/specials/SpecialRecentchanges.php)16
-rw-r--r--tests/phpunit/includes/specials/SpecialSearchTest.php108
-rw-r--r--tests/phpunit/includes/upload/UploadFromUrlTest.php19
-rw-r--r--tests/phpunit/includes/upload/UploadStashTest.php53
-rw-r--r--tests/phpunit/includes/upload/UploadTest.php87
-rw-r--r--tests/phpunit/languages/LanguageAmTest.php33
-rw-r--r--tests/phpunit/languages/LanguageArTest.php78
-rw-r--r--tests/phpunit/languages/LanguageBeTest.php40
-rw-r--r--tests/phpunit/languages/LanguageBe_taraskTest.php35
-rw-r--r--tests/phpunit/languages/LanguageBhTest.php34
-rw-r--r--tests/phpunit/languages/LanguageBsTest.php41
-rw-r--r--tests/phpunit/languages/LanguageCsTest.php40
-rw-r--r--tests/phpunit/languages/LanguageCuTest.php41
-rw-r--r--tests/phpunit/languages/LanguageCyTest.php42
-rw-r--r--tests/phpunit/languages/LanguageDsbTest.php40
-rw-r--r--tests/phpunit/languages/LanguageFrTest.php34
-rw-r--r--tests/phpunit/languages/LanguageGaTest.php34
-rw-r--r--tests/phpunit/languages/LanguageGdTest.php38
-rw-r--r--tests/phpunit/languages/LanguageGvTest.php39
-rw-r--r--tests/phpunit/languages/LanguageHeTest.php48
-rw-r--r--tests/phpunit/languages/LanguageHiTest.php34
-rw-r--r--tests/phpunit/languages/LanguageHrTest.php41
-rw-r--r--tests/phpunit/languages/LanguageHsbTest.php40
-rw-r--r--tests/phpunit/languages/LanguageHyTest.php34
-rw-r--r--tests/phpunit/languages/LanguageKshTest.php34
-rw-r--r--tests/phpunit/languages/LanguageLnTest.php34
-rw-r--r--tests/phpunit/languages/LanguageLtTest.php53
-rw-r--r--tests/phpunit/languages/LanguageLvTest.php39
-rw-r--r--tests/phpunit/languages/LanguageMgTest.php35
-rw-r--r--tests/phpunit/languages/LanguageMkTest.php41
-rw-r--r--tests/phpunit/languages/LanguageMlTest.php43
-rw-r--r--tests/phpunit/languages/LanguageMoTest.php43
-rw-r--r--tests/phpunit/languages/LanguageMtTest.php72
-rw-r--r--tests/phpunit/languages/LanguageNlTest.php28
-rw-r--r--tests/phpunit/languages/LanguageNsoTest.php32
-rw-r--r--tests/phpunit/languages/LanguagePlTest.php72
-rw-r--r--tests/phpunit/languages/LanguageRoTest.php43
-rw-r--r--tests/phpunit/languages/LanguageRuTest.php54
-rw-r--r--tests/phpunit/languages/LanguageSeTest.php48
-rw-r--r--tests/phpunit/languages/LanguageSgsTest.php66
-rw-r--r--tests/phpunit/languages/LanguageShTest.php32
-rw-r--r--tests/phpunit/languages/LanguageSkTest.php40
-rw-r--r--tests/phpunit/languages/LanguageSlTest.php42
-rw-r--r--tests/phpunit/languages/LanguageSmaTest.php48
-rw-r--r--tests/phpunit/languages/LanguageSrTest.php199
-rw-r--r--tests/phpunit/languages/LanguageTest.php591
-rw-r--r--tests/phpunit/languages/LanguageTiTest.php32
-rw-r--r--tests/phpunit/languages/LanguageTlTest.php32
-rw-r--r--tests/phpunit/languages/LanguageTrTest.php9
-rw-r--r--tests/phpunit/languages/LanguageUkTest.php54
-rw-r--r--tests/phpunit/languages/LanguageWaTest.php32
-rw-r--r--tests/phpunit/phpunit.php4
-rw-r--r--tests/phpunit/skins/SideBarTest.php9
-rw-r--r--tests/phpunit/suite.xml19
-rw-r--r--tests/phpunit/suites/UploadFromUrlTestSuite.php74
-rw-r--r--tests/qunit/QUnitTestResources.php52
-rw-r--r--tests/qunit/data/qunitOkCall.js2
-rw-r--r--tests/qunit/data/testrunner.js130
-rw-r--r--tests/qunit/index.html79
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js)13
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.byteLength.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.byteLength.js)4
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.byteLimit.js)136
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.client.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.client.js)164
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.colorUtil.js)2
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js4
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.getAttrs.js)2
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.highlightText.test.js239
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.localize.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.localize.js)2
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js)6
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.tabIndex.js)12
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js227
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.textSelection.test.js279
-rw-r--r--tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js (renamed from tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js)36
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js201
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js43
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js29
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.test.js (renamed from tests/qunit/suites/resources/mediawiki/mediawiki.js)130
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js (renamed from tests/qunit/suites/resources/mediawiki/mediawiki.user.js)12
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js (renamed from tests/qunit/suites/resources/mediawiki/mediawiki.util.js)56
-rw-r--r--tests/selenium/SeleniumConfig.php16
-rw-r--r--tests/selenium/data/SimpleSeleniumTestDB.sql1
-rw-r--r--tests/selenium/data/mediawiki118_fresh_installation.sql15
-rw-r--r--tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiInstallationConfig.php4
-rw-r--r--tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiRestartInstallationTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiUserInterfaceTestCase.php2
-rw-r--r--tests/selenium/suites/MediawikiCoreSmokeTestCase.php40
-rw-r--r--tests/selenium/suites/MediawikiCoreSmokeTestSuite.php2
-rw-r--r--tests/selenium/suites/SimpleSeleniumTestCase.php14
-rw-r--r--tests/selenium/suites/SimpleSeleniumTestSuite.php2
-rw-r--r--tests/testHelpers.inc278
217 files changed, 15042 insertions, 2702 deletions
diff --git a/tests/TestsAutoLoader.php b/tests/TestsAutoLoader.php
index cccf7bf2..41bddfcd 100644
--- a/tests/TestsAutoLoader.php
+++ b/tests/TestsAutoLoader.php
@@ -21,9 +21,6 @@ $wgAutoloadClasses += array(
'RandomImageGenerator' => "$testFolder/phpunit/includes/api/RandomImageGenerator.php",
'UserWrapper' => "$testFolder/phpunit/includes/api/ApiTestCase.php",
- //Parser
- 'ParserTestFileIterator' => "$testFolder/phpunit/includes/parser/NewParserHelpers.php",
-
//Selenium
'SeleniumTestConstants' => "$testFolder/selenium/SeleniumTestConstants.php",
diff --git a/tests/jasmine/.htaccess b/tests/jasmine/.htaccess
new file mode 100644
index 00000000..605d2f4c
--- /dev/null
+++ b/tests/jasmine/.htaccess
@@ -0,0 +1 @@
+Allow from all
diff --git a/tests/jasmine/SpecRunner.html b/tests/jasmine/SpecRunner.html
new file mode 100644
index 00000000..6af9b0c3
--- /dev/null
+++ b/tests/jasmine/SpecRunner.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title>Jasmine Test Runner</title>
+ <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.1/jasmine.css">
+ <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine.js"></script>
+ <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine-html.js"></script>
+
+ <!-- include source files here... -->
+ <script type="text/javascript" src="../../load.php?debug=true&lang=en&modules=jquery%7Cmediawiki&only=scripts&skin=vector"></script>
+
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.js"></script>
+
+ <script type="text/javascript" src="../../resources/mediawiki.language/mediawiki.language.js"></script>
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.jqueryMsg.js"></script>
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.Uri.js"></script>
+<!--
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.api.js"></script>
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.api.edit.js"></script>
+-->
+
+ <!-- insert test data files here -->
+ <script type="text/javascript" src="spec/mediawiki.jqueryMsg.spec.data.js"></script>
+
+ <!-- include spec files here... -->
+ <script type="text/javascript" src="spec/mediawiki.Uri.spec.js"></script>
+ <!--
+ <script type="text/javascript" src="spec/mw.Api.spec.js"></script>
+ <script type="text/javascript" src="spec/mw.Api.edit.spec.js"></script>
+ -->
+ <script type="text/javascript" src="spec/mediawiki.jqueryMsg.spec.js"></script>
+
+</head>
+<body>
+<script type="text/javascript">
+ jasmine.getEnv().addReporter( new jasmine.TrivialReporter() );
+ jasmine.getEnv().execute();
+</script>
+
+</body>
+</html>
diff --git a/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE b/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE
new file mode 100644
index 00000000..1eb9b49e
--- /dev/null
+++ b/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2010 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js b/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js
new file mode 100644
index 00000000..81402b9c
--- /dev/null
+++ b/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js
@@ -0,0 +1,188 @@
+jasmine.TrivialReporter = function(doc) {
+ this.document = doc || document;
+ this.suiteDivs = {};
+ this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) { el.appendChild(child); }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+ var showPassed, showSkipped;
+
+ this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
+ this.createDom('div', { className: 'banner' },
+ this.createDom('div', { className: 'logo' },
+ this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"),
+ this.createDom('span', { className: 'version' }, runner.env.versionString())),
+ this.createDom('div', { className: 'options' },
+ "Show ",
+ showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+ showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+ )
+ ),
+
+ this.runnerDiv = this.createDom('div', { className: 'runner running' },
+ this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+ this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+ this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+ );
+
+ this.document.body.appendChild(this.outerDiv);
+
+ var suites = runner.suites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ var suiteDiv = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+ this.suiteDivs[suite.id] = suiteDiv;
+ var parentDiv = this.outerDiv;
+ if (suite.parentSuite) {
+ parentDiv = this.suiteDivs[suite.parentSuite.id];
+ }
+ parentDiv.appendChild(suiteDiv);
+ }
+
+ this.startedAt = new Date();
+
+ var self = this;
+ showPassed.onclick = function(evt) {
+ if (showPassed.checked) {
+ self.outerDiv.className += ' show-passed';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+ }
+ };
+
+ showSkipped.onclick = function(evt) {
+ if (showSkipped.checked) {
+ self.outerDiv.className += ' show-skipped';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+ }
+ };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+ var results = runner.results();
+ var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+ this.runnerDiv.setAttribute("class", className);
+ //do it twice for IE
+ this.runnerDiv.setAttribute("className", className);
+ var specs = runner.specs();
+ var specCount = 0;
+ for (var i = 0; i < specs.length; i++) {
+ if (this.specFilter(specs[i])) {
+ specCount++;
+ }
+ }
+ var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+ message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+ this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+ this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+ var results = suite.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.totalCount == 0) { // todo: change this to check results.skipped
+ status = 'skipped';
+ }
+ this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+ if (this.logRunningSpecs) {
+ this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+ var results = spec.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+ var specDiv = this.createDom('div', { className: 'spec ' + status },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(spec.getFullName()),
+ title: spec.getFullName()
+ }, spec.description));
+
+
+ var resultItems = results.getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ specDiv.appendChild(messagesDiv);
+ }
+
+ this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+ return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+ var paramMap = {};
+ var params = this.getLocation().search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ if (!paramMap["spec"]) return true;
+ return spec.getFullName().indexOf(paramMap["spec"]) == 0;
+};
diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine.css b/tests/jasmine/lib/jasmine-1.0.1/jasmine.css
new file mode 100644
index 00000000..6583fe7c
--- /dev/null
+++ b/tests/jasmine/lib/jasmine-1.0.1/jasmine.css
@@ -0,0 +1,166 @@
+body {
+ font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
+}
+
+
+.jasmine_reporter a:visited, .jasmine_reporter a {
+ color: #303;
+}
+
+.jasmine_reporter a:hover, .jasmine_reporter a:active {
+ color: blue;
+}
+
+.run_spec {
+ float:right;
+ padding-right: 5px;
+ font-size: .8em;
+ text-decoration: none;
+}
+
+.jasmine_reporter {
+ margin: 0 5px;
+}
+
+.banner {
+ color: #303;
+ background-color: #fef;
+ padding: 5px;
+}
+
+.logo {
+ float: left;
+ font-size: 1.1em;
+ padding-left: 5px;
+}
+
+.logo .version {
+ font-size: .6em;
+ padding-left: 1em;
+}
+
+.runner.running {
+ background-color: yellow;
+}
+
+
+.options {
+ text-align: right;
+ font-size: .8em;
+}
+
+
+
+
+.suite {
+ border: 1px outset gray;
+ margin: 5px 0;
+ padding-left: 1em;
+}
+
+.suite .suite {
+ margin: 5px;
+}
+
+.suite.passed {
+ background-color: #dfd;
+}
+
+.suite.failed {
+ background-color: #fdd;
+}
+
+.spec {
+ margin: 5px;
+ padding-left: 1em;
+ clear: both;
+}
+
+.spec.failed, .spec.passed, .spec.skipped {
+ padding-bottom: 5px;
+ border: 1px solid gray;
+}
+
+.spec.failed {
+ background-color: #fbb;
+ border-color: red;
+}
+
+.spec.passed {
+ background-color: #bfb;
+ border-color: green;
+}
+
+.spec.skipped {
+ background-color: #bbb;
+}
+
+.messages {
+ border-left: 1px dashed gray;
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+.passed {
+ background-color: #cfc;
+ display: none;
+}
+
+.failed {
+ background-color: #fbb;
+}
+
+.skipped {
+ color: #777;
+ background-color: #eee;
+ display: none;
+}
+
+
+/*.resultMessage {*/
+ /*white-space: pre;*/
+/*}*/
+
+.resultMessage span.result {
+ display: block;
+ line-height: 2em;
+ color: black;
+}
+
+.resultMessage .mismatch {
+ color: black;
+}
+
+.stackTrace {
+ white-space: pre;
+ font-size: .8em;
+ margin-left: 10px;
+ max-height: 5em;
+ overflow: auto;
+ border: 1px inset red;
+ padding: 1em;
+ background: #eef;
+}
+
+.finished-at {
+ padding-left: 1em;
+ font-size: .6em;
+}
+
+.show-passed .passed,
+.show-skipped .skipped {
+ display: block;
+}
+
+
+#jasmine_content {
+ position:fixed;
+ right: 100%;
+}
+
+.runner {
+ border: 1px solid gray;
+ display: block;
+ margin: 5px 0;
+ padding: 2px 0 2px 10px;
+}
diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine.js b/tests/jasmine/lib/jasmine-1.0.1/jasmine.js
new file mode 100644
index 00000000..964f99ed
--- /dev/null
+++ b/tests/jasmine/lib/jasmine-1.0.1/jasmine.js
@@ -0,0 +1,2421 @@
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+};
+
+/**
+ * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+jasmine.getGlobal = function() {
+ function getGlobal() {
+ return this;
+ }
+
+ return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return jasmine.getGlobal()[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+ this.type = 'log';
+ this.values = values;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+ var text = "";
+ for(var i = 0; i < this.values.length; i++) {
+ if (i > 0) text += " ";
+ if (jasmine.isString_(this.values[i])) {
+ text += this.values[i];
+ } else {
+ text += jasmine.pp(this.values[i]);
+ }
+ }
+ return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'expect';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+
+ this.message = this.passed_ ? 'Passed.' : params.message;
+ this.trace = this.passed_ ? '' : new Error(this.message);
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+ return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj['nodeType'] > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+ /**
+ * The name of the spy, if provided.
+ */
+ this.identity = name || 'unknown';
+ /**
+ * Is this Object a spy?
+ */
+ this.isSpy = true;
+ /**
+ * The actual function this spy stubs.
+ */
+ this.plan = function() {
+ };
+ /**
+ * Tracking of the most recent call to the spy.
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy.mostRecentCall.args = [1, 2];
+ */
+ this.mostRecentCall = {};
+
+ /**
+ * Holds arguments for each call to the spy, indexed by call count
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy(7, 8);
+ * mySpy.mostRecentCall.args = [7, 8];
+ * mySpy.argsForCall[0] = [1, 2];
+ * mySpy.argsForCall[1] = [7, 8];
+ */
+ this.argsForCall = [];
+ this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ * bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+ this.plan = this.originalValue;
+ return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+ this.plan = function() {
+ throw exceptionMsg;
+ };
+ return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ * // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+ this.plan = fakeFunc;
+ return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+ this.wasCalled = false;
+ this.callCount = 0;
+ this.argsForCall = [];
+ this.calls = [];
+ this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+ var spyObj = function() {
+ spyObj.wasCalled = true;
+ spyObj.callCount++;
+ var args = jasmine.util.argsToArray(arguments);
+ spyObj.mostRecentCall.object = this;
+ spyObj.mostRecentCall.args = args;
+ spyObj.argsForCall.push(args);
+ spyObj.calls.push({object: this, args: args});
+ return spyObj.plan.apply(this, arguments);
+ };
+
+ var spy = new jasmine.Spy(name);
+
+ for (var prop in spy) {
+ spyObj[prop] = spy[prop];
+ }
+
+ spyObj.reset();
+
+ return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+ return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+ if (!jasmine.isArray_(methodNames) || methodNames.length == 0) {
+ throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.log = function() {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @returns a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+ return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ * expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+ return jasmine.getEnv().it(desc, func);
+};
+
+/**
+ * Creates a <em>disabled</em> Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+ return jasmine.getEnv().xit(desc, func);
+};
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ */
+var expect = function(actual) {
+ return jasmine.getEnv().currentSpec.expect(actual);
+};
+
+/**
+ * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+ jasmine.getEnv().currentSpec.runs(func);
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+ jasmine.getEnv().currentSpec.waits(timeout);
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+ jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+ jasmine.getEnv().afterEach(afterEachFunction);
+};
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+ return jasmine.getEnv().describe(description, specDefinitions);
+};
+
+/**
+ * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+ return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+ try {
+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+ } catch(e) {
+ }
+ try {
+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+ } catch(e) {
+ }
+ try {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ } catch(e) {
+ }
+ try {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ } catch(e) {
+ }
+ throw new Error("This browser does not support XMLHttpRequest.");
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+ /**
+ * @private
+ */
+ var subclass = function() {
+ };
+ subclass.prototype = parentClass.prototype;
+ childClass.prototype = new subclass;
+};
+
+jasmine.util.formatException = function(e) {
+ var lineNumber;
+ if (e.line) {
+ lineNumber = e.line;
+ }
+ else if (e.lineNumber) {
+ lineNumber = e.lineNumber;
+ }
+
+ var file;
+
+ if (e.sourceURL) {
+ file = e.sourceURL;
+ }
+ else if (e.fileName) {
+ file = e.fileName;
+ }
+
+ var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+ if (file && lineNumber) {
+ message += ' in ' + file + ' (line ' + lineNumber + ')';
+ }
+
+ return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+ if (!str) return str;
+ return str.replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+};
+
+jasmine.util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+ return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+ this.currentSpec = null;
+ this.currentSuite = null;
+ this.currentRunner_ = new jasmine.Runner(this);
+
+ this.reporter = new jasmine.MultiReporter();
+
+ this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+ this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+ this.lastUpdate = 0;
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.nextSpecId_ = 0;
+ this.nextSuiteId_ = 0;
+ this.equalityTesters_ = [];
+
+ // wrap matchers
+ this.matchersClass = function() {
+ jasmine.Matchers.apply(this, arguments);
+ };
+ jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+ jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+ if (jasmine.version_) {
+ return jasmine.version_;
+ } else {
+ throw new Error('Version not set');
+ }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+ if (jasmine.version_) {
+ var version = this.version();
+ return version.major + "." + version.minor + "." + version.build + " revision " + version.revision;
+ } else {
+ return "version unknown";
+ }
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+ return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+ return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+ this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+ this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+ var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+ var parentSuite = this.currentSuite;
+ if (parentSuite) {
+ parentSuite.add(suite);
+ } else {
+ this.currentRunner_.add(suite);
+ }
+
+ this.currentSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch(e) {
+ declarationError = e;
+ }
+
+ this.currentSuite = parentSuite;
+
+ if (declarationError) {
+ this.it("encountered a declaration exception", function() {
+ throw declarationError;
+ });
+ }
+
+ return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.beforeEach(beforeEachFunction);
+ } else {
+ this.currentRunner_.beforeEach(beforeEachFunction);
+ }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+ return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.afterEach(afterEachFunction);
+ } else {
+ this.currentRunner_.afterEach(afterEachFunction);
+ }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+ return {
+ execute: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+ var spec = new jasmine.Spec(this, this.currentSuite, description);
+ this.currentSuite.add(spec);
+ this.currentSpec = spec;
+
+ if (func) {
+ spec.runs(func);
+ }
+
+ return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+ return {
+ id: this.nextSpecId(),
+ runs: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+ return true;
+ }
+
+ a.__Jasmine_been_here_before__ = b;
+ b.__Jasmine_been_here_before__ = a;
+
+ var hasKey = function(obj, keyName) {
+ return obj != null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in b) {
+ if (!hasKey(a, property) && hasKey(b, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ }
+ for (property in a) {
+ if (!hasKey(b, property) && hasKey(a, property)) {
+ mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+ }
+ }
+ for (property in b) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+ }
+ }
+
+ if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+ mismatchValues.push("arrays were not the same length");
+ }
+
+ delete a.__Jasmine_been_here_before__;
+ delete b.__Jasmine_been_here_before__;
+ return (mismatchKeys.length == 0 && mismatchValues.length == 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ for (var i = 0; i < this.equalityTesters_.length; i++) {
+ var equalityTester = this.equalityTesters_[i];
+ var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+ if (result !== jasmine.undefined) return result;
+ }
+
+ if (a === b) return true;
+
+ if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+ return (a == jasmine.undefined && b == jasmine.undefined);
+ }
+
+ if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+ return a === b;
+ }
+
+ if (a instanceof Date && b instanceof Date) {
+ return a.getTime() == b.getTime();
+ }
+
+ if (a instanceof jasmine.Matchers.Any) {
+ return a.matches(b);
+ }
+
+ if (b instanceof jasmine.Matchers.Any) {
+ return b.matches(a);
+ }
+
+ if (jasmine.isString_(a) && jasmine.isString_(b)) {
+ return (a == b);
+ }
+
+ if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+ return (a == b);
+ }
+
+ if (typeof a === "object" && typeof b === "object") {
+ return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ //Straight check
+ return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+ if (jasmine.isArray_(haystack)) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals_(haystack[i], needle)) return true;
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+ this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+ this.env = env;
+ this.func = func;
+ this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+ try {
+ this.func.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ }
+ onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+ this.started = false;
+ this.finished = false;
+ this.suites_ = [];
+ this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+ this.started = true;
+ var suites = runner.topLevelSuites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ this.suites_.push(this.summarize_(suite));
+ }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+ var isSuite = suiteOrSpec instanceof jasmine.Suite;
+ var summary = {
+ id: suiteOrSpec.id,
+ name: suiteOrSpec.description,
+ type: isSuite ? 'suite' : 'spec',
+ children: []
+ };
+
+ if (isSuite) {
+ var children = suiteOrSpec.children();
+ for (var i = 0; i < children.length; i++) {
+ summary.children.push(this.summarize_(children[i]));
+ }
+ }
+ return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+ return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+ this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+ this.results_[spec.id] = {
+ messages: spec.results().getItems(),
+ result: spec.results().failedCount > 0 ? "failed" : "passed"
+ };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+ var results = {};
+ for (var i = 0; i < specIds.length; i++) {
+ var specId = specIds[i];
+ results[specId] = this.summarizeResult_(this.results_[specId]);
+ }
+ return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+ var summaryMessages = [];
+ var messagesLength = result.messages.length;
+ for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+ var resultMessage = result.messages[messageIndex];
+ summaryMessages.push({
+ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+ passed: resultMessage.passed ? resultMessage.passed() : true,
+ type: resultMessage.type,
+ message: resultMessage.message,
+ trace: {
+ stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+ }
+ });
+ }
+
+ return {
+ result : result.result,
+ messages : summaryMessages
+ };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+ this.env = env;
+ this.actual = actual;
+ this.spec = spec;
+ this.isNot = opt_isNot || false;
+ this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+ throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+ throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+ for (var methodName in prototype) {
+ if (methodName == 'report') continue;
+ var orig = prototype[methodName];
+ matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+ }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+ return function() {
+ var matcherArgs = jasmine.util.argsToArray(arguments);
+ var result = matcherFunction.apply(this, arguments);
+
+ if (this.isNot) {
+ result = !result;
+ }
+
+ if (this.reportWasCalled_) return result;
+
+ var message;
+ if (!result) {
+ if (this.message) {
+ message = this.message.apply(this, arguments);
+ if (jasmine.isArray_(message)) {
+ message = message[this.isNot ? 1 : 0];
+ }
+ } else {
+ var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+ message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+ if (matcherArgs.length > 0) {
+ for (var i = 0; i < matcherArgs.length; i++) {
+ if (i > 0) message += ",";
+ message += " " + jasmine.pp(matcherArgs[i]);
+ }
+ }
+ message += ".";
+ }
+ }
+ var expectationResult = new jasmine.ExpectationResult({
+ matcherName: matcherName,
+ passed: result,
+ expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+ actual: this.actual,
+ message: message
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return jasmine.undefined;
+ };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+ return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+ return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+ return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toNotEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+ return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+ return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+ return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+ return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+ return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+ return (this.actual === null);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+ return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+ return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called.",
+ "Expected spy " + this.actual.identity + " not to have been called."
+ ];
+ };
+
+ return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasNotCalled does not take arguments');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to not have been called.",
+ "Expected spy " + this.actual.identity + " to have been called."
+ ];
+ };
+
+ return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ this.message = function() {
+ if (this.actual.callCount == 0) {
+ // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
+ return [
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
+ ];
+ } else {
+ return [
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
+ ];
+ }
+ };
+
+ return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+ ]
+ };
+
+ return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+ return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toNotContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+ return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} expected
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+ var result = false;
+ var exception;
+ if (typeof this.actual != 'function') {
+ throw new Error('Actual is not a function');
+ }
+ try {
+ this.actual();
+ } catch (e) {
+ exception = e;
+ }
+ if (exception) {
+ result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+ }
+
+ var not = this.isNot ? "not " : "";
+
+ this.message = function() {
+ if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+ return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", exception.message || exception].join(' ');
+ } else {
+ return "Expected function to throw an exception.";
+ }
+ };
+
+ return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+ this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.matches = function(other) {
+ if (this.expectedClass == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedClass == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedClass == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedClass == Object) {
+ return typeof other == 'object';
+ }
+
+ return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.toString = function() {
+ return '<jasmine.any(' + this.expectedClass + ')>';
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+ this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+ this.subReporters_.push(reporter);
+};
+
+(function() {
+ var functionNames = [
+ "reportRunnerStarting",
+ "reportRunnerResults",
+ "reportSuiteResults",
+ "reportSpecStarting",
+ "reportSpecResults",
+ "log"
+ ];
+ for (var i = 0; i < functionNames.length; i++) {
+ var functionName = functionNames[i];
+ jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+ return function() {
+ for (var j = 0; j < this.subReporters_.length; j++) {
+ var subReporter = this.subReporters_[j];
+ if (subReporter[functionName]) {
+ subReporter[functionName].apply(subReporter, arguments);
+ }
+ }
+ };
+ })(functionName);
+ }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+ /**
+ * The total count of results
+ */
+ this.totalCount = 0;
+ /**
+ * Number of passed results
+ */
+ this.passedCount = 0;
+ /**
+ * Number of failed results
+ */
+ this.failedCount = 0;
+ /**
+ * Was this suite/spec skipped?
+ */
+ this.skipped = false;
+ /**
+ * @ignore
+ */
+ this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+ this.totalCount += result.totalCount;
+ this.passedCount += result.passedCount;
+ this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+ this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+ return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+ if (result.type != 'log') {
+ if (result.items_) {
+ this.rollupCounts(result);
+ } else {
+ this.totalCount++;
+ if (result.passed()) {
+ this.passedCount++;
+ } else {
+ this.failedCount++;
+ }
+ }
+ }
+ this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if <b>everything</b> below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+ return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+ this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+ if (this.ppNestLevel_ > 40) {
+ throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
+ }
+
+ this.ppNestLevel_++;
+ try {
+ if (value === jasmine.undefined) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === jasmine.getGlobal()) {
+ this.emitScalar('<global>');
+ } else if (value instanceof jasmine.Matchers.Any) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (jasmine.isSpy(value)) {
+ this.emitScalar("spy on " + value.identity);
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (value.__Jasmine_been_here_before__) {
+ this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
+ } else if (jasmine.isArray_(value) || typeof value == 'object') {
+ value.__Jasmine_been_here_before__ = true;
+ if (jasmine.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ delete value.__Jasmine_been_here_before__;
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false);
+ }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+ jasmine.PrettyPrinter.call(this);
+
+ this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+ this.append('[ ');
+ for (var i = 0; i < array.length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(' : ');
+ if (isGetter) {
+ self.append('<getter>');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+};
+jasmine.Queue = function(env) {
+ this.env = env;
+ this.blocks = [];
+ this.running = false;
+ this.index = 0;
+ this.offset = 0;
+ this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block) {
+ this.blocks.unshift(block);
+};
+
+jasmine.Queue.prototype.add = function(block) {
+ this.blocks.push(block);
+};
+
+jasmine.Queue.prototype.insertNext = function(block) {
+ this.blocks.splice((this.index + this.offset + 1), 0, block);
+ this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+ this.running = true;
+ this.onComplete = onComplete;
+ this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+ return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+ var self = this;
+ var goAgain = true;
+
+ while (goAgain) {
+ goAgain = false;
+
+ if (self.index < self.blocks.length && !this.abort) {
+ var calledSynchronously = true;
+ var completedSynchronously = false;
+
+ var onComplete = function () {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+ completedSynchronously = true;
+ return;
+ }
+
+ if (self.blocks[self.index].abort) {
+ self.abort = true;
+ }
+
+ self.offset = 0;
+ self.index++;
+
+ var now = new Date().getTime();
+ if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+ self.env.lastUpdate = now;
+ self.env.setTimeout(function() {
+ self.next_();
+ }, 0);
+ } else {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+ goAgain = true;
+ } else {
+ self.next_();
+ }
+ }
+ };
+ self.blocks[self.index].execute(onComplete);
+
+ calledSynchronously = false;
+ if (completedSynchronously) {
+ onComplete();
+ }
+
+ } else {
+ self.running = false;
+ if (self.onComplete) {
+ self.onComplete();
+ }
+ }
+ }
+};
+
+jasmine.Queue.prototype.results = function() {
+ var results = new jasmine.NestedResults();
+ for (var i = 0; i < this.blocks.length; i++) {
+ if (this.blocks[i].results) {
+ results.addResult(this.blocks[i].results());
+ }
+ }
+ return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+ var self = this;
+ self.env = env;
+ self.queue = new jasmine.Queue(env);
+ self.before_ = [];
+ self.after_ = [];
+ self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+ var self = this;
+ if (self.env.reporter.reportRunnerStarting) {
+ self.env.reporter.reportRunnerStarting(this);
+ }
+ self.queue.start(function () {
+ self.finishCallback();
+ });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+ this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+ this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+ if (block instanceof jasmine.Suite) {
+ this.addSuite(block);
+ }
+ this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+ var suites = this.suites();
+ var specs = [];
+ for (var i = 0; i < suites.length; i++) {
+ specs = specs.concat(suites[i].specs());
+ }
+ return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+ var topLevelSuites = [];
+ for (var i = 0; i < this.suites_.length; i++) {
+ if (!this.suites_[i].parentSuite) {
+ topLevelSuites.push(this.suites_[i]);
+ }
+ }
+ return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+ return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+ if (!env) {
+ throw new Error('jasmine.Env() required');
+ }
+ if (!suite) {
+ throw new Error('jasmine.Suite() required');
+ }
+ var spec = this;
+ spec.id = env.nextSpecId ? env.nextSpecId() : null;
+ spec.env = env;
+ spec.suite = suite;
+ spec.description = description;
+ spec.queue = new jasmine.Queue(env);
+
+ spec.afterCallbacks = [];
+ spec.spies_ = [];
+
+ spec.results_ = new jasmine.NestedResults();
+ spec.results_.description = description;
+ spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+ return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+ return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+ return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+ var block = new jasmine.Block(this.env, func, this);
+ this.addToQueue(block);
+ return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+ if (this.queue.isRunning()) {
+ this.queue.insertNext(block);
+ } else {
+ this.queue.add(block);
+ }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+ this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+ var positive = new (this.getMatchersClass_())(this.env, actual, this);
+ positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+ return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+ var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+ this.addToQueue(waitsFunc);
+ return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ var latchFunction_ = null;
+ var optional_timeoutMessage_ = null;
+ var optional_timeout_ = null;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ switch (typeof arg) {
+ case 'function':
+ latchFunction_ = arg;
+ break;
+ case 'string':
+ optional_timeoutMessage_ = arg;
+ break;
+ case 'number':
+ optional_timeout_ = arg;
+ break;
+ }
+ }
+
+ var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+ this.addToQueue(waitsForFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: false,
+ message: e ? jasmine.util.formatException(e) : 'Exception'
+ });
+ this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+ return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+ var parent = this.getMatchersClass_();
+ var newMatchersClass = function() {
+ parent.apply(this, arguments);
+ };
+ jasmine.util.inherit(newMatchersClass, parent);
+ jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+ this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+ this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+ this.removeAllSpies();
+ this.finishCallback();
+ if (onComplete) {
+ onComplete();
+ }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+ if (this.queue.isRunning()) {
+ this.queue.add(new jasmine.Block(this.env, doAfter, this));
+ } else {
+ this.afterCallbacks.unshift(doAfter);
+ }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+ var spec = this;
+ if (!spec.env.specFilter(spec)) {
+ spec.results_.skipped = true;
+ spec.finish(onComplete);
+ return;
+ }
+
+ this.env.reporter.reportSpecStarting(this);
+
+ spec.env.currentSpec = spec;
+
+ spec.addBeforesAndAftersToQueue();
+
+ spec.queue.start(function () {
+ spec.finish(onComplete);
+ });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+ var runner = this.env.currentRunner();
+ var i;
+
+ for (var suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+ }
+ }
+ for (i = 0; i < runner.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+ }
+ for (i = 0; i < this.afterCallbacks.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
+ }
+ for (suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
+ }
+ }
+ for (i = 0; i < runner.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
+ }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+ throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+ if (obj == jasmine.undefined) {
+ throw "spyOn could not find an object to spy upon for " + methodName + "()";
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+ throw methodName + '() method does not exist';
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spyObj = jasmine.createSpy(methodName);
+
+ this.spies_.push(spyObj);
+ spyObj.baseObj = obj;
+ spyObj.methodName = methodName;
+ spyObj.originalValue = obj[methodName];
+
+ obj[methodName] = spyObj;
+
+ return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+ for (var i = 0; i < this.spies_.length; i++) {
+ var spy = this.spies_[i];
+ spy.baseObj[spy.methodName] = spy.originalValue;
+ }
+ this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+ var self = this;
+ self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+ self.description = description;
+ self.queue = new jasmine.Queue(env);
+ self.parentSuite = parentSuite;
+ self.env = env;
+ self.before_ = [];
+ self.after_ = [];
+ self.children_ = [];
+ self.suites_ = [];
+ self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+ this.env.reporter.reportSuiteResults(this);
+ this.finished = true;
+ if (typeof(onComplete) == 'function') {
+ onComplete();
+ }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+ return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+ this.children_.push(suiteOrSpec);
+ if (suiteOrSpec instanceof jasmine.Suite) {
+ this.suites_.push(suiteOrSpec);
+ this.env.currentRunner().addSuite(suiteOrSpec);
+ } else {
+ this.specs_.push(suiteOrSpec);
+ }
+ this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+ return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+ return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ this.queue.start(function () {
+ self.finish(onComplete);
+ });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+ this.timeout = timeout;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+ this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+ this.env.setTimeout(function () {
+ onComplete();
+ }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+ this.timeout = timeout || env.defaultTimeoutInterval;
+ this.latchFunction = latchFunction;
+ this.message = message;
+ this.totalTimeSpentWaitingForLatch = 0;
+ jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+ this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+ var latchFunctionResult;
+ try {
+ latchFunctionResult = this.latchFunction.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ onComplete();
+ return;
+ }
+
+ if (latchFunctionResult) {
+ onComplete();
+ } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+ var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+ this.spec.fail({
+ name: 'timeout',
+ message: message
+ });
+
+ this.abort = true;
+ onComplete();
+ } else {
+ this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+ var self = this;
+ this.env.setTimeout(function() {
+ self.execute(onComplete);
+ }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+ }
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+ this.reset();
+
+ var self = this;
+ self.setTimeout = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+ return self.timeoutsMade;
+ };
+
+ self.setInterval = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+ return self.timeoutsMade;
+ };
+
+ self.clearTimeout = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+ self.clearInterval = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+ this.timeoutsMade = 0;
+ this.scheduledFunctions = {};
+ this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+ var oldMillis = this.nowMillis;
+ var newMillis = oldMillis + millis;
+ this.runFunctionsWithinRange(oldMillis, newMillis);
+ this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+ var scheduledFunc;
+ var funcsToRun = [];
+ for (var timeoutKey in this.scheduledFunctions) {
+ scheduledFunc = this.scheduledFunctions[timeoutKey];
+ if (scheduledFunc != jasmine.undefined &&
+ scheduledFunc.runAtMillis >= oldMillis &&
+ scheduledFunc.runAtMillis <= nowMillis) {
+ funcsToRun.push(scheduledFunc);
+ this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ }
+ }
+
+ if (funcsToRun.length > 0) {
+ funcsToRun.sort(function(a, b) {
+ return a.runAtMillis - b.runAtMillis;
+ });
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ try {
+ var funcToRun = funcsToRun[i];
+ this.nowMillis = funcToRun.runAtMillis;
+ funcToRun.funcToCall();
+ if (funcToRun.recurring) {
+ this.scheduleFunction(funcToRun.timeoutKey,
+ funcToRun.funcToCall,
+ funcToRun.millis,
+ true);
+ }
+ } catch(e) {
+ }
+ }
+ this.runFunctionsWithinRange(oldMillis, nowMillis);
+ }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+ this.scheduledFunctions[timeoutKey] = {
+ runAtMillis: this.nowMillis + millis,
+ funcToCall: funcToCall,
+ recurring: recurring,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+ defaultFakeTimer: new jasmine.FakeTimer(),
+
+ reset: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.reset();
+ },
+
+ tick: function(millis) {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.tick(millis);
+ },
+
+ runFunctionsWithinRange: function(oldMillis, nowMillis) {
+ jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+ },
+
+ scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+ jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+ },
+
+ useMock: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.after(jasmine.Clock.uninstallMock);
+
+ jasmine.Clock.installMock();
+ }
+ },
+
+ installMock: function() {
+ jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+ },
+
+ uninstallMock: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.installed = jasmine.Clock.real;
+ },
+
+ real: {
+ setTimeout: jasmine.getGlobal().setTimeout,
+ clearTimeout: jasmine.getGlobal().clearTimeout,
+ setInterval: jasmine.getGlobal().setInterval,
+ clearInterval: jasmine.getGlobal().clearInterval
+ },
+
+ assertInstalled: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+ }
+ },
+
+ isInstalled: function() {
+ return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+ },
+
+ installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setTimeout.apply) {
+ return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setInterval.apply) {
+ return jasmine.Clock.installed.setInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setInterval(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearTimeout(timeoutKey);
+ }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearInterval(timeoutKey);
+ }
+};
+
+
+jasmine.version_= {
+ "major": 1,
+ "minor": 0,
+ "build": 1,
+ "revision": 1286311016
+};
diff --git a/tests/jasmine/spec/mediawiki.Uri.spec.js b/tests/jasmine/spec/mediawiki.Uri.spec.js
new file mode 100644
index 00000000..721ccb38
--- /dev/null
+++ b/tests/jasmine/spec/mediawiki.Uri.spec.js
@@ -0,0 +1,307 @@
+( function() {
+
+ // ensure we have a generic URI parser if not running in a browser
+ if ( !mw.Uri ) {
+ mw.Uri = mw.UriRelative( 'http://example.com/' );
+ }
+
+ describe( "mw.Uri", function() {
+
+ describe( "should work well in loose and strict mode", function() {
+
+ function basicTests( strict ) {
+
+ describe( "should parse a simple HTTP URI correctly", function() {
+
+ var uriString = 'http://www.ietf.org/rfc/rfc2396.txt';
+ var uri;
+ if ( strict ) {
+ uri = new mw.Uri( uriString, strict );
+ } else {
+ uri = new mw.Uri( uriString );
+ }
+
+ it( "should have basic object properties", function() {
+ expect( uri.protocol ).toEqual( 'http' );
+ expect( uri.host ).toEqual( 'www.ietf.org' );
+ expect( uri.port ).not.toBeDefined();
+ expect( uri.path ).toEqual( '/rfc/rfc2396.txt' );
+ expect( uri.query ).toEqual( {} );
+ expect( uri.fragment ).not.toBeDefined();
+ } );
+
+ describe( "should construct composite components of URI on request", function() {
+ it( "should have empty userinfo", function() {
+ expect( uri.getUserInfo() ).toEqual( '' );
+ } );
+
+ it( "should have authority equal to host", function() {
+ expect( uri.getAuthority() ).toEqual( 'www.ietf.org' );
+ } );
+
+ it( "should have hostport equal to host", function() {
+ expect( uri.getHostPort() ).toEqual( 'www.ietf.org' );
+ } );
+
+ it( "should have empty string as query string", function() {
+ expect( uri.getQueryString() ).toEqual( '' );
+ } );
+
+ it( "should have path as relative path", function() {
+ expect( uri.getRelativePath() ).toEqual( '/rfc/rfc2396.txt' );
+ } );
+
+ it( "should return a uri string equivalent to original", function() {
+ expect( uri.toString() ).toEqual( uriString );
+ } );
+ } );
+ } );
+ }
+
+ describe( "should work in loose mode", function() {
+ basicTests( false );
+ } );
+
+ describe( "should work in strict mode", function() {
+ basicTests( true );
+ } );
+
+ } );
+
+ it( "should parse a simple ftp URI correctly with user and password", function() {
+ var uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' );
+ expect( uri.protocol ).toEqual( 'ftp' );
+ expect( uri.user ).toEqual( 'usr' );
+ expect( uri.password ).toEqual( 'pwd' );
+ expect( uri.host ).toEqual( '192.0.2.16' );
+ expect( uri.port ).not.toBeDefined();
+ expect( uri.path ).toEqual( '/' );
+ expect( uri.query ).toEqual( {} );
+ expect( uri.fragment ).not.toBeDefined();
+ } );
+
+ it( "should parse a simple querystring", function() {
+ var uri = new mw.Uri( 'http://www.google.com/?q=uri' );
+ expect( uri.protocol ).toEqual( 'http' );
+ expect( uri.host ).toEqual( 'www.google.com' );
+ expect( uri.port ).not.toBeDefined();
+ expect( uri.path ).toEqual( '/' );
+ expect( uri.query ).toBeDefined();
+ expect( uri.query ).toEqual( { q: 'uri' } );
+ expect( uri.fragment ).not.toBeDefined();
+ expect( uri.getQueryString() ).toEqual( 'q=uri' );
+ } );
+
+ describe( "should handle multiple value query args (overrideKeys on)", function() {
+ var uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { overrideKeys: true } );
+ it ( "should parse with multiple values", function() {
+ expect( uri.query.m ).toEqual( 'bar' );
+ expect( uri.query.n ).toEqual( '1' );
+ } );
+ it ( "should accept multiple values", function() {
+ uri.query.n = [ "x", "y", "z" ];
+ expect( uri.toString() ).toContain( 'm=bar' );
+ expect( uri.toString() ).toContain( 'n=x&n=y&n=z' );
+ expect( uri.toString().length ).toEqual( 'http://www.example.com/dir/?m=bar&n=x&n=y&n=z'.length );
+ } );
+ } );
+
+ describe( "should handle multiple value query args (overrideKeys off)", function() {
+ var uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { overrideKeys: false } );
+ it ( "should parse with multiple values", function() {
+ expect( uri.query.m.length ).toEqual( 2 );
+ expect( uri.query.m[0] ).toEqual( 'foo' );
+ expect( uri.query.m[1] ).toEqual( 'bar' );
+ expect( uri.query.n ).toEqual( '1' );
+ } );
+ it ( "should accept multiple values", function() {
+ uri.query.n = [ "x", "y", "z" ];
+ expect( uri.toString() ).toContain( 'm=foo&m=bar' );
+ expect( uri.toString() ).toContain( 'n=x&n=y&n=z' );
+ expect( uri.toString().length ).toEqual( 'http://www.example.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length );
+ } );
+ it ( "should be okay with removing values", function() {
+ uri.query.m.splice( 0, 1 );
+ delete uri.query.n;
+ expect( uri.toString() ).toEqual( 'http://www.example.com/dir/?m=bar' );
+ uri.query.m.splice( 0, 1 );
+ expect( uri.toString() ).toEqual( 'http://www.example.com/dir/' );
+ } );
+ } );
+
+ describe( "should deal with an all-dressed URI with everything", function() {
+ var uri = new mw.Uri( 'http://auth@www.example.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' );
+
+ it( "should have basic object properties", function() {
+ expect( uri.protocol ).toEqual( 'http' );
+ expect( uri.user ).toEqual( 'auth' );
+ expect( uri.password ).not.toBeDefined();
+ expect( uri.host ).toEqual( 'www.example.com' );
+ expect( uri.port ).toEqual( '81' );
+ expect( uri.path ).toEqual( '/dir/dir.2/index.htm' );
+ expect( uri.query ).toEqual( { q1: '0', test1: null, test2: 'value (escaped)' } );
+ expect( uri.fragment ).toEqual( 'top' );
+ } );
+
+ describe( "should construct composite components of URI on request", function() {
+ it( "should have userinfo", function() {
+ expect( uri.getUserInfo() ).toEqual( 'auth' );
+ } );
+
+ it( "should have authority equal to auth@hostport", function() {
+ expect( uri.getAuthority() ).toEqual( 'auth@www.example.com:81' );
+ } );
+
+ it( "should have hostport equal to host:port", function() {
+ expect( uri.getHostPort() ).toEqual( 'www.example.com:81' );
+ } );
+
+ it( "should have query string which contains all components", function() {
+ var queryString = uri.getQueryString();
+ expect( queryString ).toContain( 'q1=0' );
+ expect( queryString ).toContain( 'test1' );
+ expect( queryString ).not.toContain( 'test1=' );
+ expect( queryString ).toContain( 'test2=value+%28escaped%29' );
+ } );
+
+ it( "should have path as relative path", function() {
+ expect( uri.getRelativePath() ).toContain( uri.path );
+ expect( uri.getRelativePath() ).toContain( uri.getQueryString() );
+ expect( uri.getRelativePath() ).toContain( uri.fragment );
+ } );
+
+ } );
+ } );
+
+ describe( "should be able to clone itself", function() {
+ var original = new mw.Uri( 'http://en.wiki.local/w/api.php?action=query&foo=bar' );
+ var clone = original.clone();
+
+ it( "should make clones equivalent", function() {
+ expect( original ).toEqual( clone );
+ expect( original.toString() ).toEqual( clone.toString() );
+ } );
+
+ it( "should be able to manipulate clones independently", function() {
+ // but they are still different objects
+ expect( original ).not.toBe( clone );
+ // and can diverge
+ clone.host = 'fr.wiki.local';
+ expect( original.host ).not.toEqual( clone.host );
+ expect( original.toString() ).not.toEqual( clone.toString() );
+ } );
+ } );
+
+ describe( "should be able to construct URL from object", function() {
+ it ( "should construct given basic arguments", function() {
+ var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local', path: '/this' } );
+ expect( uri.toString() ).toEqual( 'http://www.foo.local/this' );
+ } );
+
+ it ( "should construct given more complex arguments", function() {
+ var uri = new mw.Uri( {
+ protocol: 'http',
+ host: 'www.foo.local',
+ path: '/this',
+ query: { hi: 'there' },
+ fragment: 'blah'
+ } );
+ expect( uri.toString() ).toEqual( 'http://www.foo.local/this?hi=there#blah' );
+ } );
+
+ it ( "should fail to construct without required properties", function() {
+ expect( function() {
+ var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local' } );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+ } );
+
+ describe( "should be able to manipulate properties", function() {
+ var uri;
+
+ beforeEach( function() {
+ uri = new mw.Uri( 'http://en.wiki.local/w/api.php' );
+ } );
+
+ it( "can add a fragment", function() {
+ uri.fragment = 'frag';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php#frag' );
+ } );
+
+ it( "can change host and port", function() {
+ uri.host = 'fr.wiki.local';
+ uri.port = '8080';
+ expect( uri.toString() ).toEqual( 'http://fr.wiki.local:8080/w/api.php' );
+ } );
+
+ it ( "can add query arguments", function() {
+ uri.query.foo = 'bar';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
+ } );
+
+ it ( "can extend query arguments", function() {
+ uri.query.foo = 'bar';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
+ uri.extend( { foo: 'quux', pif: 'paf' } );
+ expect( uri.toString() ).toContain( 'foo=quux' );
+ expect( uri.toString() ).not.toContain( 'foo=bar' );
+ expect( uri.toString() ).toContain( 'pif=paf' );
+ } );
+
+ it ( "can remove query arguments", function() {
+ uri.query.foo = 'bar';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
+ delete( uri.query.foo );
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php' );
+ } );
+
+ } );
+
+ describe( "should handle protocol-relative URLs", function() {
+
+ it ( "should create protocol-relative URLs with same protocol as document", function() {
+ var uriRel = mw.UriRelative( 'glork://en.wiki.local/foo.php' );
+ var uri = new uriRel( '//en.wiki.local/w/api.php' );
+ expect( uri.protocol ).toEqual( 'glork' );
+ } );
+
+ } );
+
+ it( "should throw error on no arguments to constructor", function() {
+ expect( function() {
+ var uri = new mw.Uri();
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on empty string as argument to constructor", function() {
+ expect( function() {
+ var uri = new mw.Uri( '' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on non-URI as argument to constructor", function() {
+ expect( function() {
+ var uri = new mw.Uri( 'glaswegian penguins' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on improper URI as argument to constructor", function() {
+ expect( function() {
+ var uri = new mw.Uri( 'http:/foo.com' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on URI without protocol or // in strict mode", function() {
+ expect( function() {
+ var uri = new mw.Uri( 'foo.com/bar/baz', true );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should normalize URI without protocol or // in loose mode", function() {
+ var uri = new mw.Uri( 'foo.com/bar/baz', false );
+ expect( uri.toString() ).toEqual( 'http://foo.com/bar/baz' );
+ } );
+
+ } );
+
+} )();
diff --git a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js
new file mode 100644
index 00000000..a867f72c
--- /dev/null
+++ b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js
@@ -0,0 +1,488 @@
+// This file stores the results from the PHP parser for certain messages and arguments,
+// so we can test the equivalent Javascript libraries.
+// Last generated with makeLanguageSpec.php at 2011-01-28T02:04:09+00:00
+
+mediaWiki.messages.set( {
+ "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
+ "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
+ "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}",
+ "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.",
+ "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}",
+ "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}",
+ "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
+ "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
+ "zh_undelete_short": "\u6062\u590d\u88ab\u5220\u9664\u7684$1\u9879\u4fee\u8ba2",
+ "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u6709$2\u4e2a\u5b50\u5206\u7c7b\u3002}}"
+} );
+var jasmineMsgSpec = [
+ {
+ "name": "en undelete_short 0",
+ "key": "en_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "Undelete 0 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 1",
+ "key": "en_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "Undelete one edit",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 2",
+ "key": "en_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "Undelete 2 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 5",
+ "key": "en_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "Undelete 5 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 21",
+ "key": "en_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "Undelete 21 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 101",
+ "key": "en_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "Undelete 101 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en category-subcat-count 0,10",
+ "key": "en_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "This category has the following 0 subcategories, out of 10 total.",
+ "lang": "en"
+ },
+ {
+ "name": "en category-subcat-count 1,1",
+ "key": "en_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "This category has only the following subcategory.",
+ "lang": "en"
+ },
+ {
+ "name": "en category-subcat-count 1,2",
+ "key": "en_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "This category has the following subcategory, out of 2 total.",
+ "lang": "en"
+ },
+ {
+ "name": "en category-subcat-count 3,30",
+ "key": "en_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "This category has the following 3 subcategories, out of 30 total.",
+ "lang": "en"
+ },
+ {
+ "name": "fr undelete_short 0",
+ "key": "fr_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "Restaurer 0 modification",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 1",
+ "key": "fr_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "Restaurer 1 modification",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 2",
+ "key": "fr_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "Restaurer 2 modifications",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 5",
+ "key": "fr_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "Restaurer 5 modifications",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 21",
+ "key": "fr_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "Restaurer 21 modifications",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 101",
+ "key": "fr_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "Restaurer 101 modifications",
+ "lang": "fr"
+ },
+ {
+ "name": "fr category-subcat-count 0,10",
+ "key": "fr_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.",
+ "lang": "fr"
+ },
+ {
+ "name": "fr category-subcat-count 1,1",
+ "key": "fr_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.",
+ "lang": "fr"
+ },
+ {
+ "name": "fr category-subcat-count 1,2",
+ "key": "fr_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.",
+ "lang": "fr"
+ },
+ {
+ "name": "fr category-subcat-count 3,30",
+ "key": "fr_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.",
+ "lang": "fr"
+ },
+ {
+ "name": "ar undelete_short 0",
+ "key": "ar_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 1",
+ "key": "ar_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 2",
+ "key": "ar_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 5",
+ "key": "ar_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 21",
+ "key": "ar_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 101",
+ "key": "ar_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627",
+ "lang": "ar"
+ },
+ {
+ "name": "ar category-subcat-count 0,10",
+ "key": "ar_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.",
+ "lang": "ar"
+ },
+ {
+ "name": "ar category-subcat-count 1,1",
+ "key": "ar_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.",
+ "lang": "ar"
+ },
+ {
+ "name": "ar category-subcat-count 1,2",
+ "key": "ar_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.",
+ "lang": "ar"
+ },
+ {
+ "name": "ar category-subcat-count 3,30",
+ "key": "ar_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.",
+ "lang": "ar"
+ },
+ {
+ "name": "jp undelete_short 0",
+ "key": "jp_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "Undelete 0 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 1",
+ "key": "jp_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "Undelete one edit",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 2",
+ "key": "jp_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "Undelete 2 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 5",
+ "key": "jp_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "Undelete 5 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 21",
+ "key": "jp_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "Undelete 21 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 101",
+ "key": "jp_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "Undelete 101 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp category-subcat-count 0,10",
+ "key": "jp_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "This category has the following 0 subcategories, out of 10 total.",
+ "lang": "jp"
+ },
+ {
+ "name": "jp category-subcat-count 1,1",
+ "key": "jp_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "This category has only the following subcategory.",
+ "lang": "jp"
+ },
+ {
+ "name": "jp category-subcat-count 1,2",
+ "key": "jp_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "This category has the following subcategory, out of 2 total.",
+ "lang": "jp"
+ },
+ {
+ "name": "jp category-subcat-count 3,30",
+ "key": "jp_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "This category has the following 3 subcategories, out of 30 total.",
+ "lang": "jp"
+ },
+ {
+ "name": "zh undelete_short 0",
+ "key": "zh_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "\u6062\u590d\u88ab\u5220\u9664\u76840\u9879\u4fee\u8ba2",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 1",
+ "key": "zh_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "\u6062\u590d\u88ab\u5220\u9664\u76841\u9879\u4fee\u8ba2",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 2",
+ "key": "zh_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "\u6062\u590d\u88ab\u5220\u9664\u76842\u9879\u4fee\u8ba2",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 5",
+ "key": "zh_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "\u6062\u590d\u88ab\u5220\u9664\u76845\u9879\u4fee\u8ba2",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 21",
+ "key": "zh_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "\u6062\u590d\u88ab\u5220\u9664\u768421\u9879\u4fee\u8ba2",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 101",
+ "key": "zh_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "\u6062\u590d\u88ab\u5220\u9664\u7684101\u9879\u4fee\u8ba2",
+ "lang": "zh"
+ },
+ {
+ "name": "zh category-subcat-count 0,10",
+ "key": "zh_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670910\u4e2a\u5b50\u5206\u7c7b\u3002",
+ "lang": "zh"
+ },
+ {
+ "name": "zh category-subcat-count 1,1",
+ "key": "zh_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002",
+ "lang": "zh"
+ },
+ {
+ "name": "zh category-subcat-count 1,2",
+ "key": "zh_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u67092\u4e2a\u5b50\u5206\u7c7b\u3002",
+ "lang": "zh"
+ },
+ {
+ "name": "zh category-subcat-count 3,30",
+ "key": "zh_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670930\u4e2a\u5b50\u5206\u7c7b\u3002",
+ "lang": "zh"
+ }
+];
diff --git a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js
new file mode 100644
index 00000000..46dcaa80
--- /dev/null
+++ b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js
@@ -0,0 +1,389 @@
+/* spec for language & message behaviour in MediaWiki */
+
+mw.messages.set( {
+ "en_empty": "",
+ "en_simple": "Simple message",
+ "en_replace": "Simple $1 replacement",
+ "en_replace2": "Simple $1 $2 replacements",
+ "en_link": "Simple [http://example.com link to example].",
+ "en_link_replace": "Complex [$1 $2] behaviour.",
+ "en_simple_magic": "Simple {{ALOHOMORA}} message",
+ "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
+ "en_undelete_empty_param": "Undelete{{PLURAL:$1|| multiple edits}}",
+ "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
+ "en_escape0": "Escape \\to fantasy island",
+ "en_escape1": "I had \\$2.50 in my pocket",
+ "en_escape2": "I had {{PLURAL:$1|the absolute \\|$1\\| which came out to \\$3.00 in my C:\\\\drive| some stuff}}",
+ "en_fail": "This should fail to {{parse",
+ "en_fail_magic": "There is no such magic word as {{SIETNAME}}",
+ "en_evil": "This has <script type='text/javascript'>window.en_evil = true;</script> tags"
+} );
+
+/**
+ * Tests
+ */
+( function( mw, $, undefined ) {
+
+ describe( "mediaWiki.jqueryMsg", function() {
+
+ describe( "basic message functionality", function() {
+
+ it( "should return identity for empty string", function() {
+ var parser = new mw.jqueryMsg.parser();
+ expect( parser.parse( 'en_empty' ).html() ).toEqual( '' );
+ } );
+
+
+ it( "should return identity for simple string", function() {
+ var parser = new mw.jqueryMsg.parser();
+ expect( parser.parse( 'en_simple' ).html() ).toEqual( 'Simple message' );
+ } );
+
+ } );
+
+ describe( "escaping", function() {
+
+ it ( "should handle simple escaping", function() {
+ var parser = new mw.jqueryMsg.parser();
+ expect( parser.parse( 'en_escape0' ).html() ).toEqual( 'Escape to fantasy island' );
+ } );
+
+ it ( "should escape dollar signs found in ordinary text when backslashed", function() {
+ var parser = new mw.jqueryMsg.parser();
+ expect( parser.parse( 'en_escape1' ).html() ).toEqual( 'I had $2.50 in my pocket' );
+ } );
+
+ it ( "should handle a complicated escaping case, including escaped pipe chars in template args", function() {
+ var parser = new mw.jqueryMsg.parser();
+ expect( parser.parse( 'en_escape2', [ 1 ] ).html() ).toEqual( 'I had the absolute |1| which came out to $3.00 in my C:\\drive' );
+ } );
+
+ } );
+
+ describe( "replacing", function() {
+
+ it ( "should handle simple replacing", function() {
+ var parser = new mw.jqueryMsg.parser();
+ expect( parser.parse( 'en_replace', [ 'foo' ] ).html() ).toEqual( 'Simple foo replacement' );
+ } );
+
+ it ( "should return $n if replacement not there", function() {
+ var parser = new mw.jqueryMsg.parser();
+ expect( parser.parse( 'en_replace', [] ).html() ).toEqual( 'Simple $1 replacement' );
+ expect( parser.parse( 'en_replace2', [ 'bar' ] ).html() ).toEqual( 'Simple bar $2 replacements' );
+ } );
+
+ } );
+
+ describe( "linking", function() {
+
+ it ( "should handle a simple link", function() {
+ var parser = new mw.jqueryMsg.parser();
+ var parsed = parser.parse( 'en_link' );
+ var contents = parsed.contents();
+ expect( contents.length ).toEqual( 3 );
+ expect( contents[0].nodeName ).toEqual( '#text' );
+ expect( contents[0].nodeValue ).toEqual( 'Simple ' );
+ expect( contents[1].nodeName ).toEqual( 'A' );
+ expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com' );
+ expect( contents[1].childNodes[0].nodeValue ).toEqual( 'link to example' );
+ expect( contents[2].nodeName ).toEqual( '#text' );
+ expect( contents[2].nodeValue ).toEqual( '.' );
+ } );
+
+ it ( "should replace a URL into a link", function() {
+ var parser = new mw.jqueryMsg.parser();
+ var parsed = parser.parse( 'en_link_replace', [ 'http://example.com/foo', 'linking' ] );
+ var contents = parsed.contents();
+ expect( contents.length ).toEqual( 3 );
+ expect( contents[0].nodeName ).toEqual( '#text' );
+ expect( contents[0].nodeValue ).toEqual( 'Complex ' );
+ expect( contents[1].nodeName ).toEqual( 'A' );
+ expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com/foo' );
+ expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' );
+ expect( contents[2].nodeName ).toEqual( '#text' );
+ expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
+ } );
+
+ it ( "should bind a click handler into a link", function() {
+ var parser = new mw.jqueryMsg.parser();
+ var clicked = false;
+ var click = function() { clicked = true; };
+ var parsed = parser.parse( 'en_link_replace', [ click, 'linking' ] );
+ var contents = parsed.contents();
+ expect( contents.length ).toEqual( 3 );
+ expect( contents[0].nodeName ).toEqual( '#text' );
+ expect( contents[0].nodeValue ).toEqual( 'Complex ' );
+ expect( contents[1].nodeName ).toEqual( 'A' );
+ expect( contents[1].getAttribute( 'href' ) ).toEqual( '#' );
+ expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' );
+ expect( contents[2].nodeName ).toEqual( '#text' );
+ expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
+ // determining bindings is hard in IE
+ var anchor = parsed.find( 'a' );
+ if ( ( $.browser.mozilla || $.browser.webkit ) && anchor.click ) {
+ expect( clicked ).toEqual( false );
+ anchor.click();
+ expect( clicked ).toEqual( true );
+ }
+ } );
+
+ it ( "should wrap a jquery arg around link contents -- even another element", function() {
+ var parser = new mw.jqueryMsg.parser();
+ var clicked = false;
+ var click = function() { clicked = true; };
+ var button = $( '<button>' ).click( click );
+ var parsed = parser.parse( 'en_link_replace', [ button, 'buttoning' ] );
+ var contents = parsed.contents();
+ expect( contents.length ).toEqual( 3 );
+ expect( contents[0].nodeName ).toEqual( '#text' );
+ expect( contents[0].nodeValue ).toEqual( 'Complex ' );
+ expect( contents[1].nodeName ).toEqual( 'BUTTON' );
+ expect( contents[1].childNodes[0].nodeValue ).toEqual( 'buttoning' );
+ expect( contents[2].nodeName ).toEqual( '#text' );
+ expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
+ // determining bindings is hard in IE
+ if ( ( $.browser.mozilla || $.browser.webkit ) && button.click ) {
+ expect( clicked ).toEqual( false );
+ parsed.find( 'button' ).click();
+ expect( clicked ).toEqual( true );
+ }
+ } );
+
+
+ } );
+
+
+ describe( "magic keywords", function() {
+ it( "should substitute magic keywords", function() {
+ var options = {
+ magic: {
+ 'alohomora' : 'open'
+ }
+ };
+ var parser = new mw.jqueryMsg.parser( options );
+ expect( parser.parse( 'en_simple_magic' ).html() ).toEqual( 'Simple open message' );
+ } );
+ } );
+
+ describe( "error conditions", function() {
+ it( "should return non-existent key in square brackets", function() {
+ var parser = new mw.jqueryMsg.parser();
+ expect( parser.parse( 'en_does_not_exist' ).html() ).toEqual( '[en_does_not_exist]' );
+ } );
+
+
+ it( "should fail to parse", function() {
+ var parser = new mw.jqueryMsg.parser();
+ expect( function() { parser.parse( 'en_fail' ); } ).toThrow(
+ 'Parse error at position 20 in input: This should fail to {{parse'
+ );
+ } );
+ } );
+
+ describe( "empty parameters", function() {
+ it( "should deal with empty parameters", function() {
+ var parser = new mw.jqueryMsg.parser();
+ var ast = parser.getAst( 'en_undelete_empty_param' );
+ expect( parser.parse( 'en_undelete_empty_param', [ 1 ] ).html() ).toEqual( 'Undelete' );
+ expect( parser.parse( 'en_undelete_empty_param', [ 3 ] ).html() ).toEqual( 'Undelete multiple edits' );
+
+ } );
+ } );
+
+ describe( "easy message interface functions", function() {
+ it( "should allow a global that returns strings", function() {
+ var gM = mw.jqueryMsg.getMessageFunction();
+ // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
+ // a surrounding <SPAN> is needed for html() to work right
+ var expectedHtml = $( '<span>Complex <a href="http://example.com/foo">linking</a> behaviour.</span>' ).html();
+ var result = gM( 'en_link_replace', 'http://example.com/foo', 'linking' );
+ expect( typeof result ).toEqual( 'string' );
+ expect( result ).toEqual( expectedHtml );
+ } );
+
+ it( "should allow a jQuery plugin that appends to nodes", function() {
+ $.fn.msg = mw.jqueryMsg.getPlugin();
+ var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) );
+ var clicked = false;
+ var $button = $( '<button>' ).click( function() { clicked = true; } );
+ $div.find( '.foo' ).msg( 'en_link_replace', $button, 'buttoning' );
+ // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
+ // a surrounding <SPAN> is needed for html() to work right
+ var expectedHtml = $( '<span>Complex <button>buttoning</button> behaviour.</span>' ).html();
+ var createdHtml = $div.find( '.foo' ).html();
+ // it is hard to test for clicks with IE; also it inserts or removes spaces around nodes when creating HTML tags, depending on their type.
+ // so need to check the strings stripped of spaces.
+ if ( ( $.browser.mozilla || $.browser.webkit ) && $button.click ) {
+ expect( createdHtml ).toEqual( expectedHtml );
+ $div.find( 'button ').click();
+ expect( clicked ).toEqual( true );
+ } else if ( $.browser.ie ) {
+ expect( createdHtml.replace( /\s/, '' ) ).toEqual( expectedHtml.replace( /\s/, '' ) );
+ }
+ delete $.fn.msg;
+ } );
+
+ it( "jQuery plugin should escape incoming string arguments", function() {
+ $.fn.msg = mw.jqueryMsg.getPlugin();
+ var $div = $( '<div>' ).addClass( 'foo' );
+ $div.msg( 'en_replace', '<p>x</p>' ); // looks like HTML, but as a string, should be escaped.
+ // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
+ var expectedHtml = $( '<div class="foo">Simple &lt;p&gt;x&lt;/p&gt; replacement</div>' ).html();
+ var createdHtml = $div.html();
+ expect( expectedHtml ).toEqual( createdHtml );
+ delete $.fn.msg;
+ } );
+
+
+ it( "jQuery plugin should never execute scripts", function() {
+ window.en_evil = false;
+ $.fn.msg = mw.jqueryMsg.getPlugin();
+ var $div = $( '<div>' );
+ $div.msg( 'en_evil' );
+ expect( window.en_evil ).toEqual( false );
+ delete $.fn.msg;
+ } );
+
+
+ // n.b. this passes because jQuery already seems to strip scripts away; however, it still executes them if they are appended to any element.
+ it( "jQuery plugin should never emit scripts", function() {
+ $.fn.msg = mw.jqueryMsg.getPlugin();
+ var $div = $( '<div>' );
+ $div.msg( 'en_evil' );
+ // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
+ var expectedHtml = $( '<div>This has tags</div>' ).html();
+ var createdHtml = $div.html();
+ expect( expectedHtml ).toEqual( createdHtml );
+ console.log( 'expected: ' + expectedHtml );
+ console.log( 'created: ' + createdHtml );
+ delete $.fn.msg;
+ } );
+
+
+
+ } );
+
+ // The parser functions can throw errors, but let's not actually blow up for the user -- instead dump the error into the interface so we have
+ // a chance at fixing this
+ describe( "easy message interface functions with graceful failures", function() {
+ it( "should allow a global that returns strings, with graceful failure", function() {
+ var gM = mw.jqueryMsg.getMessageFunction();
+ // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
+ // a surrounding <SPAN> is needed for html() to work right
+ var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html();
+ var result = gM( 'en_fail' );
+ expect( typeof result ).toEqual( 'string' );
+ expect( result ).toEqual( expectedHtml );
+ } );
+
+ it( "should allow a global that returns strings, with graceful failure on missing magic words", function() {
+ var gM = mw.jqueryMsg.getMessageFunction();
+ // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
+ // a surrounding <SPAN> is needed for html() to work right
+ var expectedHtml = $( '<span>en_fail_magic: unknown operation "sietname"</span>' ).html();
+ var result = gM( 'en_fail_magic' );
+ expect( typeof result ).toEqual( 'string' );
+ expect( result ).toEqual( expectedHtml );
+ } );
+
+
+ it( "should allow a jQuery plugin, with graceful failure", function() {
+ $.fn.msg = mw.jqueryMsg.getPlugin();
+ var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) );
+ $div.find( '.foo' ).msg( 'en_fail' );
+ // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
+ // a surrounding <SPAN> is needed for html() to work right
+ var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html();
+ var createdHtml = $div.find( '.foo' ).html();
+ expect( createdHtml ).toEqual( expectedHtml );
+ delete $.fn.msg;
+ } );
+
+ } );
+
+
+
+
+ describe( "test plurals and other language-specific functions", function() {
+ /* copying some language definitions in here -- it's hard to make this test fast and reliable
+ otherwise, and we don't want to have to know the mediawiki URL from this kind of test either.
+ We also can't preload the langs for the test since they clobber the same namespace.
+ In principle Roan said it was okay to change how languages worked so that didn't happen... maybe
+ someday. We'd have to the same kind of importing of the default rules for most rules, or maybe
+ come up with some kind of subclassing scheme for languages */
+ var languageClasses = {
+ ar: {
+ /**
+ * Arabic (العربية) language functions
+ */
+
+ convertPlural: function( count, forms ) {
+ forms = mw.language.preConvertPlural( forms, 6 );
+ if ( count === 0 ) {
+ return forms[0];
+ }
+ if ( count == 1 ) {
+ return forms[1];
+ }
+ if ( count == 2 ) {
+ return forms[2];
+ }
+ if ( count % 100 >= 3 && count % 100 <= 10 ) {
+ return forms[3];
+ }
+ if ( count % 100 >= 11 && count % 100 <= 99 ) {
+ return forms[4];
+ }
+ return forms[5];
+ },
+
+ digitTransformTable: {
+ '0': '٠', // &#x0660;
+ '1': '١', // &#x0661;
+ '2': '٢', // &#x0662;
+ '3': '٣', // &#x0663;
+ '4': '٤', // &#x0664;
+ '5': '٥', // &#x0665;
+ '6': '٦', // &#x0666;
+ '7': '٧', // &#x0667;
+ '8': '٨', // &#x0668;
+ '9': '٩', // &#x0669;
+ '.': '٫', // &#x066b; wrong table ?
+ ',': '٬' // &#x066c;
+ }
+
+ },
+ en: { },
+ fr: {
+ convertPlural: function( count, forms ) {
+ forms = mw.language.preConvertPlural( forms, 2 );
+ return ( count <= 1 ) ? forms[0] : forms[1];
+ }
+ },
+ jp: { },
+ zh: { }
+ };
+
+ /* simulate how the language classes override, or don't, the standard functions in mw.language */
+ $.each( languageClasses, function( langCode, rules ) {
+ $.each( [ 'convertPlural', 'convertNumber' ], function( i, propertyName ) {
+ if ( typeof rules[ propertyName ] === 'undefined' ) {
+ rules[ propertyName ] = mw.language[ propertyName ];
+ }
+ } );
+ } );
+
+ $.each( jasmineMsgSpec, function( i, test ) {
+ it( "should parse " + test.name, function() {
+ // using language override so we don't have to muck with global namespace
+ var parser = new mw.jqueryMsg.parser( { language: languageClasses[ test.lang ] } );
+ var parsedHtml = parser.parse( test.key, test.args ).html();
+ expect( parsedHtml ).toEqual( test.result );
+ } );
+ } );
+
+ } );
+
+ } );
+} )( window.mediaWiki, jQuery );
diff --git a/tests/jasmine/spec_makers/makeJqueryMsgSpec.php b/tests/jasmine/spec_makers/makeJqueryMsgSpec.php
new file mode 100644
index 00000000..1ac8dcba
--- /dev/null
+++ b/tests/jasmine/spec_makers/makeJqueryMsgSpec.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * This PHP script defines the spec that the Javascript message parser should conform to.
+ *
+ * It does this by looking up the results of various string kinds of string parsing, with various languages,
+ * in the current installation of MediaWiki. It then outputs a static specification, mapping expected inputs to outputs,
+ * which can be used with the JasmineBDD framework. This specification can then be used by simply including it into
+ * the SpecRunner.html file.
+ *
+ * This is similar to Michael Dale (mdale@mediawiki.org)'s parser tests, except that it doesn't look up the
+ * API results while doing the test, so the Jasmine run is much faster(at the cost of being out of date in rare
+ * circumstances. But mostly the parsing that we are doing in Javascript doesn't change much.)
+ *
+ */
+
+$maintenanceDir = dirname( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) ) . '/maintenance';
+
+require( "$maintenanceDir/Maintenance.php" );
+
+class MakeLanguageSpec extends Maintenance {
+
+ static $keyToTestArgs = array(
+ 'undelete_short' => array(
+ array( 0 ),
+ array( 1 ),
+ array( 2 ),
+ array( 5 ),
+ array( 21 ),
+ array( 101 )
+ ),
+ 'category-subcat-count' => array(
+ array( 0, 10 ),
+ array( 1, 1 ),
+ array( 1, 2 ),
+ array( 3, 30 )
+ )
+ );
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Create a JasmineBDD-compatible specification for message parsing";
+ // add any other options here
+ }
+
+ public function execute() {
+ list( $messages, $tests ) = $this->getMessagesAndTests();
+ $this->writeJavascriptFile( $messages, $tests, "spec/mediawiki.language.parser.spec.data.js" );
+ }
+
+ private function getMessagesAndTests() {
+ $messages = array();
+ $tests = array();
+ $wfMsgExtOptions = array( 'parsemag' );
+ foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) {
+ $wfMsgExtOptions['language'] = $languageCode;
+ foreach ( self::$keyToTestArgs as $key => $testArgs ) {
+ foreach ($testArgs as $args) {
+ // get the raw template, without any transformations
+ $template = wfMsgGetKey( $key, /* useDb */ true, $languageCode, /* transform */ false );
+
+ // get the magic-parsed version with args
+ $wfMsgExtArgs = array_merge( array( $key, $wfMsgExtOptions ), $args );
+ $result = call_user_func_array( 'wfMsgExt', $wfMsgExtArgs );
+
+ // record the template, args, language, and expected result
+ // fake multiple languages by flattening them together
+ $langKey = $languageCode . '_' . $key;
+ $messages[ $langKey ] = $template;
+ $tests[] = array(
+ 'name' => $languageCode . " " . $key . " " . join( ",", $args ),
+ 'key' => $langKey,
+ 'args' => $args,
+ 'result' => $result,
+ 'lang' => $languageCode
+ );
+ }
+ }
+ }
+ return array( $messages, $tests );
+ }
+
+ private function writeJavascriptFile( $messages, $tests, $dataSpecFile ) {
+ global $argv;
+ $arguments = count($argv) ? $argv : $_SERVER[ 'argv' ];
+
+ $json = new Services_JSON;
+ $json->pretty = true;
+ $javascriptPrologue = "// This file stores the results from the PHP parser for certain messages and arguments,\n"
+ . "// so we can test the equivalent Javascript libraries.\n"
+ . '// Last generated with ' . join(' ', $arguments) . ' at ' . gmdate('c') . "\n\n";
+ $javascriptMessages = "mediaWiki.messages.set( " . $json->encode( $messages, true ) . " );\n";
+ $javascriptTests = 'var jasmineMsgSpec = ' . $json->encode( $tests, true ) . ";\n";
+
+ $fp = fopen( $dataSpecFile, 'w' );
+ if ( !$fp ) {
+ die( "couldn't open $dataSpecFile for writing" );
+ }
+ $success = fwrite( $fp, $javascriptPrologue . $javascriptMessages . $javascriptTests );
+ if ( !$success ) {
+ die( "couldn't write to $dataSpecFile" );
+ }
+ $success = fclose( $fp );
+ if ( !$success ) {
+ die( "couldn't close $dataSpecFile" );
+ }
+ }
+}
+
+$maintClass = "MakeLanguageSpec";
+require_once( "$maintenanceDir/doMaintenance.php" );
+
+
+
diff --git a/tests/parser/README b/tests/parser/README
new file mode 100644
index 00000000..8b413376
--- /dev/null
+++ b/tests/parser/README
@@ -0,0 +1,8 @@
+Parser tests are run using our PHPUnit test suite in tests/phpunit:
+
+ $ cd tests/phpunit
+ ./phpunit.php --group Parser
+
+You can optionally filter by title using --regex. I.e. :
+
+ ./phpunit.php --group Parser --regex="Bug 6200"
diff --git a/tests/parser/parserTest.inc b/tests/parser/parserTest.inc
index 0ce7c997..30e451b3 100644
--- a/tests/parser/parserTest.inc
+++ b/tests/parser/parserTest.inc
@@ -105,6 +105,9 @@ class ParserTest {
$this->showOutput = isset( $options['show-output'] );
+ if ( isset( $options['filter'] ) ) {
+ $options['regex'] = $options['filter'];
+ }
if ( isset( $options['regex'] ) ) {
if ( isset( $options['record'] ) ) {
@@ -132,45 +135,65 @@ class ParserTest {
}
static function setUp() {
- global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList,
+ global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
$wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache,
$wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
$parserMemc, $wgThumbnailScriptPath, $wgScriptPath,
- $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath;
+ $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath, $wgExtensionAssetsPath,
+ $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType;
$wgScript = '/index.php';
$wgScriptPath = '/';
$wgArticlePath = '/wiki/$1';
$wgStyleSheetPath = '/skins';
$wgStylePath = '/skins';
+ $wgExtensionAssetsPath = '/extensions';
$wgThumbnailScriptPath = false;
$wgLocalFileRepo = array(
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'directory' => wfTempDir() . '/test-repo',
- 'url' => 'http://example.com/images',
- 'deletedDir' => wfTempDir() . '/test-repo/delete',
- 'hashLevels' => 2,
+ 'class' => 'LocalRepo',
+ 'name' => 'local',
+ 'url' => 'http://example.com/images',
+ 'hashLevels' => 2,
'transformVia404' => false,
+ 'backend' => new FSFileBackend( array(
+ 'name' => 'local-backend',
+ 'lockManager' => 'fsLockManager',
+ 'containerPaths' => array(
+ 'local-public' => wfTempDir() . '/test-repo/public',
+ 'local-thumb' => wfTempDir() . '/test-repo/thumb',
+ 'local-temp' => wfTempDir() . '/test-repo/temp',
+ 'local-deleted' => wfTempDir() . '/test-repo/deleted',
+ )
+ ) )
);
$wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
$wgNamespaceAliases['Image'] = NS_FILE;
$wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
+ // XXX: tests won't run without this (for CACHE_DB)
+ if ( $wgMainCacheType === CACHE_DB ) {
+ $wgMainCacheType = CACHE_NONE;
+ }
+ if ( $wgMessageCacheType === CACHE_DB ) {
+ $wgMessageCacheType = CACHE_NONE;
+ }
+ if ( $wgParserCacheType === CACHE_DB ) {
+ $wgParserCacheType = CACHE_NONE;
+ }
$wgEnableParserCache = false;
- $wgDeferredUpdateList = array();
- $wgMemc = wfGetMainCache();
+ DeferredUpdates::clearPendingUpdates();
+ $wgMemc = wfGetMainCache(); // checks $wgMainCacheType
$messageMemc = wfGetMessageCacheStorage();
$parserMemc = wfGetParserCacheStorage();
// $wgContLang = new StubContLang;
$wgUser = new User;
$context = new RequestContext;
- $wgLang = $context->getLang();
+ $wgLang = $context->getLanguage();
$wgOut = $context->getOutput();
$wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
- $wgRequest = new WebRequest;
+ $wgRequest = $context->getRequest();
if ( $wgStyleDirectory === false ) {
$wgStyleDirectory = "$IP/skins";
@@ -423,10 +446,10 @@ class ParserTest {
}
$opts = $this->parseOptions( $opts );
- $this->setupGlobals( $opts, $config );
+ $context = $this->setupGlobals( $opts, $config );
- $user = new User();
- $options = ParserOptions::newFromUser( $user );
+ $user = $context->getUser();
+ $options = ParserOptions::newFromContext( $context );
if ( isset( $opts['title'] ) ) {
$titleText = $opts['title'];
@@ -452,8 +475,7 @@ class ParserTest {
$replace = $opts['replace'][1];
$out = $parser->replaceSection( $input, $section, $replace );
} elseif ( isset( $opts['comment'] ) ) {
- $linker = $user->getSkin();
- $out = $linker->formatComment( $input, $title, $local );
+ $out = Linker::formatComment( $input, $title, $local );
} elseif ( isset( $opts['preload'] ) ) {
$out = $parser->getpreloadText( $input, $title, $options );
} else {
@@ -471,10 +493,9 @@ class ParserTest {
if ( isset( $opts['ill'] ) ) {
$out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) );
} elseif ( isset( $opts['cat'] ) ) {
- global $wgOut;
-
- $wgOut->addCategoryLinks( $output->getCategories() );
- $cats = $wgOut->getCategoryLinks();
+ $outputPage = $context->getOutput();
+ $outputPage->addCategoryLinks( $output->getCategories() );
+ $cats = $outputPage->getCategoryLinks();
if ( isset( $cats['normal'] ) ) {
$out = $this->tidy( implode( ' ', $cats['normal'] ) );
@@ -609,10 +630,19 @@ class ParserTest {
'wgLocalFileRepo' => array(
'class' => 'LocalRepo',
'name' => 'local',
- 'directory' => $this->uploadDir,
'url' => 'http://example.com/images',
'hashLevels' => 2,
'transformVia404' => false,
+ 'backend' => new FSFileBackend( array(
+ 'name' => 'local-backend',
+ 'lockManager' => 'fsLockManager',
+ 'containerPaths' => array(
+ 'local-public' => $this->uploadDir,
+ 'local-thumb' => $this->uploadDir . '/thumb',
+ 'local-temp' => $this->uploadDir . '/temp',
+ 'local-deleted' => $this->uploadDir . '/delete',
+ )
+ ) )
),
'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
'wgStylePath' => '/skins',
@@ -650,6 +680,7 @@ class ParserTest {
'wgExternalLinkTarget' => false,
'wgAlwaysUseTidy' => false,
'wgHtml5' => true,
+ 'wgCleanupPresentationalAttributes' => true,
'wgWellFormedXml' => true,
'wgAllowMicrodataAttributes' => true,
'wgAdaptiveMessageCache' => true,
@@ -681,7 +712,7 @@ class ParserTest {
$GLOBALS['wgMemc'] = new EmptyBagOStuff;
$context = new RequestContext();
- $GLOBALS['wgLang'] = $context->getLang();
+ $GLOBALS['wgLang'] = $context->getLanguage();
$GLOBALS['wgOut'] = $context->getOutput();
$GLOBALS['wgUser'] = new User();
@@ -689,10 +720,11 @@ class ParserTest {
global $wgHooks;
$wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
- $wgHooks['ParserTestParser'][] = 'ParserTestStaticParserHook::setup';
$wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
MagicWord::clearCache();
+
+ return $context;
}
/**
@@ -700,7 +732,7 @@ class ParserTest {
* Some of these probably aren't necessary.
*/
private function listTables() {
- $tables = array( 'user', 'user_properties', 'page', 'page_restrictions',
+ $tables = array( 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage',
@@ -709,8 +741,9 @@ class ParserTest {
'archive', 'user_groups', 'page_props', 'category', 'msg_resource', 'msg_resource_links'
);
- if ( in_array( $this->db->getType(), array( 'mysql', 'sqlite', 'oracle' ) ) )
+ if ( in_array( $this->db->getType(), array( 'mysql', 'sqlite', 'oracle' ) ) ) {
array_push( $tables, 'searchindex' );
+ }
// Allow extensions to add to the list of tables to duplicate;
// may be necessary if they hook into page save or other code
@@ -753,17 +786,14 @@ class ParserTest {
}
$temporary = $this->useTemporaryTables || $dbType == 'postgres';
- $tables = $this->listTables();
$prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
$this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
$this->dbClone->useTemporaryTables( $temporary );
$this->dbClone->cloneTableStructure();
- if ( $dbType == 'oracle' )
- $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
-
if ( $dbType == 'oracle' ) {
+ $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
# Insert 0 user to prevent FK violations
# Anonymous user
@@ -807,7 +837,6 @@ class ParserTest {
'iw_local' => 1 ),
) );
-
# Update certain things in site_stats
$this->db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ) );
@@ -908,9 +937,9 @@ class ParserTest {
return $dir;
}
- wfMkdirParents( $dir . '/3/3a' );
+ wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
- wfMkdirParents( $dir . '/0/09' );
+ wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" );
return $dir;
@@ -1077,7 +1106,9 @@ class ParserTest {
$shellInfile = wfEscapeShellArg($infile);
$shellOutfile = wfEscapeShellArg($outfile);
- $diff = wfIsWindows()
+ global $wgDiff3;
+ // we assume that people with diff3 also have usual diff
+ $diff = ( wfIsWindows() && !$wgDiff3 )
? `fc $shellInfile $shellOutfile`
: `diff -au $shellInfile $shellOutfile`;
unlink( $infile );
@@ -1130,30 +1161,35 @@ class ParserTest {
* @param $name String: the title, including any prefix
* @param $text String: the article text
* @param $line Integer: the input line number, for reporting errors
+ * @param $ignoreDuplicate Boolean: whether to silently ignore duplicate pages
*/
- static public function addArticle( $name, $text, $line = 'unknown' ) {
+ static public function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
global $wgCapitalLinks;
- $text = self::chomp($text);
-
$oldCapitalLinks = $wgCapitalLinks;
$wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
+ $text = self::chomp( $text );
$name = self::chomp( $name );
+
$title = Title::newFromText( $name );
if ( is_null( $title ) ) {
- throw new MWException( "invalid title ('$name' => '$title') at line $line\n" );
+ throw new MWException( "invalid title '$name' at line $line\n" );
}
- $aid = $title->getArticleID( Title::GAID_FOR_UPDATE );
+ $page = WikiPage::factory( $title );
+ $page->loadPageData( 'fromdbmaster' );
- if ( $aid != 0 ) {
- throw new MWException( "duplicate article '$name' at line $line\n" );
+ if ( $page->exists() ) {
+ if ( $ignoreDuplicate == 'ignoreduplicate' ) {
+ return;
+ } else {
+ throw new MWException( "duplicate article '$name' at line $line\n" );
+ }
}
- $art = new Article( $title );
- $art->doEdit( $text, '', EDIT_NEW );
+ $page->doEdit( $text, '', EDIT_NEW );
$wgCapitalLinks = $oldCapitalLinks;
}
@@ -1204,7 +1240,7 @@ class ParserTest {
return true;
}
- /*
+ /**
* Run the "tidy" command on text if the $wgUseTidy
* global is true
*
diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt
index 999cd717..d304b19c 100644
--- a/tests/parser/parserTests.txt
+++ b/tests/parser/parserTests.txt
@@ -615,6 +615,26 @@ disabled
</dl>
!! end
+!! test
+Definition and unordered list using wiki syntax nested in unordered list using html tags.
+!! input
+<ul><li>
+; term : description
+* unordered
+</li>
+</ul>
+!! result
+<ul><li>
+<dl><dt> term&#160;</dt><dd> description
+</dd></dl>
+<ul><li> unordered
+</li></ul>
+</li>
+</ul>
+
+!! end
+
+
###
### External links
###
@@ -1050,8 +1070,10 @@ http://www.example.com/?title=AT%26T
</p>
!! end
+# According to http://dev.w3.org/html5/spec/Overview.html#parsing-urls a plain
+# % is actually legal in HTML5. Any change in output would need testing though.
!! test
-Bug 4781, 5267: %26 in URL
+Bug 4781, 5267: %25 in URL
!! input
http://www.example.com/?title=100%25_Bran
!! result
@@ -1169,6 +1191,29 @@ URL-encoding in URL functions (multiple parameters)
</p>
!! end
+!! test
+Brackets in urls
+!! input
+http://example.com/index.php?foozoid%5B%5D=bar
+
+http://example.com/index.php?foozoid&#x5B;&#x5D;=bar
+!! result
+<p><a rel="nofollow" class="external free" href="http://example.com/index.php?foozoid%5B%5D=bar">http://example.com/index.php?foozoid%5B%5D=bar</a>
+</p><p><a rel="nofollow" class="external free" href="http://example.com/index.php?foozoid%5B%5D=bar">http://example.com/index.php?foozoid%5B%5D=bar</a>
+</p>
+!! end
+
+!! test
+IPv6 urls (bug 21261)
+!! options
+disabled
+!! input
+http://[2404:130:0:1000::187:2]/index.php
+!! result
+<p><a rel="nofollow" class="external free" href="http://[2404:130:0:1000::187:2]/index.php">http://[2404:130:0:1000::187:2]/index.php</a>
+</p>
+!! end
+
###
### Quotes
###
@@ -1346,7 +1391,7 @@ Multiplication table
!! test
Table rowspan
!! input
-{| align=right border=1
+{| border=1
| Cell 1, row 1
|rowspan=2| Cell 2, row 1 (and 2)
| Cell 3, row 1
@@ -1355,7 +1400,7 @@ Table rowspan
| Cell 3, row 2
|}
!! result
-<table align="right" border="1">
+<table border="1">
<tr>
<td> Cell 1, row 1
</td>
@@ -1435,6 +1480,28 @@ Table security: embedded pipes (http://lists.wikimedia.org/mailman/htdig/wikitec
!! end
+!! test
+Indented table markup mixed with indented pre content (proposed in bug 6200)
+!! input
+ <table>
+ <tr>
+ <td>
+ Text that should be rendered preformatted
+ </td>
+ </tr>
+ </table>
+!! result
+ <table>
+ <tr>
+ <td>
+<pre>Text that should be rendered preformatted
+</pre>
+ </td>
+ </tr>
+ </table>
+
+!! end
+
###
### Internal links
@@ -1872,7 +1939,7 @@ Inline interwiki link with empty title (bug 2372)
!! input
[[MeatBall:]]
!! result
-<p><a href="http://www.usemod.com/cgi-bin/mb.pl?" class="extiw" title="meatball:">MeatBall:</a>
+<p><a href="http://www.usemod.com/cgi-bin/mb.pl" class="extiw" title="meatball:">MeatBall:</a>
</p>
!! end
@@ -1969,13 +2036,13 @@ Incorrecly removing closing slashes from correctly formed XHTML
!! test
Failing to transform badly formed HTML into correct XHTML
!! input
-<br clear=left>
-<br clear=right>
-<br clear=all>
+<br style="clear: left;">
+<br style="clear: right;">
+<br style="clear: both;">
!! result
-<p><br clear="left" />
-<br clear="right" />
-<br clear="all" />
+<p><br style="clear: left;" />
+<br style="clear: right;" />
+<br style="clear: both;" />
</p>
!!end
@@ -3027,35 +3094,6 @@ section=1
!! result
==Section 1==
!! end
-!! article
-Template:Top-level template
-!! text
-{{Nested template}}
-!! endarticle
-
-!! article
-Template:Nested template
-!! text
-*Item 1
-*Item 2
-!! endarticle
-
-!! test
-Line-start flag in a nested template call
-!! input
-*Item A
-*Item B
-
-{{Top-level template}}
-!! result
-<ul><li>Item A
-</li><li>Item B
-</li></ul>
-<ul><li>Item 1
-</li><li>Item 2
-</li></ul>
-
-!! end
###
### Pre-save transform tests
@@ -3421,6 +3459,66 @@ pst title=[[Ns:Somearticle (IGNORED), Context]]
!! end
!! test
+pre-save transform: context links ("pipe trick") with full-width parens and no space (Japanese and Chinese style, bug 30149)
+!! options
+pst
+!! input
+[[Article(context)|]]
+[[Bar:Article(context)|]]
+[[:Bar:Article(context)|]]
+[[|Article(context)]]
+[[Bar:X (Y) Z|]]
+[[:Bar:X (Y) Z|]]
+!! result
+[[Article(context)|Article]]
+[[Bar:Article(context)|Article]]
+[[:Bar:Article(context)|Article]]
+[[Article(context)]]
+[[Bar:X (Y) Z|X (Y) Z]]
+[[:Bar:X (Y) Z|X (Y) Z]]
+!! end
+
+!! test
+pre-save transform: context links ("pipe trick") with full-width parens and space (Japanese and Chinese style, bug 30149)
+!! options
+pst
+!! input
+[[Article (context)|]]
+[[Bar:Article (context)|]]
+[[:Bar:Article (context)|]]
+[[|Article (context)]]
+[[Bar:X (Y) Z|]]
+[[:Bar:X (Y) Z|]]
+!! result
+[[Article (context)|Article]]
+[[Bar:Article (context)|Article]]
+[[:Bar:Article (context)|Article]]
+[[Article (context)]]
+[[Bar:X (Y) Z|X (Y) Z]]
+[[:Bar:X (Y) Z|X (Y) Z]]
+!! end
+
+!! test
+pre-save transform: context links ("pipe trick") with parens and no space (Korean style, bug 30149)
+!! options
+pst
+!! input
+[[Article(context)|]]
+[[Bar:Article(context)|]]
+[[:Bar:Article(context)|]]
+[[|Article(context)]]
+[[Bar:X(Y)Z|]]
+[[:Bar:X(Y)Z|]]
+!! result
+[[Article(context)|Article]]
+[[Bar:Article(context)|Article]]
+[[:Bar:Article(context)|Article]]
+[[Article(context)]]
+[[Bar:X(Y)Z|X(Y)Z]]
+[[:Bar:X(Y)Z|X(Y)Z]]
+!! end
+
+!! test
pre-save transform: trim trailing empty lines
!! options
pst
@@ -4487,9 +4585,9 @@ div with unquoted attribute
!! test
div with illegal double attributes
!! input
-<div align="center" align="right">HTML rocks</div>
+<div id="a" id="b">HTML rocks</div>
!! result
-<div align="right">HTML rocks</div>
+<div id="b">HTML rocks</div>
!!end
@@ -4519,9 +4617,9 @@ Table multiple attributes correction
!! test
DIV IN UPPERCASE
!! input
-<DIV ALIGN="center">HTML ROCKS</DIV>
+<DIV ID="x">HTML ROCKS</DIV>
!! result
-<div align="center">HTML ROCKS</div>
+<div id="x">HTML ROCKS</div>
!!end
@@ -8381,7 +8479,17 @@ comment title=[[Main Page]]
!! input
/* External links */ removed bogus entries
!! result
-<span class="autocomment"><a href="/wiki/Main_Page#External_links" title="Main Page">→</a>External links: </span> removed bogus entries
+<a href="/wiki/Main_Page#External_links" title="Main Page">→</a>‎<span dir="auto"><span class="autocomment">External links: </span> removed bogus entries</span>
+!!end
+
+!! test
+Edit comment with section link and text before it (non-local, eg in history list)
+!! options
+comment title=[[Main Page]]
+!! input
+pre-comment text /* External links */ removed bogus entries
+!! result
+pre-comment text - <a href="/wiki/Main_Page#External_links" title="Main Page">→</a>‎<span dir="auto"><span class="autocomment">External links: </span> removed bogus entries</span>
!!end
!! test
@@ -8391,7 +8499,7 @@ comment local title=[[Main Page]]
!! input
/* External links */ removed bogus entries
!! result
-<span class="autocomment"><a href="#External_links">→</a>External links: </span> removed bogus entries
+<a href="#External_links">→</a>‎<span dir="auto"><span class="autocomment">External links: </span> removed bogus entries</span>
!!end
!! test
@@ -8478,7 +8586,7 @@ title=[[Main Page]]
!!input
/* __hello__world__ */
!! result
-<span class="autocomment"><a href="/wiki/Main_Page#hello_world" title="Main Page">→</a>__hello__world__</span>
+<a href="/wiki/Main_Page#hello_world" title="Main Page">→</a>‎<span dir="auto"><span class="autocomment">__hello__world__</span></span>
!! end
!! test
@@ -8493,6 +8601,8 @@ comment
!! test
Bad images - basic functionality
+!! options
+disabled
!! input
[[File:Bad.jpg]]
!! result
@@ -8500,6 +8610,8 @@ Bad images - basic functionality
!! test
Bad images - bug 16039: text after bad image disappears
+!! options
+disabled
!! input
Foo bar
[[File:Bad.jpg]]
@@ -8713,6 +8825,33 @@ Text&apos;s been normalized?
</p>
!! end
+!! test
+Bug 19052 U+3000 IDEOGRAPHIC SPACE should terminate free external links
+!! input
+http://www.example.org/ <-- U+3000 (vim: ^Vu3000)
+!! result
+<p><a rel="nofollow" class="external free" href="http://www.example.org/">http://www.example.org/</a> &lt;-- U+3000 (vim: ^Vu3000)
+</p>
+!! end
+
+!! test
+Bug 19052 U+3000 IDEOGRAPHIC SPACE should terminate bracketed external links
+!! input
+[http://www.example.org/ ideograms]
+!! result
+<p><a rel="nofollow" class="external text" href="http://www.example.org/">ideograms</a>
+</p>
+!! end
+
+!! test
+Bug 19052 U+3000 IDEOGRAPHIC SPACE should terminate external images links
+!! input
+http://www.example.org/pic.png <-- U+3000 (vim: ^Vu3000)
+!! result
+<p><img src="http://www.example.org/pic.png" alt="pic.png" /> &lt;-- U+3000 (vim: ^Vu3000)
+</p>
+!! end
+
!! article
Mediawiki:loop1
!! text
@@ -8743,6 +8882,22 @@ Bug 31098 Template which includes system messages which includes the template
!! end
!! test
+Deprecated presentational attributes are converted to css
+!! input
+{|
+| valign=top align=left width=100 height=25% | Asdf
+|}
+<ul type="disc"></ul>
+!! result
+<table>
+<tr>
+<td style="text-align: left; height: 25%; vertical-align: top; width: 100px;"> Asdf
+</td></tr></table>
+<ul style="list-style-type: disc;"></ul>
+
+!! end
+
+!! test
Bug31490 Turkish: ucfirst 'blah'
!! options
language=tr
@@ -8797,6 +8952,132 @@ language=en
</p>
!! end
+!! test
+Bug 26375: TOC with italics
+!! options
+title=[[Main Page]]
+!! input
+__TOC__
+== ''Lost'' episodes ==
+!! result
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Lost_episodes"><span class="tocnumber">1</span> <span class="toctext"><i>Lost</i> episodes</span></a></li>
+</ul>
+</td></tr></table>
+<h2><span class="editsection">[<a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Lost episodes">edit</a>]</span> <span class="mw-headline" id="Lost_episodes"> <i>Lost</i> episodes </span></h2>
+
+!! end
+
+!! test
+Bug 26375: TOC with bold
+!! options
+title=[[Main Page]]
+!! input
+__TOC__
+== '''should be bold''' then normal text ==
+!! result
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#should_be_bold_then_normal_text"><span class="tocnumber">1</span> <span class="toctext"><b>should be bold</b> then normal text</span></a></li>
+</ul>
+</td></tr></table>
+<h2><span class="editsection">[<a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: should be bold then normal text">edit</a>]</span> <span class="mw-headline" id="should_be_bold_then_normal_text"> <b>should be bold</b> then normal text </span></h2>
+
+!! end
+
+!! test
+Bug 33845: Headings become cursive in TOC when they contain an image
+!! options
+title=[[Main Page]]
+!! input
+__TOC__
+== Image [[Image:foobar.jpg]] ==
+!! result
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Image"><span class="tocnumber">1</span> <span class="toctext">Image</span></a></li>
+</ul>
+</td></tr></table>
+<h2><span class="editsection">[<a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Image">edit</a>]</span> <span class="mw-headline" id="Image"> Image <a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </span></h2>
+
+!! end
+
+!! test
+Bug 33845 (2): Headings become bold in TOC when they contain a blockquote
+!! options
+title=[[Main Page]]
+!! input
+__TOC__
+== <blockquote>Quote</blockquote> ==
+!! result
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Quote"><span class="tocnumber">1</span> <span class="toctext">Quote</span></a></li>
+</ul>
+</td></tr></table>
+<h2><span class="editsection">[<a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Quote">edit</a>]</span> <span class="mw-headline" id="Quote"> <blockquote>Quote</blockquote> </span></h2>
+
+!! end
+
+!! test
+Unclosed tags in TOC
+!! options
+title=[[Main Page]]
+!! input
+__TOC__
+== Proof: 2 < 3 ==
+<small>Hanc marginis exiguitas non caperet.</small>
+QED
+!! result
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Proof:_2_.3C_3"><span class="tocnumber">1</span> <span class="toctext">Proof: 2 &lt; 3</span></a></li>
+</ul>
+</td></tr></table>
+<h2><span class="editsection">[<a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Proof: 2 &lt; 3">edit</a>]</span> <span class="mw-headline" id="Proof:_2_.3C_3"> Proof: 2 &lt; 3 </span></h2>
+<p><small>Hanc marginis exiguitas non caperet.</small>
+QED
+</p>
+!! end
+
+!! test
+Multiple tags in TOC
+!! input
+__TOC__
+== <i>Foo</i> <b>Bar</b> ==
+
+== <i>Foo</i> <blockquote>Bar</blockquote> ==
+!! result
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Foo_Bar"><span class="tocnumber">1</span> <span class="toctext"><i>Foo</i> <b>Bar</b></span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext"><i>Foo</i> Bar</span></a></li>
+</ul>
+</td></tr></table>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline" id="Foo_Bar"> <i>Foo</i> <b>Bar</b> </span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline" id="Foo_Bar_2"> <i>Foo</i> <blockquote>Bar</blockquote> </span></h2>
+
+!! end
+
+!! test
+Tags with parameters in TOC
+!! input
+__TOC__
+== <sup class="in-h2">Hello</sup> ==
+
+== <sup class="a > b">Evilbye</sup> ==
+!! result
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Hello"><span class="tocnumber">1</span> <span class="toctext"><sup>Hello</sup></span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#b.22.3EEvilbye"><span class="tocnumber">2</span> <span class="toctext"><sup> b"&gt;Evilbye</sup></span></a></li>
+</ul>
+</td></tr></table>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Hello">edit</a>]</span> <span class="mw-headline" id="Hello"> <sup class="in-h2">Hello</sup> </span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: b&quot;>Evilbye">edit</a>]</span> <span class="mw-headline" id="b.22.3EEvilbye"> <sup> b"&gt;Evilbye</sup> </span></h2>
+
+!! end
!! article
MediaWiki:Bug32057
@@ -8895,6 +9176,15 @@ Strip marker in anchorencode
</p>
!! end
+!! test
+nowiki inside link inside heading (bug 18295)
+!! input
+==[[foo|x<nowiki>y</nowiki>z]]==
+!! result
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: xyz">edit</a>]</span> <span class="mw-headline" id="xyz"><a href="/index.php?title=Foo&amp;action=edit&amp;redlink=1" class="new" title="Foo (page does not exist)">xyz</a></span></h2>
+
+!! end
+
TODO:
more images
diff --git a/tests/parser/parserTestsParserHook.php b/tests/parser/parserTestsParserHook.php
index 324b8e5c..24d852c5 100644
--- a/tests/parser/parserTestsParserHook.php
+++ b/tests/parser/parserTestsParserHook.php
@@ -29,7 +29,7 @@ class ParserTestParserHook {
static function setup( &$parser ) {
$parser->setHook( 'tag', array( __CLASS__, 'dumpHook' ) );
-
+ $parser->setHook( 'statictag', array( __CLASS__, 'staticTagHook' ) );
return true;
}
@@ -43,4 +43,28 @@ class ParserTestParserHook {
return "<pre>\n$ret</pre>";
}
+
+ static function staticTagHook( $in, $argv, $parser ) {
+ if ( ! count( $argv ) ) {
+ $parser->static_tag_buf = $in;
+ return '';
+ } elseif ( count( $argv ) === 1 && isset( $argv['action'] )
+ && $argv['action'] === 'flush' && $in === null )
+ {
+ // Clear the buffer, we probably don't need to
+ if ( isset( $parser->static_tag_buf ) ) {
+ $tmp = $parser->static_tag_buf;
+ } else {
+ $tmp = '';
+ }
+ $parser->static_tag_buf = null;
+ return $tmp;
+ } else
+ // wtf?
+ return
+ "\nCall this extension as <statictag>string</statictag> or as" .
+ " <statictag action=flush/>, not in any other way.\n" .
+ "text: " . var_export( $in, true ) . "\n" .
+ "argv: " . var_export( $argv, true ) . "\n";
+ }
}
diff --git a/tests/parser/parserTestsStaticParserHook.php b/tests/parser/parserTestsStaticParserHook.php
deleted file mode 100644
index e82f7f3f..00000000
--- a/tests/parser/parserTestsStaticParserHook.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-/**
- * A basic extension that's used by the parser tests to test whether the parser
- * calls extensions when they're called inside comments, it shouldn't do that
- *
- * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason
- *
- * 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
- * @ingroup Testing
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-class ParserTestStaticParserHook {
- static function setup( &$parser ) {
- $parser->setHook( 'statictag', array( __CLASS__, 'hook' ) );
-
- return true;
- }
-
- static function hook( $in, $argv, $parser ) {
- if ( ! count( $argv ) ) {
- $parser->static_tag_buf = $in;
- return '';
- } elseif ( count( $argv ) === 1 && isset( $argv['action'] )
- && $argv['action'] === 'flush' && $in === null )
- {
- // Clear the buffer, we probably don't need to
- if ( isset( $parser->static_tag_buf ) ) {
- $tmp = $parser->static_tag_buf;
- } else {
- $tmp = '';
- }
- $parser->static_tag_buf = null;
- return $tmp;
- } else
- // wtf?
- return
- "\nCall this extension as <statictag>string</statictag> or as" .
- " <statictag action=flush/>, not in any other way.\n" .
- "text: " . var_export( $in, true ) . "\n" .
- "argv: " . var_export( $argv, true ) . "\n";
- }
-}
diff --git a/tests/parser/preprocess/All_system_messages.expected b/tests/parser/preprocess/All_system_messages.expected
index 96d4569b..897c5fb0 100644
--- a/tests/parser/preprocess/All_system_messages.expected
+++ b/tests/parser/preprocess/All_system_messages.expected
@@ -1,4 +1,4 @@
-<root><template lineStart="1"><title>int:allmessagestext</title></template>
+<root><template><title>int:allmessagestext</title></template>
&lt;table border=1 width=100%&gt;&lt;tr&gt;&lt;td&gt;
'''Name'''
diff --git a/tests/parser/preprocess/Factorial.expected b/tests/parser/preprocess/Factorial.expected
index 099029c0..a10fd6ca 100644
--- a/tests/parser/preprocess/Factorial.expected
+++ b/tests/parser/preprocess/Factorial.expected
@@ -1,4 +1,4 @@
-<root><template lineStart="1"><title>#expr:<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=00</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=01</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=02</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*02<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=03</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*03<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=04</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*04<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=05</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*05<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=06</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*06<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=07</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*07<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=08</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*08<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=09</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*09<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=10</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*10<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=11</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*11<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=12</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*12<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=13</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*13<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=14</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*14<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=15</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*15<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=16</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*16<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=17</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*17<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=18</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*18<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=19</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*19<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=20</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*20<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=21</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*21<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=22</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*22<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=23</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*23<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=24</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*24<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=25</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*25<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=26</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*26<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=27</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*27<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=28</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*28<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=29</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*29<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=30</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*30<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=31</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*31<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=32</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*32<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=33</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*33<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=34</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*34<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=35</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*35<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=36</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*36<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=37</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*37<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=38</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*38<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=39</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*39<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=40</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*40<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=41</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*41<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=42</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*42<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=43</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*43<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=44</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*44<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=45</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*45<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=46</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*46<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=47</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*47<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=48</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*48<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=49</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*49<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=50</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*50<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=51</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*51<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=52</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*52<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=53</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*53<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=54</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*54<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=55</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*55<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=56</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*56<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=57</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*57<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=58</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*58<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=59</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*59<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=60</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*60<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=61</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*61<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=62</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*62<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=63</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*63<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=64</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*64<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=65</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*65<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=66</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*66<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=67</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*67<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=68</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*68<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=69</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*69<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=70</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*70<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=71</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*71<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=72</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*72<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=73</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*73<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=74</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*74<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=75</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*75<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=76</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*76<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=77</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*77<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=78</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*78<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=79</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*79<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=80</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*80<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=81</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*81<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=82</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*82<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=83</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*83<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=84</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*84<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=85</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*85<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=86</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*86<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=87</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*87<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=88</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*88<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=89</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*89<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=90</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*90<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=91</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*91<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=92</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*92<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=93</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*93<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=94</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*94<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=95</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*95<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=96</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*96<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=97</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*97<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=98</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*98<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=99</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*99</value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></title></template><ignore>&lt;noinclude&gt;</ignore>
+<root><template><title>#expr:<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=00</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=01</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=02</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*02<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=03</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*03<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=04</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*04<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=05</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*05<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=06</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*06<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=07</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*07<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=08</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*08<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=09</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*09<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=10</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*10<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=11</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*11<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=12</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*12<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=13</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*13<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=14</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*14<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=15</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*15<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=16</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*16<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=17</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*17<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=18</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*18<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=19</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*19<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=20</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*20<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=21</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*21<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=22</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*22<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=23</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*23<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=24</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*24<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=25</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*25<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=26</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*26<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=27</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*27<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=28</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*28<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=29</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*29<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=30</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*30<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=31</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*31<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=32</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*32<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=33</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*33<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=34</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*34<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=35</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*35<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=36</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*36<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=37</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*37<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=38</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*38<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=39</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*39<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=40</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*40<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=41</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*41<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=42</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*42<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=43</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*43<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=44</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*44<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=45</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*45<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=46</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*46<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=47</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*47<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=48</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*48<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=49</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*49<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=50</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*50<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=51</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*51<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=52</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*52<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=53</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*53<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=54</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*54<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=55</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*55<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=56</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*56<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=57</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*57<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=58</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*58<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=59</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*59<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=60</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*60<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=61</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*61<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=62</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*62<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=63</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*63<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=64</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*64<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=65</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*65<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=66</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*66<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=67</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*67<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=68</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*68<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=69</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*69<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=70</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*70<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=71</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*71<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=72</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*72<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=73</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*73<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=74</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*74<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=75</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*75<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=76</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*76<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=77</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*77<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=78</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*78<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=79</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*79<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=80</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*80<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=81</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*81<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=82</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*82<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=83</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*83<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=84</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*84<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=85</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*85<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=86</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*86<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=87</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*87<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=88</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*88<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=89</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*89<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=90</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*90<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=91</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*91<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=92</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*92<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=93</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*93<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=94</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*94<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=95</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*95<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=96</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*96<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=97</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*97<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=98</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*98<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=99</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*99</value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></title></template><ignore>&lt;noinclude&gt;</ignore>
<template lineStart="1"><title>Template documentation</title></template>
This template finds the [[factorial]] of a number. To use it, enter:&lt;br /&gt;
&lt;code&gt;&lt;nowiki&gt;<template><title>factorial</title><part><name index="1" /><value>input</value></part></template>&lt;/nowiki&gt;&lt;/code&gt;&lt;br /&gt;
diff --git a/tests/parserTests.php b/tests/parserTests.php
index 99ea2ed4..d930ac5a 100644
--- a/tests/parserTests.php
+++ b/tests/parserTests.php
@@ -24,8 +24,8 @@
* @ingroup Testing
*/
-$options = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' );
-$optionsWithArgs = array( 'regex', 'seed', 'setversion' );
+$otions = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' );
+$optionsWithArgs = array( 'regex', 'filter', 'seed', 'setversion' );
require_once( dirname( __FILE__ ) . '/../maintenance/commandLine.inc' );
@@ -39,13 +39,14 @@ Options:
--quiet Suppress notification of passed tests (shows only failed tests)
--show-output Show expected and actual output
--color[=yes|no] Override terminal detection and force color output on or off
- use wgCommandLineDarkBg = true; if your term is dark
+ use wgCommandLineDarkBg = true; if your term is dark
--regex Only run tests whose descriptions which match given regex
+ --filter Alias for --regex
--file=<testfile> Run test cases from a custom file instead of parserTests.txt
--record Record tests in database
--compare Compare with recorded results, without updating the database.
--setversion When using --record, set the version string to use (useful
- with git-svn so that you can get the exact revision)
+ with git-svn so that you can get the exact revision)
--keep-uploads Re-use the same upload directory for each test, don't delete it
--fuzz Do a fuzz test instead of a normal test
--seed <n> Start the fuzz test from the specified seed
diff --git a/tests/phpunit/Makefile b/tests/phpunit/Makefile
index 24536efc..8a55dae0 100644
--- a/tests/phpunit/Makefile
+++ b/tests/phpunit/Makefile
@@ -46,17 +46,26 @@ coverage:
parser:
${PU} --group Parser
+parserfuzz:
+ @echo "******************************************************************"
+ @echo "* This WILL kill your computer by eating all memory AND all swap *"
+ @echo "* *"
+ @echo "* If you are on a production machine. ABORT NOW!! *"
+ @echo "* Press control+C to stop *"
+ @echo "* *"
+ @echo "******************************************************************"
+ ${PU} --group Parser,ParserFuzz
noparser:
- ${PU} --exclude-group Parser,Broken,Stub
+ ${PU} --exclude-group Parser,Broken,ParserFuzz,Stub
safe:
- ${PU} --exclude-group Broken,Destructive,Stub
+ ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub
databaseless:
- ${PU} --exclude-group Broken,Destructive,Database,Stub
+ ${PU} --exclude-group Broken,ParserFuzz,Destructive,Database,Stub
database:
- ${PU} --exclude-group Broken,Destructive,Stub --group Database
+ ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub --group Database
list-groups:
${PU} --list-groups
diff --git a/tests/phpunit/MediaWikiLangTestCase.php b/tests/phpunit/MediaWikiLangTestCase.php
index 1cd6a3ba..783f0315 100644
--- a/tests/phpunit/MediaWikiLangTestCase.php
+++ b/tests/phpunit/MediaWikiLangTestCase.php
@@ -13,7 +13,11 @@ abstract class MediaWikiLangTestCase extends MediaWikiTestCase {
self::$oldLang = $wgLang;
self::$oldContLang = $wgContLang;
- if( $wgLanguageCode != $wgContLang->getCode() ) die("nooo!");
+ if( $wgLanguageCode != $wgContLang->getCode() ) {
+ throw new MWException("Error in MediaWikiLangTestCase::setUp(): " .
+ "\$wgLanguageCode ('$wgLanguageCode') is different from " .
+ "\$wgContLang->getCode() (" . $wgContLang->getCode() . ")" );
+ }
$wgLanguageCode = 'en'; # For mainpage to be 'Main Page'
diff --git a/tests/phpunit/MediaWikiPHPUnitCommand.php b/tests/phpunit/MediaWikiPHPUnitCommand.php
index c0d9f363..ea385ad9 100644
--- a/tests/phpunit/MediaWikiPHPUnitCommand.php
+++ b/tests/phpunit/MediaWikiPHPUnitCommand.php
@@ -5,7 +5,10 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
static $additionalOptions = array(
'regex=' => false,
'file=' => false,
+ 'use-filebackend=' => false,
'keep-uploads' => false,
+ 'use-normal-tables' => false,
+ 'reuse-db' => false,
);
public function __construct() {
@@ -17,6 +20,28 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
public static function main( $exit = true ) {
$command = new self;
+
+ if( wfIsWindows() ) {
+ # Windows does not come anymore with ANSI.SYS loaded by default
+ # PHPUnit uses the suite.xml parameters to enable/disable colors
+ # which can be then forced to be enabled with --colors.
+ # The below code inject a parameter just like if the user called
+ # phpunit with a --no-color option (which does not exist). It
+ # overrides the suite.xml setting.
+ # Probably fix bug 29226
+ $command->arguments['colors'] = false;
+ }
+
+ # Makes MediaWiki PHPUnit directory includable so the PHPUnit will
+ # be able to resolve relative files inclusion such as suites/*
+ # PHPUnit uses stream_resolve_include_path() internally
+ # See bug 32022
+ set_include_path(
+ dirname( __FILE__ )
+ .PATH_SEPARATOR
+ . get_include_path()
+ );
+
$command->run($_SERVER['argv'], $exit);
}
@@ -40,6 +65,11 @@ ParserTest-specific options:
--keep-uploads Re-use the same upload directory for each test, don't delete it
+Database options:
+ --use-normal-tables Use normal DB tables.
+ --reuse-db Init DB only if tables are missing and keep after finish.
+
+
EOT;
}
diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php
index 64cb486b..6ec8bdc7 100644
--- a/tests/phpunit/MediaWikiTestCase.php
+++ b/tests/phpunit/MediaWikiTestCase.php
@@ -11,6 +11,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
protected $db;
protected $oldTablePrefix;
protected $useTemporaryTables = true;
+ protected $reuseDB = false;
+ protected $tablesUsed = array(); // tables with data
+
private static $dbSetup = false;
/**
@@ -22,6 +25,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
protected $supportedDBs = array(
'mysql',
'sqlite',
+ 'postgres',
'oracle'
);
@@ -40,8 +44,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
if( $this->needsDB() ) {
-
global $wgDBprefix;
+
+ $this->useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
+ $this->reuseDB = $this->getCliArg('reuse-db');
$this->db = wfGetDB( DB_MASTER );
@@ -81,6 +87,34 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
function addDBData() {}
private function addCoreDBData() {
+ # disabled for performance
+ #$this->tablesUsed[] = 'page';
+ #$this->tablesUsed[] = 'revision';
+
+ if ( $this->db->getType() == 'oracle' ) {
+
+ # Insert 0 user to prevent FK violations
+ # Anonymous user
+ $this->db->insert( 'user', array(
+ 'user_id' => 0,
+ 'user_name' => 'Anonymous' ), __METHOD__, array( 'IGNORE' ) );
+
+ # Insert 0 page to prevent FK violations
+ # Blank page
+ $this->db->insert( 'page', array(
+ 'page_id' => 0,
+ 'page_namespace' => 0,
+ 'page_title' => ' ',
+ 'page_restrictions' => NULL,
+ 'page_counter' => 0,
+ 'page_is_redirect' => 0,
+ 'page_is_new' => 0,
+ 'page_random' => 0,
+ 'page_touched' => $this->db->timestamp(),
+ 'page_latest' => 0,
+ 'page_len' => 0 ), __METHOD__, array( 'IGNORE' ) );
+
+ }
User::resetIdByNameCache();
@@ -98,12 +132,14 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
//Make 1 page with 1 revision
- $article = new Article( Title::newFromText( 'UTPage' ) );
- $article->doEdit( 'UTContent',
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ if ( !$page->getId() == 0 ) {
+ $page->doEdit( 'UTContent',
'UTPageSummary',
EDIT_NEW,
false,
User::newFromName( 'UTSysop' ) );
+ }
}
private function initDB() {
@@ -112,18 +148,20 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
throw new MWException( 'Cannot run unit tests, the database prefix is already "unittest_"' );
}
- $dbClone = new CloneDatabase( $this->db, $this->listTables(), $this->dbPrefix() );
+ $tablesCloned = $this->listTables();
+ $dbClone = new CloneDatabase( $this->db, $tablesCloned, $this->dbPrefix() );
$dbClone->useTemporaryTables( $this->useTemporaryTables );
- $dbClone->cloneTableStructure();
+
+ if ( ( $this->db->getType() == 'oracle' || !$this->useTemporaryTables ) && $this->reuseDB ) {
+ CloneDatabase::changePrefix( $this->dbPrefix() );
+ $this->resetDB();
+ return;
+ } else {
+ $dbClone->cloneTableStructure();
+ }
if ( $this->db->getType() == 'oracle' ) {
$this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
-
- # Insert 0 user to prevent FK violations
- # Anonymous user
- $this->db->insert( 'user', array(
- 'user_id' => 0,
- 'user_name' => 'Anonymous' ) );
}
}
@@ -132,35 +170,25 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
*/
private function resetDB() {
if( $this->db ) {
- foreach( $this->listTables() as $tbl ) {
- if( $tbl == 'interwiki' || $tbl == 'user' ) continue;
- $this->db->delete( $tbl, '*', __METHOD__ );
+ if ( $this->db->getType() == 'oracle' ) {
+ if ( $this->useTemporaryTables ) {
+ wfGetLB()->closeAll();
+ $this->db = wfGetDB( DB_MASTER );
+ } else {
+ foreach( $this->tablesUsed as $tbl ) {
+ if( $tbl == 'interwiki') continue;
+ $this->db->query( 'TRUNCATE TABLE '.$this->db->tableName($tbl), __METHOD__ );
+ }
+ }
+ } else {
+ foreach( $this->tablesUsed as $tbl ) {
+ if( $tbl == 'interwiki' || $tbl == 'user' ) continue;
+ $this->db->delete( $tbl, '*', __METHOD__ );
+ }
}
}
}
- protected function destroyDB() {
- if ( $this->useTemporaryTables || is_null( $this->db ) ) {
- # Don't need to do anything
- return;
- }
-
- $tables = $this->db->listTables( $this->dbPrefix(), __METHOD__ );
-
- foreach ( $tables as $table ) {
- try {
- $sql = $this->db->getType() == 'oracle' ? "DROP TABLE $table CASCADE CONSTRAINTS PURGE" : "DROP TABLE `$table`";
- $this->db->query( $sql, __METHOD__ );
- } catch( MWException $mwe ) {}
- }
-
- if ( $this->db->getType() == 'oracle' )
- $this->db->query( 'BEGIN FILL_WIKI_INFO; END;', __METHOD__ );
-
- CloneDatabase::changePrefix( $this->oldTablePrefix );
- }
-
-
function __call( $func, $args ) {
static $compatibility = array(
'assertInternalType' => 'assertType',
@@ -235,5 +263,16 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
public static function disableInterwikis( $prefix, &$data ) {
return false;
}
-}
+ /**
+ * Don't throw a warning if $function is deprecated and called later
+ *
+ * @param $function String
+ * @return null
+ */
+ function hideDeprecated( $function ) {
+ wfSuppressWarnings();
+ wfDeprecated( $function );
+ wfRestoreWarnings();
+ }
+}
diff --git a/tests/phpunit/StructureTest.php b/tests/phpunit/StructureTest.php
new file mode 100644
index 00000000..f967c18d
--- /dev/null
+++ b/tests/phpunit/StructureTest.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * The tests here verify the structure of the code. This is for outright bugs,
+ * not just style issues.
+ */
+
+class StructureTest extends MediaWikiTestCase {
+ /**
+ * Verify all files that appear to be tests have file names ending in
+ * Test. If the file names do not end in Test, they will not be run.
+ */
+ public function testUnitTestFileNamesEndWithTest() {
+ if ( wfIsWindows() ) {
+ $this->markTestSkipped( 'This test does not work on Windows' );
+ }
+ $rootPath = escapeshellarg( __DIR__ );
+ $testClassRegex = implode( '|', array(
+ 'ApiFormatTestBase',
+ 'ApiTestCase',
+ 'MediaWikiLangTestCase',
+ 'MediaWikiTestCase',
+ 'PHPUnit_Framework_TestCase',
+ ) );
+ $testClassRegex = "^class .* extends ($testClassRegex)";
+ $finder = "find $rootPath -name '*.php' '!' -name '*Test.php'" .
+ " | xargs grep -El '$testClassRegex|function suite\('";
+
+ $results = null;
+ $exitCode = null;
+ exec($finder, $results, $exitCode);
+
+ $this->assertEquals(
+ 0,
+ $exitCode,
+ 'Verify find/grep command succeeds.'
+ );
+
+ $results = array_filter(
+ $results,
+ array( $this, 'filterSuites' )
+ );
+
+ $this->assertEquals(
+ array(),
+ $results,
+ 'Unit test file names must end with Test.'
+ );
+ }
+
+ /**
+ * Filter to remove testUnitTestFileNamesEndWithTest false positives.
+ */
+ public function filterSuites( $filename ) {
+ return strpos( $filename, __DIR__ . '/suites/' ) !== 0;
+ }
+}
diff --git a/tests/phpunit/data/db/mysql/functions.sql b/tests/phpunit/data/db/mysql/functions.sql
new file mode 100644
index 00000000..9e5e470f
--- /dev/null
+++ b/tests/phpunit/data/db/mysql/functions.sql
@@ -0,0 +1,12 @@
+-- MySQL test file for DatabaseTest::testStoredFunctions()
+
+DELIMITER //
+
+CREATE FUNCTION mw_test_function()
+RETURNS int DETERMINISTIC
+BEGIN
+ SET @foo = 21;
+ RETURN @foo * 2;
+END//
+
+DELIMITER //
diff --git a/tests/phpunit/data/db/postgres/functions.sql b/tests/phpunit/data/db/postgres/functions.sql
new file mode 100644
index 00000000..3086d4d5
--- /dev/null
+++ b/tests/phpunit/data/db/postgres/functions.sql
@@ -0,0 +1,12 @@
+-- Postgres test file for DatabaseTest::testStoredFunctions()
+
+CREATE FUNCTION mw_test_function()
+RETURNS INTEGER
+LANGUAGE plpgsql AS
+$mw$
+DECLARE foo INTEGER;
+BEGIN
+ foo := 21;
+ RETURN foo * 2;
+END
+$mw$;
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.13.sql b/tests/phpunit/data/db/sqlite/tables-1.13.sql
index a0dcb553..66847ab1 100644
--- a/tests/phpunit/includes/db/sqlite/tables-1.13.sql
+++ b/tests/phpunit/data/db/sqlite/tables-1.13.sql
@@ -123,7 +123,7 @@ CREATE TABLE /*$wgDBprefix*/site_stats (
ss_images INTEGER default '0') /*$wgDBTableOptions*/;
CREATE TABLE /*$wgDBprefix*/hitcounter (
- hc_id INTEGER
+ hc_id INTEGER
) ;
CREATE TABLE /*$wgDBprefix*/ipblocks (
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.15.sql b/tests/phpunit/data/db/sqlite/tables-1.15.sql
index 901bac52..6b3a628e 100644
--- a/tests/phpunit/includes/db/sqlite/tables-1.15.sql
+++ b/tests/phpunit/data/db/sqlite/tables-1.15.sql
@@ -141,7 +141,7 @@ CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
CREATE TABLE /*_*/langlinks (
ll_from int unsigned NOT NULL default 0,
-
+
ll_lang varbinary(20) NOT NULL default '',
ll_title varchar(255) binary NOT NULL default ''
) /*$wgDBTableOptions*/;
@@ -181,7 +181,7 @@ CREATE TABLE /*_*/ipblocks (
ipb_block_email bool NOT NULL default 0,
ipb_allow_usertalk bool NOT NULL default 0
) /*$wgDBTableOptions*/;
-
+
CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.16.sql b/tests/phpunit/data/db/sqlite/tables-1.16.sql
index 6e56add2..6e56add2 100644
--- a/tests/phpunit/includes/db/sqlite/tables-1.16.sql
+++ b/tests/phpunit/data/db/sqlite/tables-1.16.sql
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.17.sql b/tests/phpunit/data/db/sqlite/tables-1.17.sql
index 69ae3764..69ae3764 100644
--- a/tests/phpunit/includes/db/sqlite/tables-1.17.sql
+++ b/tests/phpunit/data/db/sqlite/tables-1.17.sql
diff --git a/tests/phpunit/data/db/sqlite/tables-1.18.sql b/tests/phpunit/data/db/sqlite/tables-1.18.sql
new file mode 100644
index 00000000..bedf6c33
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.18.sql
@@ -0,0 +1,535 @@
+-- This is a copy of MediaWiki 1.18 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+ user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ user_name varchar(255) binary NOT NULL default '',
+ user_real_name varchar(255) binary NOT NULL default '',
+ user_password tinyblob NOT NULL,
+ user_newpassword tinyblob NOT NULL,
+ user_newpass_time binary(14),
+ user_email tinytext NOT NULL,
+ user_options blob NOT NULL,
+ user_touched binary(14) NOT NULL default '',
+ user_token binary(32) NOT NULL default '',
+ user_email_authenticated binary(14),
+ user_email_token binary(32),
+ user_email_token_expires binary(14),
+ user_registration binary(14),
+ user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
+CREATE TABLE /*_*/user_groups (
+ ug_user int unsigned NOT NULL default 0,
+ ug_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_former_groups (
+ ufg_user int unsigned NOT NULL default 0,
+ ufg_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
+CREATE TABLE /*_*/user_newtalk (
+ user_id int NOT NULL default 0,
+ user_ip varbinary(40) NOT NULL default '',
+ user_last_timestamp varbinary(14) NULL default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+ up_user int NOT NULL,
+ up_property varbinary(255) NOT NULL,
+ up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+ page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ page_namespace int NOT NULL,
+ page_title varchar(255) binary NOT NULL,
+ page_restrictions tinyblob NOT NULL,
+ page_counter bigint unsigned NOT NULL default 0,
+ page_is_redirect tinyint unsigned NOT NULL default 0,
+ page_is_new tinyint unsigned NOT NULL default 0,
+ page_random real unsigned NOT NULL,
+ page_touched binary(14) NOT NULL default '',
+ page_latest int unsigned NOT NULL,
+ page_len int unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE TABLE /*_*/revision (
+ rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rev_page int unsigned NOT NULL,
+ rev_text_id int unsigned NOT NULL,
+ rev_comment tinyblob NOT NULL,
+ rev_user int unsigned NOT NULL default 0,
+ rev_user_text varchar(255) binary NOT NULL default '',
+ rev_timestamp binary(14) NOT NULL default '',
+ rev_minor_edit tinyint unsigned NOT NULL default 0,
+ rev_deleted tinyint unsigned NOT NULL default 0,
+ rev_len int unsigned,
+ rev_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE TABLE /*_*/text (
+ old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ old_text mediumblob NOT NULL,
+ old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_text mediumblob NOT NULL,
+ ar_comment tinyblob NOT NULL,
+ ar_user int unsigned NOT NULL default 0,
+ ar_user_text varchar(255) binary NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_flags tinyblob NOT NULL,
+ ar_rev_id int unsigned,
+ ar_text_id int unsigned,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE TABLE /*_*/pagelinks (
+ pl_from int unsigned NOT NULL default 0,
+ pl_namespace int NOT NULL default 0,
+ pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+ tl_from int unsigned NOT NULL default 0,
+ tl_namespace int NOT NULL default 0,
+ tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+ il_from int unsigned NOT NULL default 0,
+ il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+ cl_from int unsigned NOT NULL default 0,
+ cl_to varchar(255) binary NOT NULL default '',
+ cl_sortkey varbinary(230) NOT NULL default '',
+ cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+ cl_timestamp timestamp NOT NULL,
+ cl_collation varbinary(32) NOT NULL default '',
+ cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+CREATE TABLE /*_*/category (
+ cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ cat_title varchar(255) binary NOT NULL,
+ cat_pages int signed NOT NULL default 0,
+ cat_subcats int signed NOT NULL default 0,
+ cat_files int signed NOT NULL default 0,
+ cat_hidden tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+ el_from int unsigned NOT NULL default 0,
+ el_to blob NOT NULL,
+ el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40));
+CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
+CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
+CREATE TABLE /*_*/external_user (
+ eu_local_id int unsigned NOT NULL PRIMARY KEY,
+ eu_external_id varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id);
+CREATE TABLE /*_*/langlinks (
+ ll_from int unsigned NOT NULL default 0,
+ ll_lang varbinary(20) NOT NULL default '',
+ ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/iwlinks (
+ iwl_from int unsigned NOT NULL default 0,
+ iwl_prefix varbinary(20) NOT NULL default '',
+ iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+CREATE TABLE /*_*/site_stats (
+ ss_row_id int unsigned NOT NULL,
+ ss_total_views bigint unsigned default 0,
+ ss_total_edits bigint unsigned default 0,
+ ss_good_articles bigint unsigned default 0,
+ ss_total_pages bigint default '-1',
+ ss_users bigint default '-1',
+ ss_active_users bigint default '-1',
+ ss_admins int default '-1',
+ ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+ hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+ ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ipb_address tinyblob NOT NULL,
+ ipb_user int unsigned NOT NULL default 0,
+ ipb_by int unsigned NOT NULL default 0,
+ ipb_by_text varchar(255) binary NOT NULL default '',
+ ipb_reason tinyblob NOT NULL,
+ ipb_timestamp binary(14) NOT NULL default '',
+ ipb_auto bool NOT NULL default 0,
+ ipb_anon_only bool NOT NULL default 0,
+ ipb_create_account bool NOT NULL default 1,
+ ipb_enable_autoblock bool NOT NULL default '1',
+ ipb_expiry varbinary(14) NOT NULL default '',
+ ipb_range_start tinyblob NOT NULL,
+ ipb_range_end tinyblob NOT NULL,
+ ipb_deleted bool NOT NULL default 0,
+ ipb_block_email bool NOT NULL default 0,
+ ipb_allow_usertalk bool NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE TABLE /*_*/image (
+ img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+ img_size int unsigned NOT NULL default 0,
+ img_width int NOT NULL default 0,
+ img_height int NOT NULL default 0,
+ img_metadata mediumblob NOT NULL,
+ img_bits int NOT NULL default 0,
+ img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ img_minor_mime varbinary(100) NOT NULL default "unknown",
+ img_description tinyblob NOT NULL,
+ img_user int unsigned NOT NULL default 0,
+ img_user_text varchar(255) binary NOT NULL,
+ img_timestamp varbinary(14) NOT NULL default '',
+ img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
+CREATE TABLE /*_*/oldimage (
+ oi_name varchar(255) binary NOT NULL default '',
+ oi_archive_name varchar(255) binary NOT NULL default '',
+ oi_size int unsigned NOT NULL default 0,
+ oi_width int NOT NULL default 0,
+ oi_height int NOT NULL default 0,
+ oi_bits int NOT NULL default 0,
+ oi_description tinyblob NOT NULL,
+ oi_user int unsigned NOT NULL default 0,
+ oi_user_text varchar(255) binary NOT NULL,
+ oi_timestamp binary(14) NOT NULL default '',
+ oi_metadata mediumblob NOT NULL,
+ oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+ oi_minor_mime varbinary(100) NOT NULL default "unknown",
+ oi_deleted tinyint unsigned NOT NULL default 0,
+ oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
+CREATE TABLE /*_*/filearchive (
+ fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ fa_name varchar(255) binary NOT NULL default '',
+ fa_archive_name varchar(255) binary default '',
+ fa_storage_group varbinary(16),
+ fa_storage_key varbinary(64) default '',
+ fa_deleted_user int,
+ fa_deleted_timestamp binary(14) default '',
+ fa_deleted_reason text,
+ fa_size int unsigned default 0,
+ fa_width int default 0,
+ fa_height int default 0,
+ fa_metadata mediumblob,
+ fa_bits int default 0,
+ fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+ fa_minor_mime varbinary(100) default "unknown",
+ fa_description tinyblob,
+ fa_user int unsigned default 0,
+ fa_user_text varchar(255) binary,
+ fa_timestamp binary(14) default '',
+ fa_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE TABLE /*_*/uploadstash (
+ us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
+ us_user int unsigned NOT NULL,
+ us_key varchar(255) NOT NULL,
+ us_orig_path varchar(255) NOT NULL,
+ us_path varchar(255) NOT NULL,
+ us_source_type varchar(50),
+ us_timestamp varbinary(14) not null,
+ us_status varchar(50) not null,
+ us_size int unsigned NOT NULL,
+ us_sha1 varchar(31) NOT NULL,
+ us_mime varchar(255),
+ us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ us_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
+CREATE TABLE /*_*/recentchanges (
+ rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rc_timestamp varbinary(14) NOT NULL default '',
+ rc_cur_time varbinary(14) NOT NULL default '',
+ rc_user int unsigned NOT NULL default 0,
+ rc_user_text varchar(255) binary NOT NULL,
+ rc_namespace int NOT NULL default 0,
+ rc_title varchar(255) binary NOT NULL default '',
+ rc_comment varchar(255) binary NOT NULL default '',
+ rc_minor tinyint unsigned NOT NULL default 0,
+ rc_bot tinyint unsigned NOT NULL default 0,
+ rc_new tinyint unsigned NOT NULL default 0,
+ rc_cur_id int unsigned NOT NULL default 0,
+ rc_this_oldid int unsigned NOT NULL default 0,
+ rc_last_oldid int unsigned NOT NULL default 0,
+ rc_type tinyint unsigned NOT NULL default 0,
+ rc_moved_to_ns tinyint unsigned NOT NULL default 0,
+ rc_moved_to_title varchar(255) binary NOT NULL default '',
+ rc_patrolled tinyint unsigned NOT NULL default 0,
+ rc_ip varbinary(40) NOT NULL default '',
+ rc_old_len int,
+ rc_new_len int,
+ rc_deleted tinyint unsigned NOT NULL default 0,
+ rc_logid int unsigned NOT NULL default 0,
+ rc_log_type varbinary(255) NULL default NULL,
+ rc_log_action varbinary(255) NULL default NULL,
+ rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+ wl_user int unsigned NOT NULL,
+ wl_namespace int NOT NULL default 0,
+ wl_title varchar(255) binary NOT NULL default '',
+ wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/searchindex (
+ si_page int unsigned NOT NULL,
+ si_title varchar(255) NOT NULL default '',
+ si_text mediumtext NOT NULL
+) ENGINE=MyISAM;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+ iw_prefix varchar(32) NOT NULL,
+ iw_url blob NOT NULL,
+ iw_api blob NOT NULL,
+ iw_wikiid varchar(64) NOT NULL,
+ iw_local bool NOT NULL,
+ iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+ qc_type varbinary(32) NOT NULL,
+ qc_value int unsigned NOT NULL default 0,
+ qc_namespace int NOT NULL default 0,
+ qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+ keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+ value mediumblob,
+ exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+ tc_url varbinary(255) NOT NULL,
+ tc_contents text,
+ tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+ log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ log_type varbinary(32) NOT NULL default '',
+ log_action varbinary(32) NOT NULL default '',
+ log_timestamp binary(14) NOT NULL default '19700101000000',
+ log_user int unsigned NOT NULL default 0,
+ log_user_text varchar(255) binary NOT NULL default '',
+ log_namespace int NOT NULL default 0,
+ log_title varchar(255) binary NOT NULL default '',
+ log_page int unsigned NULL,
+ log_comment varchar(255) NOT NULL default '',
+ log_params blob NOT NULL,
+ log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE TABLE /*_*/log_search (
+ ls_field varbinary(32) NOT NULL,
+ ls_value varchar(255) NOT NULL,
+ ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/trackbacks (
+ tb_id int PRIMARY KEY AUTO_INCREMENT,
+ tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
+ tb_title varchar(255) NOT NULL,
+ tb_url blob NOT NULL,
+ tb_ex text,
+ tb_name varchar(255)
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page);
+CREATE TABLE /*_*/job (
+ job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ job_cmd varbinary(60) NOT NULL default '',
+ job_namespace int NOT NULL,
+ job_title varchar(255) binary NOT NULL,
+ job_params blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE TABLE /*_*/querycache_info (
+ qci_type varbinary(32) NOT NULL default '',
+ qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+ rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+ rd_namespace int NOT NULL default 0,
+ rd_title varchar(255) binary NOT NULL default '',
+ rd_interwiki varchar(32) default NULL,
+ rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+ qcc_type varbinary(32) NOT NULL,
+ qcc_value int unsigned NOT NULL default 0,
+ qcc_namespace int NOT NULL default 0,
+ qcc_title varchar(255) binary NOT NULL default '',
+ qcc_namespacetwo int NOT NULL default 0,
+ qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+ pr_page int NOT NULL,
+ pr_type varbinary(60) NOT NULL,
+ pr_level varbinary(60) NOT NULL,
+ pr_cascade tinyint NOT NULL,
+ pr_user int NULL,
+ pr_expiry varbinary(14) NULL,
+ pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+ pt_namespace int NOT NULL,
+ pt_title varchar(255) binary NOT NULL,
+ pt_user int unsigned NOT NULL,
+ pt_reason tinyblob,
+ pt_timestamp binary(14) NOT NULL,
+ pt_expiry varbinary(14) NOT NULL default '',
+ pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+ pp_page int NOT NULL,
+ pp_propname varbinary(60) NOT NULL,
+ pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE TABLE /*_*/updatelog (
+ ul_key varchar(255) NOT NULL PRIMARY KEY,
+ ul_value blob
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+ ct_rc_id int NULL,
+ ct_log_id int NULL,
+ ct_rev_id int NULL,
+ ct_tag varchar(255) NOT NULL,
+ ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+ ts_rc_id int NULL,
+ ts_log_id int NULL,
+ ts_rev_id int NULL,
+ ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+ vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+ lc_lang varbinary(32) NOT NULL,
+ lc_key varchar(255) NOT NULL,
+ lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+CREATE TABLE /*_*/msg_resource (
+ mr_resource varbinary(255) NOT NULL,
+ mr_lang varbinary(32) NOT NULL,
+ mr_blob mediumblob NOT NULL,
+ mr_timestamp binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
+CREATE TABLE /*_*/msg_resource_links (
+ mrl_resource varbinary(255) NOT NULL,
+ mrl_message varbinary(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
+CREATE TABLE /*_*/module_deps (
+ md_module varbinary(255) NOT NULL,
+ md_skin varbinary(32) NOT NULL,
+ md_deps mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
+
diff --git a/tests/phpunit/data/media/80x60-2layers.xcf b/tests/phpunit/data/media/80x60-2layers.xcf
new file mode 100644
index 00000000..c51e980c
--- /dev/null
+++ b/tests/phpunit/data/media/80x60-2layers.xcf
Binary files differ
diff --git a/tests/phpunit/data/media/80x60-Greyscale.xcf b/tests/phpunit/data/media/80x60-Greyscale.xcf
new file mode 100644
index 00000000..84bf3e67
--- /dev/null
+++ b/tests/phpunit/data/media/80x60-Greyscale.xcf
Binary files differ
diff --git a/tests/phpunit/data/media/80x60-RGB.xcf b/tests/phpunit/data/media/80x60-RGB.xcf
new file mode 100644
index 00000000..1d58f16d
--- /dev/null
+++ b/tests/phpunit/data/media/80x60-RGB.xcf
Binary files differ
diff --git a/tests/phpunit/data/media/Toll_Texas_1.svg b/tests/phpunit/data/media/Toll_Texas_1.svg
new file mode 100644
index 00000000..73004e3e
--- /dev/null
+++ b/tests/phpunit/data/media/Toll_Texas_1.svg
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">
+ <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="385" height="385.0004883"
+ viewBox="0 0 385 385.0004883" overflow="visible" enable-background="new 0 0 385 385.0004883" xml:space="preserve">
+<g>
+ <g>
+ <g>
+ <path fill="#FFFFFF" d="M0.5,24.5c0-13.2548828,10.7451172-24,24-24h336c13.2548828,0,24,10.7451172,24,24v336.0004883
+ c0,13.2548828-10.7451172,24-24,24h-336c-13.2548828,0-24-10.7451172-24-24V24.5L0.5,24.5z"/>
+ <path fill="#FFFFFF" d="M192.5,192.5004883"/>
+ </g>
+ <g>
+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" d="M0.5,24.5
+ c0-13.2548828,10.7451172-24,24-24h336c13.2548828,0,24,10.7451172,24,24v336.0004883c0,13.2548828-10.7451172,24-24,24h-336
+ c-13.2548828,0-24-10.7451172-24-24V24.5L0.5,24.5z"/>
+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" d="
+ M192.5,192.5004883"/>
+ </g>
+ </g>
+ <g>
+ <path fill="#003882" d="M24.5,0.5h336c13.2548828,0,24,10.7451172,24,24v232.0004883H0.5V24.5
+ C0.5,11.2451172,11.2451172,0.5,24.5,0.5z"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M10.5,24.5c0-7.7319336,6.2680664-14,14-14h336c7.7324219,0,14,6.2680664,14,14v222.0004883h-364V24.5z"/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="93.809082,348.2397461 91.6787109,347.8666992
+ 89.5478516,347.7368164 85.2929688,347.7368164 83.1640625,347.8666992 78.9042969,348.6157227 76.7763672,349.1166992
+ 72.7666016,350.3706055 70.7631836,351.246582 68.8837891,352.121582 67.0053711,353.1254883 65.1254883,354.253418
+ 63.3740234,355.5063477 60.1210938,358.2602539 58.4926758,359.762207 57.1132813,361.2641602 55.7338867,362.8959961
+ 54.3603516,364.6459961 21.2949219,301.3999023 21.168457,301.3999023 22.5478516,299.6469727 23.9248047,298.0200195
+ 25.3022461,296.5180664 26.9296875,295.0141602 30.1875,292.2592773 31.9404297,291.0073242 33.8188477,289.8793945
+ 35.6972656,288.8774414 37.5761719,288.0004883 39.5791016,287.1245117 43.5878906,285.8706055 45.7148438,285.3706055
+ 49.9755859,284.6176758 52.1020508,284.4946289 56.3632813,284.4946289 58.4926758,284.6176758 60.6201172,284.9946289
+ 60.6201172,284.8696289 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="32.8154297,319.559082 45.0898438,312.4233398
+ 42.2080078,298.5209961 52.7299805,308.0375977 65.1254883,300.9008789 59.2421875,313.9243164 69.8867188,323.4428711
+ 55.7338867,321.9389648 49.8476563,334.965332 46.9677734,321.0629883 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#B01C2E" points="132.0053711,306.6606445 148.5385742,338.2211914
+ 147.4101563,339.7241211 146.1577148,341.2270508 144.9052734,342.6020508 143.5302734,343.9848633 142.1494141,345.2358398
+ 140.6484375,346.4868164 139.2705078,347.6157227 137.765625,348.6157227 136.1381836,349.6176758 134.6357422,350.621582
+ 133.0083008,351.3696289 131.3798828,352.246582 129.625,352.8745117 128,353.5024414 126.2441406,354.0004883
+ 124.4921875,354.5043945 122.737793,354.8774414 120.9853516,355.1293945 117.4785156,355.3793945 113.9707031,355.3793945
+ 112.09375,355.2543945 110.3408203,355.003418 108.5854492,354.6274414 106.9589844,354.253418 103.4501953,353.2485352
+ 101.8232422,352.6254883 100.0688477,351.871582 98.4428711,351.1235352 96.9389648,350.1176758 95.3095703,349.2426758
+ 93.809082,348.2397461 93.809082,348.1147461 93.809082,348.2397461 77.1523438,316.4282227 77.2753906,316.4282227
+ 77.2753906,316.5551758 78.78125,317.5581055 80.4057617,318.4350586 81.9116211,319.4360352 83.5395508,320.1860352
+ 85.2929688,320.9399414 86.9208984,321.5649414 90.4257813,322.5668945 92.0551758,322.9418945 93.809082,323.3188477
+ 95.5620117,323.5688477 97.4423828,323.6948242 100.9462891,323.6948242 102.6992188,323.5688477 104.4521484,323.4428711
+ 106.2060547,323.1928711 107.9609375,322.8168945 111.4682617,321.8168945 113.0947266,321.1889648 114.8481445,320.5639648
+ 116.4765625,319.6879883 118.1035156,318.9350586 119.6083984,317.934082 121.2363281,316.9301758 122.737793,315.9282227
+ 124.1152344,314.8012695 125.6196289,313.5483398 126.9960938,312.2983398 128.3759766,310.9194336 129.625,309.5424805
+ 130.8789063,308.0375977 132.0053711,306.5366211 132.1328125,306.5366211 "/>
+ </g>
+ <g>
+
+ <polyline fill="none" stroke="#003882" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" points="
+ 60.7441406,285.1196289 63,286.4956055 65.2529297,287.7485352 67.5068359,288.8774414 69.8867188,289.8793945
+ 72.3916016,290.6313477 74.8955078,291.3813477 77.4008789,291.7583008 80.0302734,292.1333008 82.5366211,292.2592773
+ 85.1655273,292.2592773 87.6708984,292.0083008 90.3022461,291.6313477 92.8066406,291.1313477 95.3095703,290.3793945
+ 97.6904297,289.5024414 100.0688477,288.5004883 102.3256836,287.2504883 104.578125,285.8706055 106.7070313,284.4946289
+ 108.7124023,282.8637695 110.5888672,281.1108398 112.3427734,279.2329102 114.0986328,277.2290039 115.5976563,275.1010742 "/>
+ </g>
+ <g>
+
+ <line fill="none" stroke="#003882" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" x1="115.4746094" y1="275.1010742" x2="131.8793945" y2="306.2836914"/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="215.1650391,349.1166992 213.1601563,348.2397461
+ 211.2832031,347.237793 207.7773438,344.7329102 206.1503906,343.2319336 204.6435547,341.7270508 203.2685547,340.0991211
+ 202.0166016,338.347168 200.8867188,336.5942383 199.8857422,334.590332 199.0097656,332.7114258 198.2578125,330.7075195
+ 197.6328125,328.5776367 197.1289063,326.5756836 196.7548828,324.4458008 196.6318359,322.3168945 196.6318359,320.0629883
+ 196.7548828,317.934082 197.0058594,315.8041992 197.3818359,313.6743164 198.6357422,309.6665039 199.5107422,307.6635742
+ 200.5126953,305.7827148 201.6386719,303.9067383 202.890625,302.152832 204.2685547,300.3989258 205.6464844,298.8969727
+ 207.2744141,297.3920898 208.9023438,296.0161133 210.6572266,294.887207 212.5361328,293.7602539 214.4130859,292.7602539
+ 216.4179688,291.8833008 218.5449219,291.0073242 220.6767578,290.2543945 222.9306641,289.6274414 227.4384766,288.8774414
+ 229.6933594,288.6254883 231.9482422,288.5004883 234.2011719,288.5004883 236.4541016,288.6254883 238.7119141,288.8774414
+ 240.9638672,289.253418 243.21875,289.7543945 245.3476563,290.3793945 247.6025391,291.1313477 249.6054688,292.0083008
+ 251.7363281,293.0083008 251.7363281,292.8852539 253.6132813,294.137207 255.4931641,295.5151367 257.2460938,297.0161133
+ 258.8740234,298.6459961 260.5019531,300.3989258 261.8808594,302.277832 263.1328125,304.1577148 264.2578125,306.2836914
+ 266.0117188,310.543457 266.6386719,312.7983398 267.390625,317.3051758 267.5136719,319.6879883 267.390625,321.9418945
+ 267.265625,324.3188477 266.2626953,328.8286133 265.5117188,330.9575195 264.6347656,333.2114258 263.6318359,335.2163086
+ 262.5058594,337.2192383 261.1298828,339.0981445 259.625,340.9770508 258.1240234,342.6020508 256.3701172,344.1079102
+ 254.4902344,345.6098633 252.6142578,346.8618164 250.609375,347.9926758 248.4785156,348.9926758 246.3496094,349.8696289
+ 244.2216797,350.621582 242.0927734,351.246582 239.8359375,351.7485352 237.5830078,352.121582 235.3291016,352.3754883
+ 232.9501953,352.4985352 230.6933594,352.4985352 228.4414063,352.3754883 226.1865234,352.121582 223.9296875,351.7485352
+ 221.6777344,351.246582 219.421875,350.746582 217.2949219,349.9946289 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="219.9238281,303.6547852 221.9287109,301.5258789
+ 224.4335938,299.8999023 227.1904297,298.8969727 230.1943359,298.2700195 233.0742188,298.3959961 235.9550781,299.0219727
+ 238.7119141,300.2739258 241.0898438,301.9018555 243.0957031,304.1577148 244.5957031,306.5366211 245.9746094,308.9145508
+ 246.9765625,311.4204102 247.8535156,314.0512695 248.4785156,316.8051758 248.8535156,319.559082 248.9804688,322.3168945
+ 248.9804688,325.0727539 248.6035156,327.8256836 248.1035156,330.5805664 247.3496094,333.2114258 246.3496094,335.7172852
+ 244.9726563,337.8442383 243.34375,339.6000977 241.3408203,341.1020508 239.2109375,342.355957 236.8330078,343.2319336
+ 234.4511719,343.6049805 231.9482422,343.7319336 229.4414063,343.3579102 227.1904297,342.6020508 224.9326172,341.4770508
+ 222.9306641,340.0991211 221.1757813,338.347168 219.6748047,336.3422852 218.5449219,334.2114258 217.7949219,331.8334961
+ 217.0449219,329.7036133 216.0419922,325.4477539 215.7910156,323.1928711 215.6660156,320.9399414 215.6660156,318.684082
+ 215.9169922,316.4282227 216.1669922,314.300293 216.6679688,312.0454102 217.2949219,309.918457 218.0458984,307.7895508
+ 218.9208984,305.7827148 219.9238281,303.7817383 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="150.4169922,290.0063477 196.3789063,289.7543945
+ 192.7470703,304.4067383 192.6220703,304.5307617 192.1210938,303.7817383 191.4960938,303.1567383 190.8681641,302.5288086
+ 190.1162109,302.027832 189.2421875,301.652832 188.4887695,301.2768555 187.6113281,301.0258789 186.7353516,300.7758789
+ 179.9741211,300.7758789 179.8481445,345.1098633 179.8481445,345.2358398 180.0966797,346.1118164 180.3486328,347.1157227
+ 180.7246094,347.9926758 181.1005859,348.7407227 181.6015625,349.6176758 182.8549805,351.1235352 183.6054688,351.7485352
+ 158.5556641,351.7485352 158.5556641,351.871582 159.3095703,351.246582 160.0595703,350.4956055 160.6845703,349.7426758
+ 161.1875,348.9926758 161.6879883,347.9926758 161.9384766,347.1157227 162.1894531,346.1118164 162.3144531,345.1098633
+ 162.4375,300.9008789 156.5527344,300.9008789 155.1767578,301.0258789 153.9248047,301.2768555 152.671875,301.5258789
+ 151.4204102,301.9018555 150.1660156,302.4008789 148.9135742,303.0297852 146.6601563,304.2797852 146.6601563,304.4067383 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="275.7822266,344.3598633 276.03125,298.1450195
+ 275.90625,297.769043 275.90625,297.894043 275.7822266,296.7661133 275.5302734,295.7670898 275.15625,294.6391602
+ 274.7792969,293.637207 272.8984375,291.0073242 272.0234375,290.2543945 272.1484375,290.2543945 297.4492188,290.1313477
+ 296.4453125,290.8803711 295.5683594,291.6313477 294.6904297,292.5083008 294.0673828,293.5102539 293.5644531,294.6391602
+ 293.1904297,295.7670898 293.0644531,297.0161133 293.0644531,298.2700195 293.0644531,298.1450195 293.1904297,298.1450195
+ 293.0644531,340.7260742 292.9394531,340.7260742 294.8183594,341.3540039 296.8222656,341.8540039 298.7011719,342.1040039
+ 300.7050781,342.355957 302.7070313,342.4799805 304.5878906,342.355957 306.5917969,342.2299805 308.46875,341.8540039
+ 311.4746094,340.7260742 312.4765625,340.2231445 313.3535156,339.7241211 314.3535156,339.2241211 316.109375,337.972168
+ 311.8505859,351.621582 271.3984375,351.7485352 272.3994141,351.1235352 273.2753906,350.3706055 274.0292969,349.6176758
+ 275.2802734,347.6157227 275.53125,346.4868164 275.7822266,345.4858398 275.90625,344.2348633 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="327.5058594,344.3598633 327.7539063,298.1450195
+ 327.6308594,297.769043 327.6308594,297.894043 327.5058594,296.7661133 327.2539063,295.7670898 326.8769531,294.6391602
+ 326.5029297,293.637207 324.6259766,291.0073242 323.7480469,290.2543945 323.8740234,290.2543945 349.171875,290.1313477
+ 348.1708984,290.8803711 347.2929688,291.6313477 346.4179688,292.5083008 345.7890625,293.5102539 345.2871094,294.6391602
+ 344.9121094,295.7670898 344.7890625,297.0161133 344.7890625,298.2700195 344.7890625,298.1450195 344.9121094,298.1450195
+ 344.7890625,340.7260742 344.6640625,340.7260742 346.5410156,341.3540039 348.5458984,341.8540039 350.4238281,342.1040039
+ 352.4277344,342.355957 354.4316406,342.4799805 356.3105469,342.355957 358.3144531,342.2299805 360.1933594,341.8540039
+ 361.1933594,341.4770508 363.1992188,340.7260742 364.2011719,340.2231445 365.078125,339.7241211 366.0820313,339.2241211
+ 367.8320313,337.972168 363.5751953,351.621582 323.1220703,351.7485352 324.125,351.1235352 325.0019531,350.3706055
+ 325.7519531,349.6176758 327.0058594,347.6157227 327.2539063,346.4868164 327.5058594,345.4858398 327.6308594,344.2348633 "/>
+ </g>
+</g>
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#003882" d="M188.4228516,211.0395508V89.7011719h-23.2304688V66.4711914
+ c7.4140625-0.3291016,13.8393555-2.3886719,19.2763672-6.1782227c5.4365234-3.7890625,9.3085938-8.7314453,11.6152344-14.8276367
+ h23.7236328v165.5742188H188.4228516z"/>
+</svg>
diff --git a/tests/phpunit/data/media/iptc-invalid-psir.jpg b/tests/phpunit/data/media/iptc-invalid-psir.jpg
new file mode 100644
index 00000000..01b9acf3
--- /dev/null
+++ b/tests/phpunit/data/media/iptc-invalid-psir.jpg
Binary files differ
diff --git a/tests/phpunit/includes/ArticleTablesTest.php b/tests/phpunit/includes/ArticleTablesTest.php
index 01776c95..02571b55 100644
--- a/tests/phpunit/includes/ArticleTablesTest.php
+++ b/tests/phpunit/includes/ArticleTablesTest.php
@@ -6,29 +6,28 @@
class ArticleTablesTest extends MediaWikiLangTestCase {
function testbug14404() {
- global $wgUser, $wgContLang, $wgLanguageCode, $wgLang;
-
- $title = Title::newFromText("Bug 14404");
- $article = new Article( $title );
- $wgUser = new User();
- $wgUser->mRights = array( 'createpage', 'edit', 'purge' );
+ global $wgContLang, $wgLanguageCode, $wgLang;
+
+ $title = Title::newFromText( 'Bug 14404' );
+ $page = WikiPage::factory( $title );
+ $user = new User();
+ $user->mRights = array( 'createpage', 'edit', 'purge' );
$wgLanguageCode = 'es';
$wgContLang = Language::factory( 'es' );
-
+
$wgLang = Language::factory( 'fr' );
- $status = $article->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0 );
- $templates1 = $article->getUsedTemplates();
+ $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0, false, $user );
+ $templates1 = $page->getUsedTemplates();
$wgLang = Language::factory( 'de' );
- $article->mParserOptions = null; // Let it pick the new user language
- $article->mPreparedEdit = false; // In order to force the rerendering of the same wikitext
-
+ $page->mPreparedEdit = false; // In order to force the rerendering of the same wikitext
+
// We need an edit, a purge is not enough to regenerate the tables
- $status = $article->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE );
- $templates2 = $article->getUsedTemplates();
-
+ $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE, false, $user );
+ $templates2 = $page->getUsedTemplates();
+
$this->assertEquals( $templates1, $templates2 );
$this->assertEquals( $templates1[0]->getFullText(), 'Historial' );
}
-
+
}
diff --git a/tests/phpunit/includes/ArticleTest.php b/tests/phpunit/includes/ArticleTest.php
index 285efee9..846d2b86 100644
--- a/tests/phpunit/includes/ArticleTest.php
+++ b/tests/phpunit/includes/ArticleTest.php
@@ -19,25 +19,25 @@ class ArticleTest extends MediaWikiTestCase {
}
- function testImplementsGetMagic() {
- $this->assertEquals( -1, $this->article->mCounter, "Article __get magic" );
+ function testImplementsGetMagic() {
+ $this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
}
/**
* @depends testImplementsGetMagic
*/
function testImplementsSetMagic() {
-
- $this->article->mCounter = 2;
- $this->assertEquals( 2, $this->article->mCounter, "Article __set magic" );
+ $this->article->mLatest = 2;
+ $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
}
/**
* @depends testImplementsSetMagic
*/
function testImplementsCallMagic() {
- $this->article->mCounter = 33;
- $this->assertEquals( 33, $this->article->getCount(), "Article __call magic" );
+ $this->article->mLatest = 33;
+ $this->article->mDataLoaded = true;
+ $this->assertEquals( 33, $this->article->getLatest(), "Article __call magic" );
}
function testGetOrSetOnNewProperty() {
diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php
index 2f224ba8..749f40b4 100644
--- a/tests/phpunit/includes/BlockTest.php
+++ b/tests/phpunit/includes/BlockTest.php
@@ -2,11 +2,10 @@
/**
* @group Database
+ * @group Blocking
*/
class BlockTest extends MediaWikiLangTestCase {
- const REASON = "Some reason";
-
private $block, $madeAt;
/* variable used to save up the blockID we insert in this test suite */
@@ -36,8 +35,8 @@ class BlockTest extends MediaWikiLangTestCase {
$oldBlock->delete();
}
- $this->block = new Block( 'UTBlockee', 1, 0,
- self::REASON
+ $this->block = new Block( 'UTBlockee', $user->getID(), 0,
+ 'Parce que', 0, false, time() + 100500
);
$this->madeAt = wfTimestamp( TS_MW );
@@ -68,7 +67,7 @@ class BlockTest extends MediaWikiLangTestCase {
// $this->dumpBlocks();
$this->assertTrue( $this->block->equals( Block::newFromTarget('UTBlockee') ), "newFromTarget() returns the same block as the one that was made");
-
+
$this->assertTrue( $this->block->equals( Block::newFromID( $this->blockId ) ), "newFromID() returns the same block as the one that was made");
}
@@ -77,8 +76,9 @@ class BlockTest extends MediaWikiLangTestCase {
* per bug 26425
*/
function testBug26425BlockTimestampDefaultsToTime() {
-
- $this->assertEquals( $this->madeAt, $this->block->mTimestamp, "If no timestamp is specified, the block is recorded as time()");
+ // delta to stop one-off errors when things happen to go over a second mark.
+ $delta = abs( $this->madeAt - $this->block->mTimestamp );
+ $this->assertLessThan( 2, $delta, "If no timestamp is specified, the block is recorded as time()");
}
@@ -91,6 +91,8 @@ class BlockTest extends MediaWikiLangTestCase {
* @dataProvider dataBug29116
*/
function testBug29116LoadWithEmptyIp( $vagueTarget ) {
+ $this->hideDeprecated( 'Block::load' );
+
$uid = User::idFromName( 'UTBlockee' );
$this->assertTrue( ($uid > 0), 'Must be able to look up the target user during tests' );
@@ -121,4 +123,3 @@ class BlockTest extends MediaWikiLangTestCase {
);
}
}
-
diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php
new file mode 100644
index 00000000..e98e9707
--- /dev/null
+++ b/tests/phpunit/includes/EditPageTest.php
@@ -0,0 +1,33 @@
+<?php
+
+class EditPageTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider dataExtractSectionTitle
+ */
+ function testExtractSectionTitle( $section, $title ) {
+ $extracted = EditPage::extractSectionTitle( $section );
+ $this->assertEquals( $title, $extracted );
+ }
+
+ function dataExtractSectionTitle() {
+ return array(
+ array(
+ "== Test ==\n\nJust a test section.",
+ "Test"
+ ),
+ array(
+ "An initial section, no header.",
+ false
+ ),
+ array(
+ "An initial section with a fake heder (bug 32617)\n\n== Test == ??\nwtf",
+ false
+ ),
+ array(
+ "== Section ==\nfollowed by a fake == Non-section == ??\nnoooo",
+ "Section"
+ )
+ );
+ }
+}
diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php
index 5b0aa98b..a9088cb2 100644
--- a/tests/phpunit/includes/ExtraParserTest.php
+++ b/tests/phpunit/includes/ExtraParserTest.php
@@ -10,11 +10,13 @@ class ExtraParserTest extends MediaWikiTestCase {
global $wgContLang;
global $wgShowDBErrorBacktrace;
global $wgLanguageCode;
+ global $wgAlwaysUseTidy;
$wgShowDBErrorBacktrace = true;
$wgLanguageCode = 'en';
$wgContLang = new Language( 'en' );
$wgMemc = new EmptyBagOStuff;
+ $wgAlwaysUseTidy = false;
$this->options = new ParserOptions;
$this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) );
@@ -61,20 +63,48 @@ class ExtraParserTest extends MediaWikiTestCase {
* cleanSig() makes all templates substs and removes tildes
*/
function testCleanSig() {
+ global $wgCleanSignatures;
+ $oldCleanSignature = $wgCleanSignatures;
+ $wgCleanSignatures = true;
+
$title = Title::newFromText( __FUNCTION__ );
$outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
+
+ $wgCleanSignatures = $oldCleanSignature;
$this->assertEquals( "{{SUBST:Foo}} ", $outputText );
}
-
+
/**
- * cleanSigInSig() just removes tildes
+ * cleanSig() should do nothing if disabled
*/
- function testCleanSigInSig() {
+ function testCleanSigDisabled() {
+ global $wgCleanSignatures;
+ $oldCleanSignature = $wgCleanSignatures;
+ $wgCleanSignatures = false;
+
$title = Title::newFromText( __FUNCTION__ );
- $outputText = $this->parser->cleanSigInSig( "{{Foo}} ~~~~" );
+ $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
+
+ $wgCleanSignatures = $oldCleanSignature;
- $this->assertEquals( "{{Foo}} ", $outputText );
+ $this->assertEquals( "{{Foo}} ~~~~", $outputText );
+ }
+
+ /**
+ * cleanSigInSig() just removes tildes
+ * @dataProvider provideStringsForCleanSigInSig
+ */
+ function testCleanSigInSig( $in, $out ) {
+ $this->assertEquals( Parser::cleanSigInSig( $in), $out );
+ }
+
+ function provideStringsForCleanSigInSig() {
+ return array(
+ array( "{{Foo}} ~~~~", "{{Foo}} " ),
+ array( "~~~", "" ),
+ array( "~~~~~", "" ),
+ );
}
function testGetSection() {
@@ -110,4 +140,28 @@ class ExtraParserTest extends MediaWikiTestCase {
'finalTitle' => $title,
'deps' => $deps );
}
+
+ /**
+ * @group Database
+ */
+ function testTrackingCategory() {
+ $title = Title::newFromText( __FUNCTION__ );
+ $catName = wfMsgForContent( 'broken-file-category' );
+ $cat = Title::makeTitleSafe( NS_CATEGORY, $catName );
+ $expected = array( $cat->getDBkey() );
+ $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options );
+ $result = $parserOutput->getCategoryLinks();
+ $this->assertEquals( $expected, $result );
+ }
+
+ /**
+ * @group Database
+ */
+ function testTrackingCategorySpecial() {
+ // Special pages shouldn't have tracking cats.
+ $title = SpecialPage::getTitleFor( 'Contributions' );
+ $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options );
+ $result = $parserOutput->getCategoryLinks();
+ $this->assertEmpty( $result );
+ }
}
diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php
index 85d76271..d86c95d7 100644
--- a/tests/phpunit/includes/FormOptionsInitializationTest.php
+++ b/tests/phpunit/includes/FormOptionsInitializationTest.php
@@ -25,9 +25,9 @@ class FormOptionsExposed extends FormOptions {
*
* Generated by PHPUnit on 2011-02-28 at 20:46:27.
*
- * Copyright © 2011, Ashar Voultoiz
+ * Copyright © 2011, Antoine Musso
*
- * @author Ashar Voultoiz
+ * @author Antoine Musso
*/
class FormOptionsInitializationTest extends MediaWikiTestCase {
/**
diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php
index 86618d93..749343ec 100644
--- a/tests/phpunit/includes/FormOptionsTest.php
+++ b/tests/phpunit/includes/FormOptionsTest.php
@@ -12,9 +12,9 @@
* Test class for FormOptions methods.
* Generated by PHPUnit on 2011-02-28 at 20:46:27.
*
- * Copyright © 2011, Ashar Voultoiz
+ * Copyright © 2011, Antoine Musso
*
- * @author Ashar Voultoiz
+ * @author Antoine Musso
*/
class FormOptionsTest extends MediaWikiTestCase {
/**
diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
index 3d157d0a..3cb42f12 100644
--- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
+++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
@@ -94,24 +94,80 @@ class GlobalTest extends MediaWikiTestCase {
$this->assertTrue( $end > $start, "Time is running backwards!" );
}
- function testArrayToCGI() {
+ function dataArrayToCGI() {
+ return array(
+ array( array(), '' ), // empty
+ array( array( 'foo' => 'bar' ), 'foo=bar' ), // string test
+ array( array( 'foo' => '' ), 'foo=' ), // empty string test
+ array( array( 'foo' => 1 ), 'foo=1' ), // number test
+ array( array( 'foo' => true ), 'foo=1' ), // true test
+ array( array( 'foo' => false ), '' ), // false test
+ array( array( 'foo' => null ), '' ), // null test
+ array( array( 'foo' => 'A&B=5+6@!"\'' ), 'foo=A%26B%3D5%2B6%40%21%22%27' ), // urlencoding test
+ array( array( 'foo' => 'bar', 'baz' => 'is', 'asdf' => 'qwerty' ), 'foo=bar&baz=is&asdf=qwerty' ), // multi-item test
+ array( array( 'foo' => array( 'bar' => 'baz' ) ), 'foo%5Bbar%5D=baz' ),
+ array( array( 'foo' => array( 'bar' => 'baz', 'qwerty' => 'asdf' ) ), 'foo%5Bbar%5D=baz&foo%5Bqwerty%5D=asdf' ),
+ array( array( 'foo' => array( 'bar', 'baz' ) ), 'foo%5B0%5D=bar&foo%5B1%5D=baz' ),
+ array( array( 'foo' => array( 'bar' => array( 'bar' => 'baz' ) ) ), 'foo%5Bbar%5D%5Bbar%5D=baz' ),
+ );
+ }
+
+ /**
+ * @dataProvider dataArrayToCGI
+ */
+ function testArrayToCGI( $array, $result ) {
+ $this->assertEquals( $result, wfArrayToCGI( $array ) );
+ }
+
+
+ function testArrayToCGI2() {
$this->assertEquals(
- "baz=AT%26T&foo=bar",
+ "baz=bar&foo=bar",
wfArrayToCGI(
- array( 'baz' => 'AT&T', 'ignore' => '' ),
+ array( 'baz' => 'bar' ),
array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) );
- $this->assertEquals(
- "path%5B0%5D=wiki&path%5B1%5D=test&cfg%5Bservers%5D%5Bhttp%5D=localhost",
- wfArrayToCGI( array(
- 'path' => array( 'wiki', 'test' ),
- 'cfg' => array( 'servers' => array( 'http' => 'localhost' ) ) ) ) );
}
- function testCgiToArray() {
- $this->assertEquals(
- array( 'path' => array( 'wiki', 'test' ),
- 'cfg' => array( 'servers' => array( 'http' => 'localhost' ) ) ),
- wfCgiToArray( 'path%5B0%5D=wiki&path%5B1%5D=test&cfg%5Bservers%5D%5Bhttp%5D=localhost' ) );
+ function dataCgiToArray() {
+ return array(
+ array( '', array() ), // empty
+ array( 'foo=bar', array( 'foo' => 'bar' ) ), // string
+ array( 'foo=', array( 'foo' => '' ) ), // empty string
+ array( 'foo', array( 'foo' => '' ) ), // missing =
+ array( 'foo=bar&qwerty=asdf', array( 'foo' => 'bar', 'qwerty' => 'asdf' ) ), // multiple value
+ array( 'foo=A%26B%3D5%2B6%40%21%22%27', array( 'foo' => 'A&B=5+6@!"\'' ) ), // urldecoding test
+ array( 'foo%5Bbar%5D=baz', array( 'foo' => array( 'bar' => 'baz' ) ) ),
+ array( 'foo%5Bbar%5D=baz&foo%5Bqwerty%5D=asdf', array( 'foo' => array( 'bar' => 'baz', 'qwerty' => 'asdf' ) ) ),
+ array( 'foo%5B0%5D=bar&foo%5B1%5D=baz', array( 'foo' => array( 0 => 'bar', 1 => 'baz' ) ) ),
+ array( 'foo%5Bbar%5D%5Bbar%5D=baz', array( 'foo' => array( 'bar' => array( 'bar' => 'baz' ) ) ) ),
+ );
+ }
+
+ /**
+ * @dataProvider dataCgiToArray
+ */
+ function testCgiToArray( $cgi, $result ) {
+ $this->assertEquals( $result, wfCgiToArray( $cgi ) );
+ }
+
+ function dataCgiRoundTrip() {
+ return array(
+ array( '' ),
+ array( 'foo=bar' ),
+ array( 'foo=' ),
+ array( 'foo=bar&baz=biz' ),
+ array( 'foo=A%26B%3D5%2B6%40%21%22%27' ),
+ array( 'foo%5Bbar%5D=baz' ),
+ array( 'foo%5B0%5D=bar&foo%5B1%5D=baz' ),
+ array( 'foo%5Bbar%5D%5Bbar%5D=baz' ),
+ );
+ }
+
+ /**
+ * @dataProvider dataCgiRoundTrip
+ */
+ function testCgiRoundTrip( $cgi ) {
+ $this->assertEquals( $cgi, wfArrayToCGI( wfCgiToArray( $cgi ) ) );
}
function testMimeTypeMatch() {
@@ -176,263 +232,6 @@ class GlobalTest extends MediaWikiTestCase {
array( 'text/*' => 1.0 ),
array( 'application/xhtml+xml' => 1.0 ) ) );
}
-
- function testTimestamp() {
- $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, $t ),
- 'TS_UNIX to TS_MW' );
- $this->assertEquals(
- '19690115123456',
- wfTimestamp( TS_MW, -30281104 ),
- 'Negative TS_UNIX to TS_MW' );
- $this->assertEquals(
- 979562096,
- wfTimestamp( TS_UNIX, $t ),
- 'TS_UNIX to TS_UNIX' );
- $this->assertEquals(
- '2001-01-15 12:34:56',
- wfTimestamp( TS_DB, $t ),
- 'TS_UNIX to TS_DB' );
- $this->assertEquals(
- '20010115T123456Z',
- wfTimestamp( TS_ISO_8601_BASIC, $t ),
- 'TS_ISO_8601_BASIC to TS_DB' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, '20010115123456' ),
- 'TS_MW to TS_MW' );
- $this->assertEquals(
- 979562096,
- wfTimestamp( TS_UNIX, '20010115123456' ),
- 'TS_MW to TS_UNIX' );
- $this->assertEquals(
- '2001-01-15 12:34:56',
- wfTimestamp( TS_DB, '20010115123456' ),
- 'TS_MW to TS_DB' );
- $this->assertEquals(
- '20010115T123456Z',
- wfTimestamp( TS_ISO_8601_BASIC, '20010115123456' ),
- 'TS_MW to TS_ISO_8601_BASIC' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, '2001-01-15 12:34:56' ),
- 'TS_DB to TS_MW' );
- $this->assertEquals(
- 979562096,
- wfTimestamp( TS_UNIX, '2001-01-15 12:34:56' ),
- 'TS_DB to TS_UNIX' );
- $this->assertEquals(
- '2001-01-15 12:34:56',
- wfTimestamp( TS_DB, '2001-01-15 12:34:56' ),
- 'TS_DB to TS_DB' );
- $this->assertEquals(
- '20010115T123456Z',
- wfTimestamp( TS_ISO_8601_BASIC, '2001-01-15 12:34:56' ),
- 'TS_DB to TS_ISO_8601_BASIC' );
-
- # rfc2822 section 3.3
-
- $this->assertEquals(
- 'Mon, 15 Jan 2001 12:34:56 GMT',
- wfTimestamp( TS_RFC2822, '20010115123456' ),
- 'TS_MW to TS_RFC2822' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, 'Mon, 15 Jan 2001 12:34:56 GMT' ),
- 'TS_RFC2822 to TS_MW' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, ' Mon, 15 Jan 2001 12:34:56 GMT' ),
- 'TS_RFC2822 with leading space to TS_MW' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, '15 Jan 2001 12:34:56 GMT' ),
- 'TS_RFC2822 without optional day-of-week to TS_MW' );
-
- # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
- # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, 'Mon, 15 Jan 2001 12:34:56 GMT' ),
- 'TS_RFC2822 to TS_MW' );
-
- # WSP = SP / HTAB ; rfc2234
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, "Mon, 15 Jan\x092001 12:34:56 GMT" ),
- 'TS_RFC2822 with HTAB to TS_MW' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, "Mon, 15 Jan\x09 \x09 2001 12:34:56 GMT" ),
- 'TS_RFC2822 with HTAB and SP to TS_MW' );
-
- $this->assertEquals(
- '19941106084937',
- wfTimestamp( TS_MW, "Sun, 6 Nov 94 08:49:37 GMT" ),
- 'TS_RFC2822 with obsolete year to TS_MW' );
- }
-
- /**
- * This test checks wfTimestamp() with values outside.
- * It needs PHP 64 bits or PHP > 5.1.
- * See r74778 and bug 25451
- */
- function testOldTimestamps() {
- $this->assertEquals( 'Fri, 13 Dec 1901 20:45:54 GMT',
- wfTimestamp( TS_RFC2822, '19011213204554' ),
- 'Earliest time according to php documentation' );
-
- $this->assertEquals( 'Tue, 19 Jan 2038 03:14:07 GMT',
- wfTimestamp( TS_RFC2822, '20380119031407' ),
- 'Latest 32 bit time' );
-
- $this->assertEquals( '-2147483648',
- wfTimestamp( TS_UNIX, '19011213204552' ),
- 'Earliest 32 bit unix time' );
-
- $this->assertEquals( '2147483647',
- wfTimestamp( TS_UNIX, '20380119031407' ),
- 'Latest 32 bit unix time' );
-
- $this->assertEquals( 'Fri, 13 Dec 1901 20:45:52 GMT',
- wfTimestamp( TS_RFC2822, '19011213204552' ),
- 'Earliest 32 bit time' );
-
- $this->assertEquals( 'Fri, 13 Dec 1901 20:45:51 GMT',
- wfTimestamp( TS_RFC2822, '19011213204551' ),
- 'Earliest 32 bit time - 1' );
-
- $this->assertEquals( 'Tue, 19 Jan 2038 03:14:08 GMT',
- wfTimestamp( TS_RFC2822, '20380119031408' ),
- 'Latest 32 bit time + 1' );
-
- $this->assertEquals( '19011212000000',
- wfTimestamp(TS_MW, '19011212000000'),
- 'Convert to itself r74778#c10645' );
-
- $this->assertEquals( '-2147483649',
- wfTimestamp( TS_UNIX, '19011213204551' ),
- 'Earliest 32 bit unix time - 1' );
-
- $this->assertEquals( '2147483648',
- wfTimestamp( TS_UNIX, '20380119031408' ),
- 'Latest 32 bit unix time + 1' );
-
- $this->assertEquals( '19011213204551',
- wfTimestamp( TS_MW, '-2147483649' ),
- '1901 negative unix time to MediaWiki' );
-
- $this->assertEquals( '18010115123456',
- wfTimestamp( TS_MW, '-5331871504' ),
- '1801 negative unix time to MediaWiki' );
-
- $this->assertEquals( 'Tue, 09 Aug 0117 12:34:56 GMT',
- wfTimestamp( TS_RFC2822, '0117-08-09 12:34:56'),
- 'Death of Roman Emperor [[Trajan]]');
-
- /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
-
- $this->assertEquals( 'Sun, 01 Jan 0101 00:00:00 GMT',
- wfTimestamp( TS_RFC2822, '-58979923200'),
- '1/1/101');
-
- $this->assertEquals( 'Mon, 01 Jan 0001 00:00:00 GMT',
- wfTimestamp( TS_RFC2822, '-62135596800'),
- 'Year 1');
-
- /* It is not clear if we should generate a year 0 or not
- * We are completely off RFC2822 requirement of year being
- * 1900 or later.
- */
- $this->assertEquals( 'Wed, 18 Oct 0000 00:00:00 GMT',
- wfTimestamp( TS_RFC2822, '-62142076800'),
- 'ISO 8601:2004 [[year 0]], also called [[1 BC]]');
- }
-
- function testHttpDate() {
- # The Resource Loader uses wfTimestamp() to convert timestamps
- # from If-Modified-Since header.
- # Thus it must be able to parse all rfc2616 date formats
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
-
- $this->assertEquals(
- '19941106084937',
- wfTimestamp( TS_MW, 'Sun, 06 Nov 1994 08:49:37 GMT' ),
- 'RFC 822 date' );
-
- $this->assertEquals(
- '19941106084937',
- wfTimestamp( TS_MW, 'Sunday, 06-Nov-94 08:49:37 GMT' ),
- 'RFC 850 date' );
-
- $this->assertEquals(
- '19941106084937',
- wfTimestamp( TS_MW, 'Sun Nov 6 08:49:37 1994' ),
- "ANSI C's asctime() format" );
-
- // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
- $this->assertEquals(
- '20101122141242',
- wfTimestamp( TS_MW, 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626' ),
- "Netscape extension to HTTP/1.0" );
-
- }
-
- function testTimestampParameter() {
- // There are a number of assumptions in our codebase where wfTimestamp() should give
- // the current date but it is not given a 0 there. See r71751 CR
-
- $now = wfTimestamp( TS_UNIX );
- // We check that wfTimestamp doesn't return false (error) and use a LessThan assert
- // for the cases where the test is run in a second boundary.
-
- $zero = wfTimestamp( TS_UNIX, 0 );
- $this->assertNotEquals( false, $zero );
- $this->assertLessThan( 5, $zero - $now );
-
- $empty = wfTimestamp( TS_UNIX, '' );
- $this->assertNotEquals( false, $empty );
- $this->assertLessThan( 5, $empty - $now );
-
- $null = wfTimestamp( TS_UNIX, null );
- $this->assertNotEquals( false, $null );
- $this->assertLessThan( 5, $null - $now );
- }
-
- function testBasename() {
- $sets = array(
- '' => '',
- '/' => '',
- '\\' => '',
- '//' => '',
- '\\\\' => '',
- 'a' => 'a',
- 'aaaa' => 'aaaa',
- '/a' => 'a',
- '\\a' => 'a',
- '/aaaa' => 'aaaa',
- '\\aaaa' => 'aaaa',
- '/aaaa/' => 'aaaa',
- '\\aaaa\\' => 'aaaa',
- '\\aaaa\\' => 'aaaa',
- '/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg' => '93px-Zork_Grand_Inquisitor_box_cover.jpg',
- 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE' => 'VIEWER.EXE',
- 'Östergötland_coat_of_arms.png' => 'Östergötland_coat_of_arms.png',
- );
- foreach ( $sets as $from => $to ) {
- $this->assertEquals( $to, wfBaseName( $from ),
- "wfBaseName('$from') => '$to'" );
- }
- }
-
function testFallbackMbstringFunctions() {
@@ -701,135 +500,6 @@ class GlobalTest extends MediaWikiTestCase {
);
}
-
- /**
- * test @see wfBCP47().
- * Please note the BCP explicitly state that language codes are case
- * insensitive, there are some exceptions to the rule :)
- * This test is used to verify our formatting against all lower and
- * all upper cases language code.
- *
- * @see http://tools.ietf.org/html/bcp47
- * @dataProvider provideLanguageCodes()
- */
- function testBCP47( $code, $expected ) {
- $code = strtolower( $code );
- $this->assertEquals( $expected, wfBCP47($code),
- "Applying BCP47 standard to lower case '$code'"
- );
-
- $code = strtoupper( $code );
- $this->assertEquals( $expected, wfBCP47($code),
- "Applying BCP47 standard to upper case '$code'"
- );
- }
-
- /**
- * Array format is ($code, $expected)
- */
- function provideLanguageCodes() {
- return array(
- // Extracted from BCP47 (list not exhaustive)
- # 2.1.1
- array( 'en-ca-x-ca' , 'en-CA-x-ca' ),
- array( 'sgn-be-fr' , 'sgn-BE-FR' ),
- array( 'az-latn-x-latn', 'az-Latn-x-latn' ),
- # 2.2
- array( 'sr-Latn-RS', 'sr-Latn-RS' ),
- array( 'az-arab-ir', 'az-Arab-IR' ),
-
- # 2.2.5
- array( 'sl-nedis' , 'sl-nedis' ),
- array( 'de-ch-1996', 'de-CH-1996' ),
-
- # 2.2.6
- array(
- 'en-latn-gb-boont-r-extended-sequence-x-private',
- 'en-Latn-GB-boont-r-extended-sequence-x-private'
- ),
-
- // Examples from BCP47 Appendix A
- # Simple language subtag:
- array( 'DE', 'de' ),
- array( 'fR', 'fr' ),
- array( 'ja', 'ja' ),
-
- # Language subtag plus script subtag:
- array( 'zh-hans', 'zh-Hans'),
- array( 'sr-cyrl', 'sr-Cyrl'),
- array( 'sr-latn', 'sr-Latn'),
-
- # Extended language subtags and their primary language subtag
- # counterparts:
- array( 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ),
- array( 'cmn-hans-cn' , 'cmn-Hans-CN' ),
- array( 'zh-yue-hk' , 'zh-yue-HK' ),
- array( 'yue-hk' , 'yue-HK' ),
-
- # Language-Script-Region:
- array( 'zh-hans-cn', 'zh-Hans-CN' ),
- array( 'sr-latn-RS', 'sr-Latn-RS' ),
-
- # Language-Variant:
- array( 'sl-rozaj' , 'sl-rozaj' ),
- array( 'sl-rozaj-biske', 'sl-rozaj-biske' ),
- array( 'sl-nedis' , 'sl-nedis' ),
-
- # Language-Region-Variant:
- array( 'de-ch-1901' , 'de-CH-1901' ),
- array( 'sl-it-nedis' , 'sl-IT-nedis' ),
-
- # Language-Script-Region-Variant:
- array( 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ),
-
- # Language-Region:
- array( 'de-de' , 'de-DE' ),
- array( 'en-us' , 'en-US' ),
- array( 'es-419', 'es-419'),
-
- # Private use subtags:
- array( 'de-ch-x-phonebk' , 'de-CH-x-phonebk' ),
- array( 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ),
- /**
- * Previous test does not reflect the BCP which states:
- * az-Arab-x-AZE-derbend
- * AZE being private, it should be lower case, hence the test above
- * should probably be:
- #array( 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ),
- */
-
- # Private use registry values:
- array( 'x-whatever', 'x-whatever' ),
- array( 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ),
- array( 'de-qaaa' , 'de-Qaaa' ),
- array( 'sr-latn-qm', 'sr-Latn-QM' ),
- array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ),
-
- # Tags that use extensions
- array( 'en-us-u-islamcal', 'en-US-u-islamcal' ),
- array( 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ),
- array( 'en-a-myext-b-another', 'en-a-myext-b-another' ),
-
- # Invalid:
- // de-419-DE
- // a-DE
- // ar-a-aaa-b-bbb-a-ccc
-
- /*
- // ISO 15924 :
- array( 'sr-Cyrl', 'sr-Cyrl' ),
- # @todo FIXME: Fix our function?
- array( 'SR-lATN', 'sr-Latn' ),
- array( 'fr-latn', 'fr-Latn' ),
- // Use lowercase for single segment
- // ISO 3166-1-alpha-2 code
- array( 'US', 'us' ), # USA
- array( 'uS', 'us' ), # USA
- array( 'Fr', 'fr' ), # France
- array( 'va', 'va' ), # Holy See (Vatican City State)
- */);
- }
-
/**
* @dataProvider provideMakeUrlIndexes()
*/
@@ -886,7 +556,63 @@ class GlobalTest extends MediaWikiTestCase {
),
);
}
+
+ /**
+ * @dataProvider provideWfMatchesDomainList
+ */
+ function testWfMatchesDomainList( $url, $domains, $expected, $description ) {
+ $actual = wfMatchesDomainList( $url, $domains );
+ $this->assertEquals( $expected, $actual, $description );
+ }
+
+ function provideWfMatchesDomainList() {
+ $a = array();
+ $protocols = array( 'HTTP' => 'http:', 'HTTPS' => 'https:', 'protocol-relative' => '' );
+ foreach ( $protocols as $pDesc => $p ) {
+ $a = array_merge( $a, array(
+ array( "$p//www.example.com", array(), false, "No matches for empty domains array, $pDesc URL" ),
+ array( "$p//www.example.com", array( 'www.example.com' ), true, "Exact match in domains array, $pDesc URL" ),
+ array( "$p//www.example.com", array( 'example.com' ), true, "Match without subdomain in domains array, $pDesc URL" ),
+ array( "$p//www.example2.com", array( 'www.example.com', 'www.example2.com', 'www.example3.com' ), true, "Exact match with other domains in array, $pDesc URL" ),
+ array( "$p//www.example2.com", array( 'example.com', 'example2.com', 'example3,com' ), true, "Match without subdomain with other domains in array, $pDesc URL" ),
+ array( "$p//www.example4.com", array( 'example.com', 'example2.com', 'example3,com' ), false, "Domain not in array, $pDesc URL" ),
+
+ // FIXME: This is a bug in wfMatchesDomainList(). If and when this is fixed, update this test case
+ array( "$p//nds-nl.wikipedia.org", array( 'nl.wikipedia.org' ), true, "Substrings of domains match while they shouldn't, $pDesc URL" ),
+ ) );
+ }
+ return $a;
+ }
+ /**
+ * @dataProvider provideWfShellMaintenanceCmdList
+ */
+ function testWfShellMaintenanceCmd( $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 );
+ $this->assertEquals( $expected, $actual, $description );
+ }
+
+ function provideWfShellMaintenanceCmdList() {
+ global $wgPhpCli;
+ return array(
+ array( 'eval.php', array( '--help', '--test' ), array(),
+ "'$wgPhpCli' 'eval.php' '--help' '--test'",
+ "Called eval.php --help --test" ),
+ array( 'eval.php', array( '--help', '--test space' ), array('php' => 'php5'),
+ "'php5' 'eval.php' '--help' '--test space'",
+ "Called eval.php --help --test with php option" ),
+ array( 'eval.php', array( '--help', '--test', 'X' ), array('wrapper' => 'MWScript.php'),
+ "'$wgPhpCli' 'MWScript.php' 'eval.php' '--help' '--test' 'X'",
+ "Called eval.php --help --test with wrapper option" ),
+ array( 'eval.php', array( '--help', '--test', 'y' ), array('php' => 'php5', 'wrapper' => 'MWScript.php'),
+ "'php5' 'MWScript.php' 'eval.php' '--help' '--test' 'y'",
+ "Called eval.php --help --test with wrapper and php option" ),
+ );
+ }
/* TODO: many more! */
}
diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
new file mode 100644
index 00000000..4879a38d
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @group Database
+ */
+class GlobalWithDBTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideWfIsBadImageList
+ */
+ function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) {
+ $this->assertEquals( $expected, wfIsBadImage( $name, $title, $blacklist ), $desc );
+ }
+
+ function provideWfIsBadImageList() {
+ $blacklist = '* [[File:Bad.jpg]] except [[Nasty page]]';
+ return array(
+ array( 'Bad.jpg', false, $blacklist, true,
+ 'Called on a bad image' ),
+ array( 'Bad.jpg', Title::makeTitle( NS_MAIN, 'A page' ), $blacklist, true,
+ 'Called on a bad image' ),
+ array( 'NotBad.jpg', false, $blacklist, false,
+ 'Called on a non-bad image' ),
+ array( 'Bad.jpg', Title::makeTitle( NS_MAIN, 'Nasty page' ), $blacklist, false,
+ 'Called on a bad image but is on a whitelisted page' ),
+ array( 'File:Bad.jpg', false, $blacklist, false,
+ 'Called on a bad image with File:' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
new file mode 100644
index 00000000..be6c99e7
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Unit tests for wfAssembleUrl()
+ */
+
+class wfAssembleUrl extends MediaWikiTestCase {
+ /** @dataProvider provideURLParts */
+ public function testWfAssembleUrl( $parts, $output ) {
+ $partsDump = print_r( $parts, true );
+ $this->assertEquals(
+ $output,
+ wfAssembleUrl( $parts ),
+ "Testing $partsDump assembles to $output"
+ );
+ }
+
+ /**
+ * Provider of URL parts for testing wfAssembleUrl()
+ *
+ * @return array
+ */
+ public function provideURLParts() {
+ $schemes = array(
+ '' => array(),
+ '//' => array(
+ 'delimiter' => '//',
+ ),
+ 'http://' => array(
+ 'scheme' => 'http',
+ 'delimiter' => '://',
+ ),
+ );
+
+ $hosts = array(
+ '' => array(),
+ 'example.com' => array(
+ 'host' => 'example.com',
+ ),
+ 'example.com:123' => array(
+ 'host' => 'example.com',
+ 'port' => 123,
+ ),
+ 'id@example.com' => array(
+ 'user' => 'id',
+ 'host' => 'example.com',
+ ),
+ 'id@example.com:123' => array(
+ 'user' => 'id',
+ 'host' => 'example.com',
+ 'port' => 123,
+ ),
+ 'id:key@example.com' => array(
+ 'user' => 'id',
+ 'pass' => 'key',
+ 'host' => 'example.com',
+ ),
+ 'id:key@example.com:123' => array(
+ 'user' => 'id',
+ 'pass' => 'key',
+ 'host' => 'example.com',
+ 'port' => 123,
+ ),
+ );
+
+ $cases = array();
+ foreach ( $schemes as $scheme => $schemeParts ) {
+ foreach ( $hosts as $host => $hostParts ) {
+ foreach ( array( '', '/path' ) as $path ) {
+ foreach ( array( '', 'query' ) as $query ) {
+ foreach ( array( '', 'fragment' ) as $fragment ) {
+ $parts = array_merge(
+ $schemeParts,
+ $hostParts
+ );
+ $url = $scheme .
+ $host .
+ $path;
+
+ if ( $path ) {
+ $parts['path'] = $path;
+ }
+ if ( $query ) {
+ $parts['query'] = $query;
+ $url .= '?' . $query;
+ }
+ if( $fragment ) {
+ $parts['fragment'] = $fragment;
+ $url .= '#' . $fragment;
+ }
+
+
+ $cases[] = array(
+ $parts,
+ $url,
+ );
+ }
+ }
+ }
+ }
+ }
+
+ $complexURL = 'http://id:key@example.org:321' .
+ '/over/there?name=ferret&foo=bar#nose';
+ $cases[] = array(
+ wfParseUrl( $complexURL ),
+ $complexURL,
+ );
+
+ return $cases;
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
new file mode 100644
index 00000000..f4ec7a5f
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Unit tests for wfBCP47()
+ */
+class wfBCP47 extends MediaWikiTestCase {
+ /**
+ * test @see wfBCP47().
+ * Please note the BCP explicitly state that language codes are case
+ * insensitive, there are some exceptions to the rule :)
+ * This test is used to verify our formatting against all lower and
+ * all upper cases language code.
+ *
+ * @see http://tools.ietf.org/html/bcp47
+ * @dataProvider provideLanguageCodes()
+ */
+ function testBCP47( $code, $expected ) {
+ $code = strtolower( $code );
+ $this->assertEquals( $expected, wfBCP47($code),
+ "Applying BCP47 standard to lower case '$code'"
+ );
+
+ $code = strtoupper( $code );
+ $this->assertEquals( $expected, wfBCP47($code),
+ "Applying BCP47 standard to upper case '$code'"
+ );
+ }
+
+ /**
+ * Array format is ($code, $expected)
+ */
+ function provideLanguageCodes() {
+ return array(
+ // Extracted from BCP47 (list not exhaustive)
+ # 2.1.1
+ array( 'en-ca-x-ca' , 'en-CA-x-ca' ),
+ array( 'sgn-be-fr' , 'sgn-BE-FR' ),
+ array( 'az-latn-x-latn', 'az-Latn-x-latn' ),
+ # 2.2
+ array( 'sr-Latn-RS', 'sr-Latn-RS' ),
+ array( 'az-arab-ir', 'az-Arab-IR' ),
+
+ # 2.2.5
+ array( 'sl-nedis' , 'sl-nedis' ),
+ array( 'de-ch-1996', 'de-CH-1996' ),
+
+ # 2.2.6
+ array(
+ 'en-latn-gb-boont-r-extended-sequence-x-private',
+ 'en-Latn-GB-boont-r-extended-sequence-x-private'
+ ),
+
+ // Examples from BCP47 Appendix A
+ # Simple language subtag:
+ array( 'DE', 'de' ),
+ array( 'fR', 'fr' ),
+ array( 'ja', 'ja' ),
+
+ # Language subtag plus script subtag:
+ array( 'zh-hans', 'zh-Hans'),
+ array( 'sr-cyrl', 'sr-Cyrl'),
+ array( 'sr-latn', 'sr-Latn'),
+
+ # Extended language subtags and their primary language subtag
+ # counterparts:
+ array( 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ),
+ array( 'cmn-hans-cn' , 'cmn-Hans-CN' ),
+ array( 'zh-yue-hk' , 'zh-yue-HK' ),
+ array( 'yue-hk' , 'yue-HK' ),
+
+ # Language-Script-Region:
+ array( 'zh-hans-cn', 'zh-Hans-CN' ),
+ array( 'sr-latn-RS', 'sr-Latn-RS' ),
+
+ # Language-Variant:
+ array( 'sl-rozaj' , 'sl-rozaj' ),
+ array( 'sl-rozaj-biske', 'sl-rozaj-biske' ),
+ array( 'sl-nedis' , 'sl-nedis' ),
+
+ # Language-Region-Variant:
+ array( 'de-ch-1901' , 'de-CH-1901' ),
+ array( 'sl-it-nedis' , 'sl-IT-nedis' ),
+
+ # Language-Script-Region-Variant:
+ array( 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ),
+
+ # Language-Region:
+ array( 'de-de' , 'de-DE' ),
+ array( 'en-us' , 'en-US' ),
+ array( 'es-419', 'es-419'),
+
+ # Private use subtags:
+ array( 'de-ch-x-phonebk' , 'de-CH-x-phonebk' ),
+ array( 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ),
+ /**
+ * Previous test does not reflect the BCP which states:
+ * az-Arab-x-AZE-derbend
+ * AZE being private, it should be lower case, hence the test above
+ * should probably be:
+ #array( 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ),
+ */
+
+ # Private use registry values:
+ array( 'x-whatever', 'x-whatever' ),
+ array( 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ),
+ array( 'de-qaaa' , 'de-Qaaa' ),
+ array( 'sr-latn-qm', 'sr-Latn-QM' ),
+ array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ),
+
+ # Tags that use extensions
+ array( 'en-us-u-islamcal', 'en-US-u-islamcal' ),
+ array( 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ),
+ array( 'en-a-myext-b-another', 'en-a-myext-b-another' ),
+
+ # Invalid:
+ // de-419-DE
+ // a-DE
+ // ar-a-aaa-b-bbb-a-ccc
+
+ /*
+ // ISO 15924 :
+ array( 'sr-Cyrl', 'sr-Cyrl' ),
+ # @todo FIXME: Fix our function?
+ array( 'SR-lATN', 'sr-Latn' ),
+ array( 'fr-latn', 'fr-Latn' ),
+ // Use lowercase for single segment
+ // ISO 3166-1-alpha-2 code
+ array( 'US', 'us' ), # USA
+ array( 'uS', 'us' ), # USA
+ array( 'Fr', 'fr' ), # France
+ array( 'va', 'va' ), # Holy See (Vatican City State)
+ */);
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
new file mode 100644
index 00000000..59954b23
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Tests for wfBaseName()
+ */
+class wfBaseName extends MediaWikiTestCase {
+ /**
+ * @dataProvider providePaths
+ */
+ function testBaseName( $fullpath, $basename ) {
+ $this->assertEquals( $basename, wfBaseName( $fullpath ),
+ "wfBaseName('$fullpath') => '$basename'" );
+ }
+
+ function providePaths() {
+ return array(
+ array( '', '' ),
+ array( '/', '' ),
+ array( '\\', '' ),
+ array( '//', '' ),
+ array( '\\\\', '' ),
+ array( 'a', 'a' ),
+ array( 'aaaa', 'aaaa' ),
+ array( '/a', 'a' ),
+ array( '\\a', 'a' ),
+ array( '/aaaa', 'aaaa' ),
+ array( '\\aaaa', 'aaaa' ),
+ array( '/aaaa/', 'aaaa' ),
+ array( '\\aaaa\\', 'aaaa' ),
+ array( '\\aaaa\\', 'aaaa' ),
+ array( '/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg',
+ '93px-Zork_Grand_Inquisitor_box_cover.jpg' ),
+ array( 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ),
+ array( 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
index b388b266..192689f8 100644
--- a/tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php
+++ b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* Unit tests for wfExpandUrl()
*/
@@ -12,16 +12,16 @@ class wfExpandUrl extends MediaWikiTestCase {
$oldCanServer = $wgCanonicalServer;
$wgServer = $server;
$wgCanonicalServer = $canServer;
-
+
// Fake $_SERVER['HTTPS'] if needed
if ( $httpsMode ) {
$_SERVER['HTTPS'] = 'on';
} else {
unset( $_SERVER['HTTPS'] );
}
-
+
$this->assertEquals( $fullUrl, wfExpandUrl( $shortUrl, $defaultProto ), $message );
-
+
// Restore $wgServer and $wgCanonicalServer
$wgServer = $oldServer;
$wgCanonicalServer = $oldCanServer;
@@ -29,12 +29,14 @@ class wfExpandUrl extends MediaWikiTestCase {
/**
* Provider of URL examples for testing wfExpandUrl()
+ *
+ * @return array
*/
public function provideExpandableUrls() {
$modes = array( 'http', 'https' );
$servers = array( 'http' => 'http://example.com', 'https' => 'https://example.com', 'protocol-relative' => '//example.com' );
$defaultProtos = array( 'http' => PROTO_HTTP, 'https' => PROTO_HTTPS, 'protocol-relative' => PROTO_RELATIVE, 'current' => PROTO_CURRENT, 'canonical' => PROTO_CANONICAL );
-
+
$retval = array();
foreach ( $modes as $mode ) {
$httpsMode = $mode == 'https';
@@ -46,14 +48,14 @@ class wfExpandUrl extends MediaWikiTestCase {
$retval[] = array( 'https://example.com', 'https://example.com', $defaultProto, $server, $canServer, $httpsMode, "Testing fully qualified https URLs (no need to expand) (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" );
# Would be nice to support this, see fixme on wfExpandUrl()
$retval[] = array( "wiki/FooBar", 'wiki/FooBar', $defaultProto, $server, $canServer, $httpsMode, "Test non-expandable relative URLs (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" );
-
+
// Determine expected protocol
$p = $protoDesc . ':'; // default case
if ( $protoDesc == 'protocol-relative' ) {
$p = '';
- } else if ( $protoDesc == 'current' ) {
+ } elseif ( $protoDesc == 'current' ) {
$p = "$mode:";
- } else if ( $protoDesc == 'canonical' ) {
+ } elseif ( $protoDesc == 'canonical' ) {
$p = "$canServerMode:";
} else {
$p = $protoDesc . ':';
@@ -61,12 +63,12 @@ class wfExpandUrl extends MediaWikiTestCase {
// Determine expected server name
if ( $protoDesc == 'canonical' ) {
$srv = $canServer;
- } else if ( $serverDesc == 'protocol-relative' ) {
+ } elseif ( $serverDesc == 'protocol-relative' ) {
$srv = $p . $server;
} else {
$srv = $server;
}
-
+
$retval[] = array( "$p//wikipedia.org", '//wikipedia.org', $defaultProto, $server, $canServer, $httpsMode, "Test protocol-relative URL (defaultProto: $protoDesc, wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" );
$retval[] = array( "$srv/wiki/FooBar", '/wiki/FooBar', $defaultProto, $server, $canServer, $httpsMode, "Testing expanding URL beginning with / (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" );
}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
new file mode 100644
index 00000000..1cf0e0fb
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Unit tests for wfRemoveDotSegments()
+ */
+
+class wfRemoveDotSegments extends MediaWikiTestCase {
+ /** @dataProvider providePaths */
+ public function testWfRemoveDotSegments( $inputPath, $outputPath ) {
+ $this->assertEquals(
+ $outputPath,
+ wfRemoveDotSegments( $inputPath ),
+ "Testing $inputPath expands to $outputPath"
+ );
+ }
+
+ /**
+ * Provider of URL paths for testing wfRemoveDotSegments()
+ *
+ * @return array
+ */
+ public function providePaths() {
+ return array(
+ array( '/a/b/c/./../../g', '/a/g' ),
+ array( 'mid/content=5/../6', 'mid/6' ),
+ array( '/a//../b', '/a/b' ),
+ array( '/.../a', '/.../a' ),
+ array( '.../a', '.../a' ),
+ array( '', '' ),
+ array( '/', '/' ),
+ array( '//', '//' ),
+ array( '.', '' ),
+ array( '..', '' ),
+ array( '...', '...' ),
+ array( '/.', '/' ),
+ array( '/..', '/' ),
+ array( './', '' ),
+ array( '../', '' ),
+ array( './a', 'a' ),
+ array( '../a', 'a' ),
+ array( '../../a', 'a' ),
+ array( '.././a', 'a' ),
+ array( './../a', 'a' ),
+ array( '././a', 'a' ),
+ array( '../../', '' ),
+ array( '.././', '' ),
+ array( './../', '' ),
+ array( '././', '' ),
+ array( '../..', '' ),
+ array( '../.', '' ),
+ array( './..', '' ),
+ array( './.', '' ),
+ array( '/../../a', '/a' ),
+ array( '/.././a', '/a' ),
+ array( '/./../a', '/a' ),
+ array( '/././a', '/a' ),
+ array( '/../../', '/' ),
+ array( '/.././', '/' ),
+ array( '/./../', '/' ),
+ array( '/././', '/' ),
+ array( '/../..', '/' ),
+ array( '/../.', '/' ),
+ array( '/./..', '/' ),
+ array( '/./.', '/' ),
+ array( 'b/../../a', '/a' ),
+ array( 'b/.././a', '/a' ),
+ array( 'b/./../a', '/a' ),
+ array( 'b/././a', 'b/a' ),
+ array( 'b/../../', '/' ),
+ array( 'b/.././', '/' ),
+ array( 'b/./../', '/' ),
+ array( 'b/././', 'b/' ),
+ array( 'b/../..', '/' ),
+ array( 'b/../.', '/' ),
+ array( 'b/./..', '/' ),
+ array( 'b/./.', 'b/' ),
+ array( '/b/../../a', '/a' ),
+ array( '/b/.././a', '/a' ),
+ array( '/b/./../a', '/a' ),
+ array( '/b/././a', '/b/a' ),
+ array( '/b/../../', '/' ),
+ array( '/b/.././', '/' ),
+ array( '/b/./../', '/' ),
+ array( '/b/././', '/b/' ),
+ array( '/b/../..', '/' ),
+ array( '/b/../.', '/' ),
+ array( '/b/./..', '/' ),
+ array( '/b/./.', '/b/' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
new file mode 100644
index 00000000..1df26d2c
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
@@ -0,0 +1,28 @@
+<?php
+
+class wfShorthandToIntegerTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideABunchOfShorthands
+ */
+ function testWfShorthandToInteger( $input, $output, $description ) {
+ $this->assertEquals(
+ wfShorthandToInteger( $input ),
+ $output,
+ $description
+ );
+ }
+
+ function provideABunchOfShorthands() {
+ return array(
+ array( '', -1, 'Empty string' ),
+ array( ' ', -1, 'String of spaces' ),
+ array( '1G', 1024 * 1024 * 1024, 'One gig uppercased' ),
+ array( '1g', 1024 * 1024 * 1024, 'One gig lowercased' ),
+ array( '1M', 1024 * 1024, 'One meg uppercased' ),
+ array( '1m', 1024 * 1024, 'One meg lowercased' ),
+ array( '1K', 1024, 'One kb uppercased' ),
+ array( '1k', 1024, 'One kb lowercased' ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
new file mode 100644
index 00000000..505c28c7
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * Tests for wfTimestamp()
+ */
+class wfTimestamp extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideNormalTimestamps
+ */
+ function testNormalTimestamps( $input, $format, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+ }
+
+ function provideNormalTimestamps() {
+ $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
+ return array (
+ // TS_UNIX
+ array( $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ),
+ array( -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ),
+ array( $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ),
+ array( $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ),
+
+ array( $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ),
+
+ // TS_MW
+ array( '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ),
+ array( '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ),
+ array( '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ),
+ array( '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ),
+
+ // TS_DB
+ array( '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ),
+ array( '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ),
+ array( '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ),
+ array( '2001-01-15 12:34:56', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_DB to TS_ISO_8601_BASIC' ),
+
+ # rfc2822 section 3.3
+ array( '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ),
+ array( 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ),
+ array( ' Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 with leading space to TS_MW' ),
+ array( '15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 without optional day-of-week to TS_MW' ),
+
+ # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
+ # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
+ array( 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ),
+
+ # WSP = SP / HTAB ; rfc2234
+ array( "Mon, 15 Jan\x092001 12:34:56 GMT", TS_MW, '20010115123456', 'TS_RFC2822 with HTAB to TS_MW' ),
+ array( "Mon, 15 Jan\x09 \x09 2001 12:34:56 GMT", TS_MW, '20010115123456', 'TS_RFC2822 with HTAB and SP to TS_MW' ),
+ array( 'Sun, 6 Nov 94 08:49:37 GMT', TS_MW, '19941106084937', 'TS_RFC2822 with obsolete year to TS_MW' ),
+ );
+ }
+
+ /**
+ * This test checks wfTimestamp() with values outside.
+ * It needs PHP 64 bits or PHP > 5.1.
+ * See r74778 and bug 25451
+ * @dataProvider provideOldTimestamps
+ */
+ function testOldTimestamps( $input, $format, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+ }
+
+ function provideOldTimestamps() {
+ return array (
+ array( '19011213204554', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:54 GMT', 'Earliest time according to php documentation' ),
+ array( '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ),
+ array( '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ),
+ array( '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ),
+ array( '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ),
+ array( '19011213204551', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1' ),
+ array( '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ),
+ array( '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ),
+ array( '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ),
+ array( '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ),
+ array( '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ),
+ array( '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ),
+ array( '0117-08-09 12:34:56', TS_RFC2822, 'Tue, 09 Aug 0117 12:34:56 GMT', 'Death of Roman Emperor [[Trajan]]' ),
+
+ /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
+ array( '-58979923200', TS_RFC2822, 'Sun, 01 Jan 0101 00:00:00 GMT', '1/1/101' ),
+ array( '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ),
+
+ /* It is not clear if we should generate a year 0 or not
+ * We are completely off RFC2822 requirement of year being
+ * 1900 or later.
+ */
+ array( '-62142076800', TS_RFC2822, 'Wed, 18 Oct 0000 00:00:00 GMT', 'ISO 8601:2004 [[year 0]], also called [[1 BC]]' ),
+ );
+ }
+
+ /**
+ * The Resource Loader uses wfTimestamp() to convert timestamps
+ * from If-Modified-Since header. Thus it must be able to parse all
+ * rfc2616 date formats
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
+ * @dataProvider provideHttpDates
+ */
+ function testHttpDate( $input, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
+ }
+
+ function provideHttpDates() {
+ return array(
+ array( 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ),
+ array( 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ),
+ array( 'Sun Nov 6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ),
+ // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
+ array( 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626', '20101122141242', 'Netscape extension to HTTP/1.0' ),
+ );
+ }
+
+ /**
+ * There are a number of assumptions in our codebase where wfTimestamp()
+ * should give the current date but it is not given a 0 there. See r71751 CR
+ */
+ function testTimestampParameter() {
+ $now = wfTimestamp( TS_UNIX );
+ // We check that wfTimestamp doesn't return false (error) and use a LessThan assert
+ // for the cases where the test is run in a second boundary.
+
+ $zero = wfTimestamp( TS_UNIX, 0 );
+ $this->assertNotEquals( false, $zero );
+ $this->assertLessThan( 5, $zero - $now );
+
+ $empty = wfTimestamp( TS_UNIX, '' );
+ $this->assertNotEquals( false, $empty );
+ $this->assertLessThan( 5, $empty - $now );
+
+ $null = wfTimestamp( TS_UNIX, null );
+ $this->assertNotEquals( false, $null );
+ $this->assertLessThan( 5, $null - $now );
+ }
+}
diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php
index 96bb1803..67b60d32 100644
--- a/tests/phpunit/includes/HtmlTest.php
+++ b/tests/phpunit/includes/HtmlTest.php
@@ -3,58 +3,90 @@
class HtmlTest extends MediaWikiTestCase {
private static $oldLang;
+ private static $oldContLang;
+ private static $oldLanguageCode;
+ private static $oldNamespaces;
public function setUp() {
- global $wgLang, $wgLanguageCode;
+ global $wgLang, $wgContLang, $wgLanguageCode;
self::$oldLang = $wgLang;
+ self::$oldContLang = $wgContLang;
+ self::$oldNamespaces = $wgContLang->getNamespaces();
+ self::$oldLanguageCode = $wgLanguageCode;
+
$wgLanguageCode = 'en';
- $wgLang = Language::factory( $wgLanguageCode );
+ $wgContLang = $wgLang = Language::factory( $wgLanguageCode );
+
+ // Hardcode namespaces during test runs,
+ // so that html output based on existing namespaces
+ // can be properly evaluated.
+ $wgContLang->setNamespaces( array(
+ -2 => 'Media',
+ -1 => 'Special',
+ 0 => '',
+ 1 => 'Talk',
+ 2 => 'User',
+ 3 => 'User_talk',
+ 4 => 'MyWiki',
+ 5 => 'MyWiki_Talk',
+ 6 => 'File',
+ 7 => 'File_talk',
+ 8 => 'MediaWiki',
+ 9 => 'MediaWiki_talk',
+ 10 => 'Template',
+ 11 => 'Template_talk',
+ 100 => 'Custom',
+ 101 => 'Custom_talk',
+ ) );
}
public function tearDown() {
- global $wgLang, $wgLanguageCode;
+ global $wgLang, $wgContLang, $wgLanguageCode;
+
+ $wgContLang->setNamespaces( self::$oldNamespaces );
$wgLang = self::$oldLang;
- $wgLanguageCode = $wgLang->getCode();
+ $wgContLang = self::$oldContLang;
+ $wgLanguageCode = self::$oldLanguageCode;
}
public function testExpandAttributesSkipsNullAndFalse() {
### EMPTY ########
$this->AssertEmpty(
- Html::expandAttributes( array( 'foo'=>null) ),
+ Html::expandAttributes( array( 'foo' => null ) ),
'skip keys with null value'
);
$this->AssertEmpty(
- Html::expandAttributes( array( 'foo'=>false) ),
+ Html::expandAttributes( array( 'foo' => false ) ),
'skip keys with false value'
);
$this->AssertNotEmpty(
- Html::expandAttributes( array( 'foo'=>'') ),
+ Html::expandAttributes( array( 'foo' => '' ) ),
'keep keys with an empty string'
);
}
public function testExpandAttributesForBooleans() {
+ global $wgHtml5;
$this->AssertEquals(
'',
- Html::expandAttributes( array( 'selected'=>false) ),
+ Html::expandAttributes( array( 'selected' => false ) ),
'Boolean attributes do not generates output when value is false'
);
$this->AssertEquals(
'',
- Html::expandAttributes( array( 'selected'=>null) ),
+ Html::expandAttributes( array( 'selected' => null ) ),
'Boolean attributes do not generates output when value is null'
);
- ### FIXME: maybe they should just output 'selected'
$this->AssertEquals(
- ' selected=""',
- Html::expandAttributes( array( 'selected'=>true ) ),
+ $wgHtml5 ? ' selected=""' : ' selected="selected"',
+ Html::expandAttributes( array( 'selected' => true ) ),
'Boolean attributes skip value output'
);
$this->AssertEquals(
- ' selected=""',
+ $wgHtml5 ? ' selected=""' : ' selected="selected"',
Html::expandAttributes( array( 'selected' ) ),
'Boolean attributes (ex: selected) do not need a value'
);
@@ -68,23 +100,234 @@ class HtmlTest extends MediaWikiTestCase {
### NOT EMPTY ####
$this->AssertEquals(
' empty_string=""',
- Html::expandAttributes( array( 'empty_string'=>'') ),
+ Html::expandAttributes( array( 'empty_string' => '' ) ),
'Value with an empty string'
);
$this->AssertEquals(
' key="value"',
- Html::expandAttributes( array( 'key'=>'value') ),
+ Html::expandAttributes( array( 'key' => 'value' ) ),
'Value is a string'
);
$this->AssertEquals(
' one="1"',
- Html::expandAttributes( array( 'one'=>1) ),
+ Html::expandAttributes( array( 'one' => 1 ) ),
'Value is a numeric one'
);
$this->AssertEquals(
' zero="0"',
- Html::expandAttributes( array( 'zero'=>0) ),
+ Html::expandAttributes( array( 'zero' => 0 ) ),
'Value is a numeric zero'
);
}
+
+ /**
+ * Html::expandAttributes has special features for HTML
+ * attributes that use space separated lists and also
+ * allows arrays to be used as values.
+ */
+ public function testExpandAttributesListValueAttributes() {
+ ### STRING VALUES
+ $this->AssertEquals(
+ ' class="redundant spaces here"',
+ Html::expandAttributes( array( 'class' => ' redundant spaces here ' ) ),
+ 'Normalization should strip redundant spaces'
+ );
+ $this->AssertEquals(
+ ' class="foo bar"',
+ Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ),
+ 'Normalization should remove duplicates in string-lists'
+ );
+ ### "EMPTY" ARRAY VALUES
+ $this->AssertEquals(
+ ' class=""',
+ Html::expandAttributes( array( 'class' => array() ) ),
+ 'Value with an empty array'
+ );
+ $this->AssertEquals(
+ ' class=""',
+ Html::expandAttributes( array( 'class' => array( null, '', ' ', ' ' ) ) ),
+ 'Array with null, empty string and spaces'
+ );
+ ### NON-EMPTY ARRAY VALUES
+ $this->AssertEquals(
+ ' class="foo bar"',
+ Html::expandAttributes( array( 'class' => array(
+ 'foo',
+ 'bar',
+ 'foo',
+ 'bar',
+ 'bar',
+ ) ) ),
+ 'Normalization should remove duplicates in the array'
+ );
+ $this->AssertEquals(
+ ' class="foo bar"',
+ Html::expandAttributes( array( 'class' => array(
+ 'foo bar',
+ 'bar foo',
+ 'foo',
+ 'bar bar',
+ ) ) ),
+ 'Normalization should remove duplicates in string-lists in the array'
+ );
+ }
+
+ /**
+ * Test feature added by r96188, let pass attributes values as
+ * a PHP array. Restricted to class,rel, accesskey.
+ */
+ function testExpandAttributesSpaceSeparatedAttributesWithBoolean() {
+ $this->assertEquals(
+ ' class="booltrue one"',
+ Html::expandAttributes( array( 'class' => array(
+ 'booltrue' => true,
+ 'one' => 1,
+
+ # Method use isset() internally, make sure we do discard
+ # attributes values which have been assigned well known values
+ 'emptystring' => '',
+ 'boolfalse' => false,
+ 'zero' => 0,
+ 'null' => null,
+ )))
+ );
+ }
+
+ /**
+ * How do we handle duplicate keys in HTML attributes expansion?
+ * We could pass a "class" the values: 'GREEN' and array( 'GREEN' => false )
+ * The later will take precedence.
+ *
+ * Feature added by r96188
+ */
+ function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() {
+ $this->assertEquals(
+ ' class=""',
+ Html::expandAttributes( array( 'class' => array(
+ 'GREEN',
+ 'GREEN' => false,
+ 'GREEN',
+ )))
+ );
+ }
+
+ function testNamespaceSelector() {
+ $this->assertEquals(
+ '<select id="namespace" name="namespace">' . "\n" .
+'<option value="0">(Main)</option>' . "\n" .
+'<option value="1">Talk</option>' . "\n" .
+'<option value="2">User</option>' . "\n" .
+'<option value="3">User talk</option>' . "\n" .
+'<option value="4">MyWiki</option>' . "\n" .
+'<option value="5">MyWiki Talk</option>' . "\n" .
+'<option value="6">File</option>' . "\n" .
+'<option value="7">File talk</option>' . "\n" .
+'<option value="8">MediaWiki</option>' . "\n" .
+'<option value="9">MediaWiki talk</option>' . "\n" .
+'<option value="10">Template</option>' . "\n" .
+'<option value="11">Template talk</option>' . "\n" .
+'<option value="100">Custom</option>' . "\n" .
+'<option value="101">Custom talk</option>' . "\n" .
+'</select>',
+ Html::namespaceSelector(),
+ 'Basic namespace selector without custom options'
+ );
+ $this->assertEquals(
+ '<label for="mw-test-namespace">Select a namespace:</label>&#160;' .
+'<select id="mw-test-namespace" name="wpNamespace">' . "\n" .
+'<option value="all">all</option>' . "\n" .
+'<option value="0">(Main)</option>' . "\n" .
+'<option value="1">Talk</option>' . "\n" .
+'<option value="2" selected="">User</option>' . "\n" .
+'<option value="3">User talk</option>' . "\n" .
+'<option value="4">MyWiki</option>' . "\n" .
+'<option value="5">MyWiki Talk</option>' . "\n" .
+'<option value="6">File</option>' . "\n" .
+'<option value="7">File talk</option>' . "\n" .
+'<option value="8">MediaWiki</option>' . "\n" .
+'<option value="9">MediaWiki talk</option>' . "\n" .
+'<option value="10">Template</option>' . "\n" .
+'<option value="11">Template talk</option>' . "\n" .
+'<option value="100">Custom</option>' . "\n" .
+'<option value="101">Custom talk</option>' . "\n" .
+'</select>',
+ Html::namespaceSelector(
+ array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ),
+ array( 'name' => 'wpNamespace', 'id' => 'mw-test-namespace' )
+ ),
+ 'Basic namespace selector with custom values'
+ );
+ }
+
+ function testNamespaceSelectorIdAndNameDefaultsAttributes() {
+
+ $this->assertNsSelectorIdAndName(
+ 'namespace', 'namespace',
+ Html::namespaceSelector( array(), array(
+ # neither 'id' nor 'name' key given
+ )),
+ "Neither 'id' nor 'name' key given"
+ );
+
+ $this->assertNsSelectorIdAndName(
+ 'namespace', 'select_name',
+ Html::namespaceSelector( array(), array(
+ 'name' => 'select_name',
+ # no 'id' key given
+ )),
+ "No 'id' key given, 'name' given"
+ );
+
+ $this->assertNsSelectorIdAndName(
+ 'select_id', 'namespace',
+ Html::namespaceSelector( array(), array(
+ 'id' => 'select_id',
+ # no 'name' key given
+ )),
+ "'id' given, no 'name' key given"
+ );
+
+ $this->assertNsSelectorIdAndName(
+ 'select_id', 'select_name',
+ Html::namespaceSelector( array(), array(
+ 'id' => 'select_id',
+ 'name' => 'select_name',
+ )),
+ "Both 'id' and 'name' given"
+ );
+ }
+
+ /**
+ * Helper to verify <select> attributes generated by Html::namespaceSelector()
+ * This helper expect the Html method to use 'namespace' as a default value for
+ * both 'id' and 'name' attributes.
+ *
+ * @param String $expectedId <select> id attribute value
+ * @param String $expectedName <select> name attribute value
+ * @param String $html Output of a call to Html::namespaceSelector()
+ * @param String $msg Optional message (default: '')
+ */
+ function assertNsSelectorIdAndName( $expectedId, $expectedName, $html, $msg = '' ) {
+ $actualId = 'namespace';
+ if( 1 === preg_match( '/id="(.+?)"/', $html, $m ) ) {
+ $actualId = $m[1];
+ }
+
+ $actualName = 'namespace';
+ if( 1 === preg_match( '/name="(.+?)"/', $html, $m ) ) {
+ $actualName = $m[1];
+ }
+ $this->assertEquals(
+ array( #expected
+ 'id' => $expectedId,
+ 'name' => $expectedName,
+ ),
+ array( #actual
+ 'id' => $actualId,
+ 'name' => $actualName,
+ ),
+ 'Html::namespaceSelector() got wrong id and/or name attribute(s). ' . $msg
+ );
+ }
+
}
diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php
index 1a99af7d..263383f1 100644
--- a/tests/phpunit/includes/HttpTest.php
+++ b/tests/phpunit/includes/HttpTest.php
@@ -1,325 +1,12 @@
<?php
-
-class MockCookie extends Cookie {
- public function canServeDomain( $arg ) { return parent::canServeDomain( $arg ); }
- public function canServePath( $arg ) { return parent::canServePath( $arg ); }
- public function isUnExpired() { return parent::isUnExpired(); }
-}
-
/**
* @group Broken
*/
class HttpTest extends MediaWikiTestCase {
- static $content;
- static $headers;
- static $has_curl;
- static $has_fopen;
- static $has_proxy = false;
- static $proxy = "http://hulk:8080/";
- var $test_geturl = array(
- "http://en.wikipedia.org/robots.txt",
- "https://secure.wikimedia.org/",
- "http://pecl.php.net/feeds/pkg_apc.rss",
- "http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw",
- "http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&format=php",
- );
- var $test_requesturl = array( "http://en.wikipedia.org/wiki/Special:Export/User:MarkAHershberger" );
-
- var $test_posturl = array( "http://www.comp.leeds.ac.uk/cgi-bin/Perl/environment-example" => "review=test" );
-
- function setUp() {
- putenv( "http_proxy" ); /* Remove any proxy env var, so curl doesn't get confused */
- if ( is_array( self::$content ) ) {
- return;
- }
- self::$has_curl = function_exists( 'curl_init' );
- self::$has_fopen = wfIniGetBool( 'allow_url_fopen' );
-
- if ( !file_exists( "/usr/bin/curl" ) ) {
- $this->markTestIncomplete( "This test requires the curl binary at /usr/bin/curl. If you have curl, please file a bug on this test, or, better yet, provide a patch." );
- }
-
- $content = tempnam( wfTempDir(), "" );
- $headers = tempnam( wfTempDir(), "" );
- if ( !$content && !$headers ) {
- die( "Couldn't create temp file!" );
- }
-
- // This probably isn't the best test for a proxy, but it works on my system!
- system( "curl -0 -o $content -s " . self::$proxy );
- $out = file_get_contents( $content );
- if ( $out ) {
- self::$has_proxy = true;
- }
-
- /* Maybe use wget instead of curl here ... just to use a different codebase? */
- foreach ( $this->test_geturl as $u ) {
- system( "curl -0 -s -D $headers '$u' -o $content" );
- self::$content["GET $u"] = file_get_contents( $content );
- self::$headers["GET $u"] = file_get_contents( $headers );
- }
- foreach ( $this->test_requesturl as $u ) {
- system( "curl -0 -s -X POST -H 'Content-Length: 0' -D $headers '$u' -o $content" );
- self::$content["POST $u"] = file_get_contents( $content );
- self::$headers["POST $u"] = file_get_contents( $headers );
- }
- foreach ( $this->test_posturl as $u => $postData ) {
- system( "curl -0 -s -X POST -d '$postData' -D $headers '$u' -o $content" );
- self::$content["POST $u => $postData"] = file_get_contents( $content );
- self::$headers["POST $u => $postData"] = file_get_contents( $headers );
- }
- unlink( $content );
- unlink( $headers );
- }
-
-
- function testInstantiation() {
- Http::$httpEngine = false;
-
- $r = MWHttpRequest::factory( "http://www.example.com/" );
- if ( self::$has_curl ) {
- $this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) );
- } else {
- $this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) );
- }
- unset( $r );
-
- if ( !self::$has_fopen ) {
- $this->setExpectedException( 'MWException' );
- }
- Http::$httpEngine = 'php';
- $r = MWHttpRequest::factory( "http://www.example.com/" );
- $this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) );
- unset( $r );
-
- if ( !self::$has_curl ) {
- $this->setExpectedException( 'MWException' );
- }
- Http::$httpEngine = 'curl';
- $r = MWHttpRequest::factory( "http://www.example.com/" );
- if ( self::$has_curl ) {
- $this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) );
- }
- }
-
- function runHTTPFailureChecks() {
- // Each of the following requests should result in a failure.
-
- $timeout = 1;
- $start_time = time();
- $r = Http::get( "http://www.example.com:1/", $timeout );
- $end_time = time();
- $this->assertLessThan( $timeout + 2, $end_time - $start_time,
- "Request took less than {$timeout}s via " . Http::$httpEngine );
- $this->assertEquals( $r, false, "false -- what we get on error from Http::get()" );
-
- $r = Http::get( "http://www.mediawiki.org/xml/made-up-url", $timeout );
- $this->assertFalse( $r, "False on 404s" );
-
-
- $r = MWHttpRequest::factory( "http://www.mediawiki.org/xml/made-up-url" );
- $er = $r->execute();
- if ( $r instanceof PhpHttpRequest && version_compare( '5.2.10', phpversion(), '>' ) ) {
- $this->assertRegexp( "/HTTP request failed/", $er->getWikiText() );
- } else {
- $this->assertRegexp( "/404 Not Found/", $er->getWikiText() );
- }
- }
-
- function testFailureDefault() {
- Http::$httpEngine = false;
- $this->runHTTPFailureChecks();
- }
-
- function testFailurePhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = "php";
- $this->runHTTPFailureChecks();
- }
-
- function testFailureCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = "curl";
- $this->runHTTPFailureChecks();
- }
-
- /* ./phase3/includes/Import.php:1108: $data = Http::request( $method, $url ); */
- /* ./includes/Import.php:1124: $link = Title::newFromText( "$interwiki:Special:Export/$page" ); */
- /* ./includes/Import.php:1134: return ImportStreamSource::newFromURL( $url, "POST" ); */
- function runHTTPRequests( $proxy = null ) {
- $opt = array();
-
- if ( $proxy ) {
- $opt['proxy'] = $proxy;
- } elseif ( $proxy === false ) {
- $opt['noProxy'] = true;
- }
-
- /* no postData here because the only request I could find in code so far didn't have any */
- foreach ( $this->test_requesturl as $u ) {
- $r = Http::request( "POST", $u, $opt );
- $this->assertEquals( self::$content["POST $u"], "$r", "POST $u with " . Http::$httpEngine );
- }
- }
-
- function testRequestDefault() {
- Http::$httpEngine = false;
- $this->runHTTPRequests();
- }
-
- function testRequestPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = "php";
- $this->runHTTPRequests();
- }
-
- function testRequestCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = "curl";
- $this->runHTTPRequests();
- }
-
- function runHTTPGets( $proxy = null ) {
- $opt = array();
-
- if ( $proxy ) {
- $opt['proxy'] = $proxy;
- } elseif ( $proxy === false ) {
- $opt['noProxy'] = true;
- }
-
- foreach ( $this->test_geturl as $u ) {
- $r = Http::get( $u, 30, $opt ); /* timeout of 30s */
- $this->assertEquals( self::$content["GET $u"], "$r", "Get $u with " . Http::$httpEngine );
- }
- }
-
- function testGetDefault() {
- Http::$httpEngine = false;
- $this->runHTTPGets();
- }
-
- function testGetPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = "php";
- $this->runHTTPGets();
- }
-
- function testGetCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = "curl";
- $this->runHTTPGets();
- }
-
- function runHTTPPosts( $proxy = null ) {
- $opt = array();
-
- if ( $proxy ) {
- $opt['proxy'] = $proxy;
- } elseif ( $proxy === false ) {
- $opt['noProxy'] = true;
- }
-
- foreach ( $this->test_posturl as $u => $postData ) {
- $opt['postData'] = $postData;
- $r = Http::post( $u, $opt );
- $this->assertEquals( self::$content["POST $u => $postData"], "$r",
- "POST $u (postData=$postData) with " . Http::$httpEngine );
- }
- }
-
- function testPostDefault() {
- Http::$httpEngine = false;
- $this->runHTTPPosts();
- }
-
- function testPostPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = "php";
- $this->runHTTPPosts();
- }
-
- function testPostCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = "curl";
- $this->runHTTPPosts();
- }
-
- function runProxyRequests() {
- if ( !self::$has_proxy ) {
- $this->markTestIncomplete( "This test requires a proxy." );
- }
- $this->runHTTPGets( self::$proxy );
- $this->runHTTPPosts( self::$proxy );
- $this->runHTTPRequests( self::$proxy );
-
- // Set false here to do noProxy
- $this->runHTTPGets( false );
- $this->runHTTPPosts( false );
- $this->runHTTPRequests( false );
- }
-
- function testProxyDefault() {
- Http::$httpEngine = false;
- $this->runProxyRequests();
- }
-
- function testProxyPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = 'php';
- $this->runProxyRequests();
- }
-
- function testProxyCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = 'curl';
- $this->runProxyRequests();
- }
-
- function testIsLocalUrl() {
- }
-
- /* ./extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php:559: $user_agent = Http::userAgent(); */
- function testUserAgent() {
- }
-
- function testIsValidUrl() {
- }
-
/**
* @dataProvider cookieDomains
*/
- function testValidateCookieDomain( $expected, $domain, $origin=null ) {
+ function testValidateCookieDomain( $expected, $domain, $origin = null ) {
if ( $origin ) {
$ok = Cookie::validateCookieDomain( $domain, $origin );
$msg = "$domain against origin $origin";
@@ -329,7 +16,7 @@ class HttpTest extends MediaWikiTestCase {
}
$this->assertEquals( $expected, $ok, $msg );
}
-
+
function cookieDomains() {
return array(
array( false, "org"),
@@ -359,189 +46,11 @@ class HttpTest extends MediaWikiTestCase {
);
}
- function testSetCooke() {
- $c = new MockCookie( "name", "value",
- array(
- "domain" => "ac.th",
- "path" => "/path/",
- ) );
- $this->assertFalse( $c->canServeDomain( "ac.th" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => "example.com",
- "path" => "/path/",
- ) );
-
- $this->assertTrue( $c->canServeDomain( "example.com" ) );
- $this->assertFalse( $c->canServeDomain( "www.example.com" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/",
- ) );
-
- $this->assertFalse( $c->canServeDomain( "www.example.net" ) );
- $this->assertFalse( $c->canServeDomain( "example.com" ) );
- $this->assertTrue( $c->canServeDomain( "www.example.com" ) );
-
- $this->assertFalse( $c->canServePath( "/" ) );
- $this->assertFalse( $c->canServePath( "/bogus/path/" ) );
- $this->assertFalse( $c->canServePath( "/path" ) );
- $this->assertTrue( $c->canServePath( "/path/" ) );
-
- $this->assertTrue( $c->isUnExpired() );
-
- $this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.net" ) );
- $this->assertEquals( "", $c->serializeToHttpRequest( "/", "www.example.com" ) );
- $this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => "www.example.com",
- "path" => "/path/",
- ) );
- $this->assertFalse( $c->canServeDomain( "example.com" ) );
- $this->assertFalse( $c->canServeDomain( "www.example.net" ) );
- $this->assertTrue( $c->canServeDomain( "www.example.com" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/",
- "expires" => "-1 day",
- ) );
- $this->assertFalse( $c->isUnExpired() );
- $this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/",
- "expires" => "+1 day",
- ) );
- $this->assertTrue( $c->isUnExpired() );
- $this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
- }
-
- function testCookieJarSetCookie() {
- $cj = new CookieJar;
- $cj->setCookie( "name", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/",
- ) );
- $cj->setCookie( "name2", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/sub",
- ) );
- $cj->setCookie( "name3", "value",
- array(
- "domain" => ".example.com",
- "path" => "/",
- ) );
- $cj->setCookie( "name4", "value",
- array(
- "domain" => ".example.net",
- "path" => "/path/",
- ) );
- $cj->setCookie( "name5", "value",
- array(
- "domain" => ".example.net",
- "path" => "/path/",
- "expires" => "-1 day",
- ) );
-
- $this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
- $this->assertEquals( "name3=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
- $this->assertEquals( "name=value; name3=value", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $cj->setCookie( "name5", "value",
- array(
- "domain" => ".example.net",
- "path" => "/path/",
- "expires" => "+1 day",
- ) );
- $this->assertEquals( "name4=value; name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-
- $cj->setCookie( "name4", "value",
- array(
- "domain" => ".example.net",
- "path" => "/path/",
- "expires" => "-1 day",
- ) );
- $this->assertEquals( "name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
- }
-
- function testParseResponseHeader() {
- $cj = new CookieJar;
-
- $h[] = "Set-Cookie: name4=value; domain=.example.com; path=/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[0], "www.example.com" );
- $this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
-
- $h[] = "name4=value2; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[1], "www.example.com" );
- $this->assertEquals( "", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
- $this->assertEquals( "name4=value2", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $h[] = "name5=value3; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[2], "www.example.com" );
- $this->assertEquals( "name4=value2; name5=value3", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $h[] = "name6=value3; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[3], "www.example.com" );
- $this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-
- $h[] = "name6=value0; domain=.example.net; path=/path/; expires=Mon, 09-Dec-1999 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[4], "www.example.net" );
- $this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-
- $h[] = "name6=value4; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[5], "www.example.net" );
- $this->assertEquals( "name6=value4", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
- }
-
- function runCookieRequests() {
- $r = MWHttpRequest::factory( "http://www.php.net/manual", array( 'followRedirects' => true ) );
- $r->execute();
-
- $jar = $r->getCookieJar();
- $this->assertThat( $jar, $this->isInstanceOf( 'CookieJar' ) );
-
- $serialized = $jar->serializeToHttpRequest( "/search?q=test", "www.php.net" );
- $this->assertRegExp( '/\bCOUNTRY=[^=;]+/', $serialized );
- $this->assertRegExp( '/\bLAST_LANG=[^=;]+/', $serialized );
- $this->assertEquals( '', $jar->serializeToHttpRequest( "/search?q=test", "www.php.com" ) );
- }
-
- function testCookieRequestDefault() {
- Http::$httpEngine = false;
- $this->runCookieRequests();
- }
- function testCookieRequestPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = 'php';
- $this->runCookieRequests();
- }
- function testCookieRequestCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = 'curl';
- $this->runCookieRequests();
- }
-
/**
* Test Http::isValidURI()
- * @bug 27854 : Http::isValidURI is to lax
- *@dataProvider provideURI */
+ * @bug 27854 : Http::isValidURI is too lax
+ * @dataProvider provideURI
+ */
function testIsValidUri( $expect, $URI, $message = '' ) {
$this->assertEquals(
$expect,
@@ -567,7 +76,7 @@ class HttpTest extends MediaWikiTestCase {
array( false, '\\host\directory', 'CIFS share' ),
array( false, 'gopher://host/dir', 'Reject gopher scheme' ),
array( false, 'telnet://host', 'Reject telnet scheme' ),
-
+
# :\/\/ - double slashes
array( false, 'http//example.org', 'Reject missing colon in protocol' ),
array( false, 'http:/example.org', 'Reject missing slash in protocol' ),
@@ -615,4 +124,57 @@ class HttpTest extends MediaWikiTestCase {
);
}
+ /**
+ * Warning:
+ *
+ * These tests are for code that makes use of an artifact of how CURL
+ * handles header reporting on redirect pages, and will need to be
+ * rewritten when bug 29232 is taken care of (high-level handling of
+ * HTTP redirects).
+ */
+ function testRelativeRedirections() {
+ $h = new MWHttpRequestTester( 'http://oldsite/file.ext' );
+ # Forge a Location header
+ $h->setRespHeaders( 'location', array(
+ 'http://newsite/file.ext',
+ '/newfile.ext',
+ )
+ );
+ # Verify we correctly fix the Location
+ $this->assertEquals(
+ 'http://newsite/newfile.ext',
+ $h->getFinalUrl(),
+ "Relative file path Location: interpreted as full URL"
+ );
+
+ $h->setRespHeaders( 'location', array(
+ 'https://oldsite/file.ext'
+ )
+ );
+ $this->assertEquals(
+ 'https://oldsite/file.ext',
+ $h->getFinalUrl(),
+ "Location to the HTTPS version of the site"
+ );
+
+ $h->setRespHeaders( 'location', array(
+ '/anotherfile.ext',
+ 'http://anotherfile/hoster.ext',
+ 'https://anotherfile/hoster.ext'
+ )
+ );
+ $this->assertEquals(
+ 'https://anotherfile/hoster.ext',
+ $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!")
+ );
+ }
+}
+
+/**
+ * Class to let us overwrite MWHttpREquest respHeaders variable
+ */
+class MWHttpRequestTester extends MWHttpRequest {
+ function setRespHeaders( $name, $value ) {
+ $this->respHeaders[$name] = $value ;
+ }
}
diff --git a/tests/phpunit/includes/IPTest.php b/tests/phpunit/includes/IPTest.php
index c77dd852..4397b879 100644
--- a/tests/phpunit/includes/IPTest.php
+++ b/tests/phpunit/includes/IPTest.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* Tests for IP validity functions. Ported from /t/inc/IP.t by avar.
*/
@@ -43,20 +43,20 @@ class IPTest extends MediaWikiTestCase {
$this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
$this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
$this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
+
$this->assertTrue( IP::isIPv6( 'fc:100::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
-
+
$this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
$this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words ending with "::"' );
$this->assertFalse( IP::isIPv6( ':::' ) );
$this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
-
+
$this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
$this->assertTrue( IP::isIPv6( '::0' ) );
$this->assertTrue( IP::isIPv6( '::fc' ) );
@@ -66,14 +66,14 @@ class IPTest extends MediaWikiTestCase {
$this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
$this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
$this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
-
+
$this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
$this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
$this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
$this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
$this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
-
+
$this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
$this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
$this->assertTrue( IP::isIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ) );
@@ -83,7 +83,7 @@ class IPTest extends MediaWikiTestCase {
$this->assertTrue( IP::isIPv6( '2001::df'), 'IPv6 with "::" and 2 words' );
$this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df'), 'IPv6 with "::" and 5 words' );
$this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2'), 'IPv6 with "::" and 6 words' );
-
+
$this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
$this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
@@ -135,11 +135,11 @@ class IPTest extends MediaWikiTestCase {
$this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
$this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
$this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
+
$this->assertTrue( IP::isValid( 'fc:100::' ) );
$this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
$this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
-
+
$this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' );
$this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
$this->assertTrue( IP::isValid( '2001::df'), 'IPv6 with "::" and 2 words' );
@@ -147,7 +147,7 @@ class IPTest extends MediaWikiTestCase {
$this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2'), 'IPv6 with "::" and 6 words' );
$this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
$this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
-
+
$this->assertFalse( IP::isValid( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
$this->assertFalse( IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words ending with "::"' );
}
@@ -276,7 +276,7 @@ class IPTest extends MediaWikiTestCase {
foreach ( $private as $p ) {
$this->assertFalse( IP::isPublic( $p ), "$p is not a public IP address" );
}
- $public = array( '2001:5c0:1000:a::133', 'fc::3' );
+ $public = array( '2001:5c0:1000:a::133', 'fc::3', '00FC::' );
foreach ( $public as $p ) {
$this->assertTrue( IP::isPublic( $p ), "$p is a public IP address" );
}
@@ -332,7 +332,7 @@ class IPTest extends MediaWikiTestCase {
$this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) );
}
- /*
+ /**
* IP::parseCIDR() returns an array containing a signed IP address
* representing the network mask and the bit mask.
* @covers IP::parseCIDR
@@ -391,7 +391,7 @@ class IPTest extends MediaWikiTestCase {
}
/**
- * Issues there are most probably from IP::toHex() or IP::parseRange()
+ * Issues there are most probably from IP::toHex() or IP::parseRange()
* @covers IP::isInRange
* @dataProvider provideIPsAndRanges
*/
@@ -464,9 +464,9 @@ class IPTest extends MediaWikiTestCase {
*/
function testCombineHostAndPort( $expected, $input, $description ) {
list( $host, $port, $defaultPort ) = $input;
- $this->assertEquals(
- $expected,
- IP::combineHostAndPort( $host, $port, $defaultPort ),
+ $this->assertEquals(
+ $expected,
+ IP::combineHostAndPort( $host, $port, $defaultPort ),
$description );
}
diff --git a/tests/phpunit/includes/LocalFileTest.php b/tests/phpunit/includes/LocalFileTest.php
index e08d4d7e..5b26b89c 100644
--- a/tests/phpunit/includes/LocalFileTest.php
+++ b/tests/phpunit/includes/LocalFileTest.php
@@ -10,12 +10,21 @@ class LocalFileTest extends MediaWikiTestCase {
global $wgCapitalLinks;
$wgCapitalLinks = true;
+
$info = array(
- 'name' => 'test',
- 'directory' => '/testdir',
- 'url' => '/testurl',
- 'hashLevels' => 2,
+ 'name' => 'test',
+ 'directory' => '/testdir',
+ 'url' => '/testurl',
+ 'hashLevels' => 2,
'transformVia404' => false,
+ 'backend' => new FSFileBackend( array(
+ 'name' => 'local-backend',
+ 'lockManager' => 'fsLockManager',
+ '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 );
@@ -44,17 +53,17 @@ class LocalFileTest extends MediaWikiTestCase {
}
function testGetArchivePath() {
- $this->assertEquals( '/testdir/archive', $this->file_hl0->getArchivePath() );
- $this->assertEquals( '/testdir/archive/a/a2', $this->file_hl2->getArchivePath() );
- $this->assertEquals( '/testdir/archive/!', $this->file_hl0->getArchivePath( '!' ) );
- $this->assertEquals( '/testdir/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() );
+ $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( '!' ) );
}
function testGetThumbPath() {
- $this->assertEquals( '/testdir/thumb/Test!', $this->file_hl0->getThumbPath() );
- $this->assertEquals( '/testdir/thumb/a/a2/Test!', $this->file_hl2->getThumbPath() );
- $this->assertEquals( '/testdir/thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) );
- $this->assertEquals( '/testdir/thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) );
+ $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' ) );
}
function testGetArchiveUrl() {
diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php
index 462afc24..6b231fc5 100644
--- a/tests/phpunit/includes/MWNamespaceTest.php
+++ b/tests/phpunit/includes/MWNamespaceTest.php
@@ -1,7 +1,7 @@
<?php
/**
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
* @file
*/
@@ -39,40 +39,55 @@ class MWNamespaceTest extends MediaWikiTestCase {
/**
* Please make sure to change testIsTalk() if you change the assertions below
*/
- public function testIsMain() {
+ public function testIsSubject() {
// Special namespaces
- $this->assertTrue( MWNamespace::isMain( NS_MEDIA ) );
- $this->assertTrue( MWNamespace::isMain( NS_SPECIAL ) );
+ $this->assertIsSubject( NS_MEDIA );
+ $this->assertIsSubject( NS_SPECIAL );
// Subject pages
- $this->assertTrue( MWNamespace::isMain( NS_MAIN ) );
- $this->assertTrue( MWNamespace::isMain( NS_USER ) );
- $this->assertTrue( MWNamespace::isMain( 100 ) ); # user defined
+ $this->assertIsSubject( NS_MAIN );
+ $this->assertIsSubject( NS_USER );
+ $this->assertIsSubject( 100 ); # user defined
// Talk pages
- $this->assertFalse( MWNamespace::isMain( NS_TALK ) );
- $this->assertFalse( MWNamespace::isMain( NS_USER_TALK ) );
- $this->assertFalse( MWNamespace::isMain( 101 ) ); # user defined
+ $this->assertIsNotSubject( NS_TALK );
+ $this->assertIsNotSubject( NS_USER_TALK );
+ $this->assertIsNotSubject( 101 ); # user defined
+
+ // Back compat
+ $this->assertTrue( MWNamespace::isMain( NS_MAIN ) == MWNamespace::isSubject( NS_MAIN ) );
+ $this->assertTrue( MWNamespace::isMain( NS_USER_TALK ) == MWNamespace::isSubject( NS_USER_TALK ) );
}
/**
- * Reverse of testIsMain().
- * Please update testIsMain() if you change assertions below
+ * Reverse of testIsSubject().
+ * Please update testIsSubject() if you change assertions below
*/
public function testIsTalk() {
// Special namespaces
- $this->assertFalse( MWNamespace::isTalk( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::isTalk( NS_SPECIAL ) );
+ $this->assertIsNotTalk( NS_MEDIA );
+ $this->assertIsNotTalk( NS_SPECIAL );
// Subject pages
- $this->assertFalse( MWNamespace::isTalk( NS_MAIN ) );
- $this->assertFalse( MWNamespace::isTalk( NS_USER ) );
- $this->assertFalse( MWNamespace::isTalk( 100 ) ); # user defined
+ $this->assertIsNotTalk( NS_MAIN );
+ $this->assertIsNotTalk( NS_USER );
+ $this->assertIsNotTalk( 100 ); # user defined
// Talk pages
- $this->assertTrue( MWNamespace::isTalk( NS_TALK ) );
- $this->assertTrue( MWNamespace::isTalk( NS_USER_TALK ) );
- $this->assertTrue( MWNamespace::isTalk( 101 ) ); # user defined
+ $this->assertIsTalk( NS_TALK );
+ $this->assertIsTalk( NS_USER_TALK );
+ $this->assertIsTalk( 101 ); # user defined
+ }
+
+ /**
+ */
+ public function testGetSubject() {
+ // Special namespaces are their own subjects
+ $this->assertEquals( NS_MEDIA, MWNamespace::getSubject( NS_MEDIA ) );
+ $this->assertEquals( NS_SPECIAL, MWNamespace::getSubject( NS_SPECIAL ) );
+
+ $this->assertEquals( NS_MAIN, MWNamespace::getSubject( NS_TALK ) );
+ $this->assertEquals( NS_USER, MWNamespace::getSubject( NS_USER_TALK ) );
}
/**
@@ -82,6 +97,9 @@ class MWNamespaceTest extends MediaWikiTestCase {
*/
public function testGetTalk() {
$this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_MAIN ) );
+ $this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_TALK ) );
+ $this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER ) );
+ $this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER_TALK ) );
}
/**
@@ -93,7 +111,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertNull( MWNamespace::getTalk( NS_MEDIA ) );
}
- /**
+ /**
* Exceptions with getTalk()
* NS_SPECIAL does not have talk pages. MediaWiki raise an exception for them.
* @expectedException MWException
@@ -108,7 +126,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
* the function testGetAssociatedExceptions()
*/
public function testGetAssociated() {
- $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) );
+ $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) );
$this->assertEquals( NS_MAIN, MWNamespace::getAssociated( NS_TALK ) );
}
@@ -131,80 +149,122 @@ class MWNamespaceTest extends MediaWikiTestCase {
}
/**
- */
- public function testGetSubject() {
- // Special namespaces are their own subjects
- $this->assertEquals( NS_MEDIA, MWNamespace::getSubject( NS_MEDIA ) );
- $this->assertEquals( NS_SPECIAL, MWNamespace::getSubject( NS_SPECIAL ) );
-
- $this->assertEquals( NS_MAIN, MWNamespace::getSubject( NS_TALK ) );
- $this->assertEquals( NS_USER, MWNamespace::getSubject( NS_USER_TALK ) );
- }
-
- /**
* @todo Implement testExists().
*/
+/*
public function testExists() {
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
);
}
+*/
+
+ /**
+ * Test MWNamespace::equals
+ * Note if we add a namespace registration system with keys like 'MAIN'
+ * we should add tests here for equivilance on things like 'MAIN' == 0
+ * and 'MAIN' == NS_MAIN.
+ */
+ public function testEquals() {
+ $this->assertTrue( MWNamespace::equals( NS_MAIN, NS_MAIN ) );
+ $this->assertTrue( MWNamespace::equals( NS_MAIN, 0 ) ); // In case we make NS_MAIN 'MAIN'
+ $this->assertTrue( MWNamespace::equals( NS_USER, NS_USER ) );
+ $this->assertTrue( MWNamespace::equals( NS_USER, 2 ) );
+ $this->assertTrue( MWNamespace::equals( NS_USER_TALK, NS_USER_TALK ) );
+ $this->assertTrue( MWNamespace::equals( NS_SPECIAL, NS_SPECIAL ) );
+ $this->assertFalse( MWNamespace::equals( NS_MAIN, NS_TALK ) );
+ $this->assertFalse( MWNamespace::equals( NS_USER, NS_USER_TALK ) );
+ $this->assertFalse( MWNamespace::equals( NS_PROJECT, NS_TEMPLATE ) );
+ }
+
+ /**
+ * Test MWNamespace::subjectEquals
+ */
+ public function testSubjectEquals() {
+ $this->assertSameSubject( NS_MAIN, NS_MAIN );
+ $this->assertSameSubject( NS_MAIN, 0 ); // In case we make NS_MAIN 'MAIN'
+ $this->assertSameSubject( NS_USER, NS_USER );
+ $this->assertSameSubject( NS_USER, 2 );
+ $this->assertSameSubject( NS_USER_TALK, NS_USER_TALK );
+ $this->assertSameSubject( NS_SPECIAL, NS_SPECIAL );
+ $this->assertSameSubject( NS_MAIN, NS_TALK );
+ $this->assertSameSubject( NS_USER, NS_USER_TALK );
+
+ $this->assertDifferentSubject( NS_PROJECT, NS_TEMPLATE );
+ $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN );
+ }
+
+ public function testSpecialAndMediaAreDifferentSubjects() {
+ $this->assertDifferentSubject(
+ NS_MEDIA, NS_SPECIAL,
+ "NS_MEDIA and NS_SPECIAL are different subject namespaces"
+ );
+ $this->assertDifferentSubject(
+ NS_SPECIAL, NS_MEDIA,
+ "NS_SPECIAL and NS_MEDIA are different subject namespaces"
+ );
+
+ }
/**
* @todo Implement testGetCanonicalNamespaces().
*/
+/*
public function testGetCanonicalNamespaces() {
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
);
}
-
+*/
/**
* @todo Implement testGetCanonicalName().
*/
+/*
public function testGetCanonicalName() {
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
);
}
-
+*/
/**
* @todo Implement testGetCanonicalIndex().
*/
+/*
public function testGetCanonicalIndex() {
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
);
}
-
+*/
/**
* @todo Implement testGetValidNamespaces().
*/
+/*
public function testGetValidNamespaces() {
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
);
}
-
+*/
/**
*/
public function testCanTalk() {
- $this->assertFalse( MWNamespace::canTalk( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::canTalk( NS_SPECIAL ) );
+ $this->assertCanNotTalk( NS_MEDIA );
+ $this->assertCanNotTalk( NS_SPECIAL );
- $this->assertTrue( MWNamespace::canTalk( NS_MAIN ) );
- $this->assertTrue( MWNamespace::canTalk( NS_TALK ) );
- $this->assertTrue( MWNamespace::canTalk( NS_USER ) );
- $this->assertTrue( MWNamespace::canTalk( NS_USER_TALK ) );
+ $this->assertCanTalk( NS_MAIN );
+ $this->assertCanTalk( NS_TALK );
+ $this->assertCanTalk( NS_USER );
+ $this->assertCanTalk( NS_USER_TALK );
// User defined namespaces
- $this->assertTrue( MWNamespace::canTalk( 100 ) );
- $this->assertTrue( MWNamespace::canTalk( 101 ) );
+ $this->assertCanTalk( 100 );
+ $this->assertCanTalk( 101 );
}
/**
@@ -212,16 +272,47 @@ class MWNamespaceTest extends MediaWikiTestCase {
public function testIsContent() {
// NS_MAIN is a content namespace per DefaultSettings.php
// and per function definition.
- $this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
+ $this->assertIsContent( NS_MAIN );
+
+ global $wgContentNamespaces;
+
+ $saved = $wgContentNamespaces;
+
+ $wgContentNamespaces[] = NS_MAIN;
+ $this->assertIsContent( NS_MAIN );
// Other namespaces which are not expected to be content
- $this->assertFalse( MWNamespace::isContent( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::isContent( NS_SPECIAL ) );
- $this->assertFalse( MWNamespace::isContent( NS_TALK ) );
- $this->assertFalse( MWNamespace::isContent( NS_USER ) );
- $this->assertFalse( MWNamespace::isContent( NS_CATEGORY ) );
- // User defined namespace:
- $this->assertFalse( MWNamespace::isContent( 100 ) );
+ if ( isset( $wgContentNamespaces[NS_MEDIA] ) ) {
+ unset( $wgContentNamespaces[NS_MEDIA] );
+ }
+ $this->assertIsNotContent( NS_MEDIA );
+
+ if ( isset( $wgContentNamespaces[NS_SPECIAL] ) ) {
+ unset( $wgContentNamespaces[NS_SPECIAL] );
+ }
+ $this->assertIsNotContent( NS_SPECIAL );
+
+ if ( isset( $wgContentNamespaces[NS_TALK] ) ) {
+ unset( $wgContentNamespaces[NS_TALK] );
+ }
+ $this->assertIsNotContent( NS_TALK );
+
+ if ( isset( $wgContentNamespaces[NS_USER] ) ) {
+ unset( $wgContentNamespaces[NS_USER] );
+ }
+ $this->assertIsNotContent( NS_USER );
+
+ if ( isset( $wgContentNamespaces[NS_CATEGORY] ) ) {
+ unset( $wgContentNamespaces[NS_CATEGORY] );
+ }
+ $this->assertIsNotContent( NS_CATEGORY );
+
+ if ( isset( $wgContentNamespaces[100] ) ) {
+ unset( $wgContentNamespaces[100] );
+ }
+ $this->assertIsNotContent( 100 );
+
+ $wgContentNamespaces = $saved;
}
/**
@@ -231,47 +322,47 @@ class MWNamespaceTest extends MediaWikiTestCase {
public function testIsContentWithAdditionsInWgContentNamespaces() {
// NS_MAIN is a content namespace per DefaultSettings.php
// and per function definition.
- $this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
+ $this->assertIsContent( NS_MAIN );
// Tests that user defined namespace #252 is not content:
- $this->assertFalse( MWNamespace::isContent( 252 ) );
+ $this->assertIsNotContent( 252 );
# @todo FIXME: Is global saving really required for PHPUnit?
// Bless namespace # 252 as a content namespace
global $wgContentNamespaces;
$savedGlobal = $wgContentNamespaces;
$wgContentNamespaces[] = 252;
- $this->assertTrue( MWNamespace::isContent( 252 ) );
+ $this->assertIsContent( 252 );
// Makes sure NS_MAIN was not impacted
- $this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
+ $this->assertIsContent( NS_MAIN );
// Restore global
$wgContentNamespaces = $savedGlobal;
// Verify namespaces after global restauration
- $this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
- $this->assertFalse( MWNamespace::isContent( 252 ) );
+ $this->assertIsContent( NS_MAIN );
+ $this->assertIsNotContent( 252 );
}
public function testIsWatchable() {
// Specials namespaces are not watchable
- $this->assertFalse( MWNamespace::isWatchable( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::isWatchable( NS_SPECIAL ) );
+ $this->assertIsNotWatchable( NS_MEDIA );
+ $this->assertIsNotWatchable( NS_SPECIAL );
// Core defined namespaces are watchables
- $this->assertTrue( MWNamespace::isWatchable( NS_MAIN ) );
- $this->assertTrue( MWNamespace::isWatchable( NS_TALK ) );
+ $this->assertIsWatchable( NS_MAIN );
+ $this->assertIsWatchable( NS_TALK );
// Additional, user defined namespaces are watchables
- $this->assertTrue( MWNamespace::isWatchable( 100 ) );
- $this->assertTrue( MWNamespace::isWatchable( 101 ) );
+ $this->assertIsWatchable( 100 );
+ $this->assertIsWatchable( 101 );
}
public function testHasSubpages() {
// Special namespaces:
- $this->assertFalse( MWNamespace::hasSubpages( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::hasSubpages( NS_SPECIAL ) );
+ $this->assertHasNotSubpages( NS_MEDIA );
+ $this->assertHasNotSubpages( NS_SPECIAL );
// namespaces without subpages
# save up global
@@ -282,12 +373,12 @@ class MWNamespaceTest extends MediaWikiTestCase {
unset( $wgNamespacesWithSubpages[NS_MAIN] );
}
- $this->assertFalse( MWNamespace::hasSubpages( NS_MAIN ) );
+ $this->assertHasNotSubpages( NS_MAIN );
$wgNamespacesWithSubpages[NS_MAIN] = true;
- $this->assertTrue( MWNamespace::hasSubpages( NS_MAIN ) );
+ $this->assertHasSubpages( NS_MAIN );
$wgNamespacesWithSubpages[NS_MAIN] = false;
- $this->assertFalse( MWNamespace::hasSubpages( NS_MAIN ) );
+ $this->assertHasNotSubpages( NS_MAIN );
# restore global
if( $saved !== null ) {
@@ -295,9 +386,9 @@ class MWNamespaceTest extends MediaWikiTestCase {
}
// Some namespaces with subpages
- $this->assertTrue( MWNamespace::hasSubpages( NS_TALK ) );
- $this->assertTrue( MWNamespace::hasSubpages( NS_USER ) );
- $this->assertTrue( MWNamespace::hasSubpages( NS_USER_TALK ) );
+ $this->assertHasSubpages( NS_TALK );
+ $this->assertHasSubpages( NS_USER );
+ $this->assertHasSubpages( NS_USER_TALK );
}
/**
@@ -311,6 +402,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
global $wgContentNamespaces;
+ $saved = $wgContentNamespaces;
# test !is_array( $wgcontentNamespaces )
$wgContentNamespaces = '';
$this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
@@ -330,7 +422,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertEquals(
array( NS_MAIN, NS_USER, NS_CATEGORY ),
MWNamespace::getcontentNamespaces(),
- 'NS_MAIN is forced in wgContentNamespaces even if unwanted'
+ 'NS_MAIN is forced in $wgContentNamespaces even if unwanted'
);
# test other cases, return $wgcontentNamespaces as is
@@ -346,6 +438,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
MWNamespace::getcontentNamespaces()
);
+ $wgContentNamespaces = $saved;
}
/**
@@ -361,14 +454,14 @@ class MWNamespaceTest extends MediaWikiTestCase {
);
// Boths are capitalized by default
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIA ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_FILE ) );
+ $this->assertIsCapitalized( NS_MEDIA );
+ $this->assertIsCapitalized( NS_FILE );
// Always capitalized namespaces
// @see MWNamespace::$alwaysCapitalizedNamespaces
- $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+ $this->assertIsCapitalized( NS_SPECIAL );
+ $this->assertIsCapitalized( NS_USER );
+ $this->assertIsCapitalized( NS_MEDIAWIKI );
}
/**
@@ -389,17 +482,17 @@ class MWNamespaceTest extends MediaWikiTestCase {
$savedGlobal = $wgCapitalLinks;
$wgCapitalLinks = true;
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) );
+ $this->assertIsCapitalized( NS_PROJECT );
+ $this->assertIsCapitalized( NS_PROJECT_TALK );
$wgCapitalLinks = false;
// hardcoded namespaces (see above function) are still capitalized:
- $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+ $this->assertIsCapitalized( NS_SPECIAL );
+ $this->assertIsCapitalized( NS_USER );
+ $this->assertIsCapitalized( NS_MEDIAWIKI );
// setting is correctly applied
- $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT ) );
- $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT_TALK ) );
+ $this->assertIsNotCapitalized( NS_PROJECT );
+ $this->assertIsNotCapitalized( NS_PROJECT_TALK );
// reset global state:
$wgCapitalLinks = $savedGlobal;
@@ -417,28 +510,28 @@ class MWNamespaceTest extends MediaWikiTestCase {
$savedGlobal = $wgCapitalLinkOverrides;
// Test default settings
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) );
+ $this->assertIsCapitalized( NS_PROJECT );
+ $this->assertIsCapitalized( NS_PROJECT_TALK );
// hardcoded namespaces (see above function) are capitalized:
- $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+ $this->assertIsCapitalized( NS_SPECIAL );
+ $this->assertIsCapitalized( NS_USER );
+ $this->assertIsCapitalized( NS_MEDIAWIKI );
// Hardcoded namespaces remains capitalized
$wgCapitalLinkOverrides[NS_SPECIAL] = false;
$wgCapitalLinkOverrides[NS_USER] = false;
$wgCapitalLinkOverrides[NS_MEDIAWIKI] = false;
- $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+ $this->assertIsCapitalized( NS_SPECIAL );
+ $this->assertIsCapitalized( NS_USER );
+ $this->assertIsCapitalized( NS_MEDIAWIKI );
$wgCapitalLinkOverrides = $savedGlobal;
$wgCapitalLinkOverrides[NS_PROJECT] = false;
- $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT ) );
+ $this->assertIsNotCapitalized( NS_PROJECT );
$wgCapitalLinkOverrides[NS_PROJECT] = true ;
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
+ $this->assertIsCapitalized( NS_PROJECT );
unset( $wgCapitalLinkOverrides[NS_PROJECT] );
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
+ $this->assertIsCapitalized( NS_PROJECT );
// reset global state:
$wgCapitalLinkOverrides = $savedGlobal;
@@ -456,5 +549,45 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK ) );
}
+
+ ####### HELPERS ###########################################################
+ function __call( $method, $args ) {
+ // Call the real method if it exists
+ if( method_exists($this, $method ) ) {
+ return $this->$method( $args );
+ }
+
+ if( preg_match( '/^assert(Has|Is|Can)(Not|)(Subject|Talk|Watchable|Content|Subpages|Capitalized)$/', $method, $m ) ) {
+ # Interprets arguments:
+ $ns = $args[0];
+ $msg = isset($args[1]) ? $args[1] : " dummy message";
+
+ # Forge the namespace constant name:
+ if( $ns === 0 ) {
+ $ns_name = "NS_MAIN";
+ } else {
+ $ns_name = "NS_" . strtoupper( MWNamespace::getCanonicalName( $ns ) );
+ }
+ # ... and the MWNamespace method name
+ $nsMethod = strtolower( $m[1] ) . $m[3];
+
+ $expect = ($m[2] === '');
+ $expect_name = $expect ? 'TRUE' : 'FALSE';
+
+ return $this->assertEquals( $expect,
+ MWNamespace::$nsMethod( $ns, $msg ),
+ "MWNamespace::$nsMethod( $ns_name ) should returns $expect_name"
+ );
+ }
+
+ throw new Exception( __METHOD__ . " could not find a method named $method\n" );
+ }
+
+ function assertSameSubject( $ns1, $ns2, $msg = '' ) {
+ $this->assertTrue( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) );
+ }
+ function assertDifferentSubject( $ns1, $ns2, $msg = '' ) {
+ $this->assertFalse( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) );
+ }
}
diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php
index 45c02bbe..295b6d74 100644
--- a/tests/phpunit/includes/MessageTest.php
+++ b/tests/phpunit/includes/MessageTest.php
@@ -47,7 +47,7 @@ class MessageTest extends MediaWikiLangTestCase {
$this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg disabled' );
$wgForceUIMsgAsContentMsg['testInContentLanguage'] = 'mainpage';
$this->assertEquals( 'Accueil', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg enabled' );
-
+
/* Restore globals */
$wgLang = $oldLang;
unset( $wgForceUIMsgAsContentMsg['testInContentLanguage'] );
diff --git a/tests/phpunit/includes/ParserOptionsTest.php b/tests/phpunit/includes/ParserOptionsTest.php
index 58c89146..59c955fe 100644
--- a/tests/phpunit/includes/ParserOptionsTest.php
+++ b/tests/phpunit/includes/ParserOptionsTest.php
@@ -6,10 +6,9 @@ class ParserOptionsTest extends MediaWikiTestCase {
private $pcache;
function setUp() {
- ParserTest::setUp(); //reuse setup from parser tests
global $wgContLang, $wgUser, $wgLanguageCode;
$wgContLang = Language::factory( $wgLanguageCode );
- $this->popts = new ParserOptions( $wgUser );
+ $this->popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
$this->pcache = ParserCache::singleton();
}
@@ -26,11 +25,11 @@ class ParserOptionsTest extends MediaWikiTestCase {
$wgUseDynamicDates = true;
$title = Title::newFromText( "Some test article" );
- $article = new Article( $title );
+ $page = WikiPage::factory( $title );
- $pcacheKeyBefore = $this->pcache->getKey( $article, $this->popts );
+ $pcacheKeyBefore = $this->pcache->getKey( $page, $this->popts );
$this->assertNotNull( $this->popts->getDateFormat() );
- $pcacheKeyAfter = $this->pcache->getKey( $article, $this->popts );
+ $pcacheKeyAfter = $this->pcache->getKey( $page, $this->popts );
$this->assertEquals( $pcacheKeyBefore, $pcacheKeyAfter );
}
}
diff --git a/tests/phpunit/includes/PathRouterTest.php b/tests/phpunit/includes/PathRouterTest.php
new file mode 100644
index 00000000..f6274584
--- /dev/null
+++ b/tests/phpunit/includes/PathRouterTest.php
@@ -0,0 +1,254 @@
+<?php
+/**
+ * Tests for the PathRouter parsing
+ */
+
+class PathRouterTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $router = new PathRouter;
+ $router->add("/wiki/$1");
+ $this->basicRouter = $router;
+ }
+
+ /**
+ * Test basic path parsing
+ */
+ public function testBasic() {
+ $matches = $this->basicRouter->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+ }
+
+ /**
+ * Test loose path auto-$1
+ */
+ public function testLoose() {
+ $router = new PathRouter;
+ $router->add("/"); # Should be the same as "/$1"
+ $matches = $router->parse( "/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+
+ $router = new PathRouter;
+ $router->add("/wiki"); # Should be the same as /wiki/$1
+ $matches = $router->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+
+ $router = new PathRouter;
+ $router->add("/wiki/"); # Should be the same as /wiki/$1
+ $matches = $router->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+ }
+
+ /**
+ * Test to ensure that path is based on specifity, not order
+ */
+ public function testOrder() {
+ $router = new PathRouter;
+ $router->add("/$1");
+ $router->add("/a/$1");
+ $router->add("/b/$1");
+ $matches = $router->parse( "/a/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+
+ $router = new PathRouter;
+ $router->add("/b/$1");
+ $router->add("/a/$1");
+ $router->add("/$1");
+ $matches = $router->parse( "/a/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+ }
+
+ /**
+ * Test the handling of key based arrays with a url parameter
+ */
+ public function testKeyParameter() {
+ $router = new PathRouter;
+ $router->add( array( 'edit' => "/edit/$1" ), array( 'action' => '$key' ) );
+ $matches = $router->parse( "/edit/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo", 'action' => 'edit' ) );
+ }
+
+ /**
+ * Test the handling of $2 inside paths
+ */
+ public function testAdditionalParameter() {
+ // Basic $2
+ $router = new PathRouter;
+ $router->add( '/$2/$1', array( 'test' => '$2' ) );
+ $matches = $router->parse( "/asdf/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo", 'test' => 'asdf' ) );
+ }
+
+ /**
+ * Test additional restricted value parameter
+ */
+ public function testRestrictedValue() {
+ $router = new PathRouter;
+ $router->add( '/$2/$1',
+ array( 'test' => '$2' ),
+ array( '$2' => array( 'a', 'b' ) )
+ );
+ $router->add( '/$2/$1',
+ array( 'test2' => '$2' ),
+ array( '$2' => 'c' )
+ );
+ $router->add( '/$1' );
+
+ $matches = $router->parse( "/asdf/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "asdf/Foo" ) );
+
+ $matches = $router->parse( "/a/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo", 'test' => 'a' ) );
+
+ $matches = $router->parse( "/c/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo", 'test2' => 'c' ) );
+ }
+
+ public function callbackForTest( &$matches, $data ) {
+ $matches['x'] = $data['$1'];
+ $matches['foo'] = $data['foo'];
+ }
+
+ public function testCallback() {
+ $router = new PathRouter;
+ $router->add( "/$1",
+ array( 'a' => 'b', 'data:foo' => 'bar' ),
+ array( 'callback' => array( $this, 'callbackForTest' ) )
+ );
+ $matches = $router->parse( '/Foo' );
+ $this->assertEquals( $matches, array(
+ 'title' => "Foo",
+ 'x' => 'Foo',
+ 'a' => 'b',
+ 'foo' => 'bar'
+ ) );
+ }
+
+ /**
+ * Test to ensure that matches are not made if a parameter expects nonexistent input
+ */
+ public function testFail() {
+ $router = new PathRouter;
+ $router->add( "/wiki/$1", array( 'title' => "$1$2" ) );
+ $matches = $router->parse( "/wiki/A" );
+ $this->assertEquals( array(), $matches );
+ }
+
+ /**
+ * Test to ensure weight of paths is handled correctly
+ */
+ public function testWeight() {
+ $router = new PathRouter;
+ $router->addStrict( "/Bar", array( 'ping' => 'pong' ) );
+ $router->add( "/asdf-$1", array( 'title' => 'qwerty-$1' ) );
+ $router->add( "/$1" );
+ $router->add( "/qwerty-$1", array( 'title' => 'asdf-$1' ) );
+ $router->addStrict( "/Baz", array( 'marco' => 'polo' ) );
+ $router->add( "/a/$1" );
+ $router->add( "/asdf/$1" );
+ $router->add( "/$2/$1", array( 'unrestricted' => '$2' ) );
+ $router->add( array( 'qwerty' => "/qwerty/$1" ), array( 'qwerty' => '$key' ) );
+ $router->add( "/$2/$1", array( 'restricted-to-y' => '$2' ), array( '$2' => 'y' ) );
+
+ foreach( array(
+ "/Foo" => array( 'title' => "Foo" ),
+ "/Bar" => array( 'ping' => 'pong' ),
+ "/Baz" => array( 'marco' => 'polo' ),
+ "/asdf-foo" => array( 'title' => "qwerty-foo" ),
+ "/qwerty-bar" => array( 'title' => "asdf-bar" ),
+ "/a/Foo" => array( 'title' => "Foo" ),
+ "/asdf/Foo" => array( 'title' => "Foo" ),
+ "/qwerty/Foo" => array( 'title' => "Foo", 'qwerty' => 'qwerty' ),
+ "/baz/Foo" => array( 'title' => "Foo", 'unrestricted' => 'baz' ),
+ "/y/Foo" => array( 'title' => "Foo", 'restricted-to-y' => 'y' ),
+ ) as $path => $result ) {
+ $this->assertEquals( $router->parse( $path ), $result );
+ }
+ }
+
+ /**
+ * Make sure the router handles titles like Special:Recentchanges correctly
+ */
+ public function testSpecial() {
+ $matches = $this->basicRouter->parse( "/wiki/Special:Recentchanges" );
+ $this->assertEquals( $matches, array( 'title' => "Special:Recentchanges" ) );
+ }
+
+ /**
+ * Make sure the router decodes urlencoding properly
+ */
+ public function testUrlencoding() {
+ $matches = $this->basicRouter->parse( "/wiki/Title_With%20Space" );
+ $this->assertEquals( $matches, array( 'title' => "Title_With Space" ) );
+ }
+
+ public function dataRegexpChars() {
+ return array(
+ array( "$" ),
+ array( "$1" ),
+ array( "\\" ),
+ array( "\\$1" ),
+ );
+ }
+
+ /**
+ * Make sure the router doesn't break on special characters like $ used in regexp replacements
+ * @dataProvider dataRegexpChars
+ */
+ public function testRegexpChars( $char ) {
+ $matches = $this->basicRouter->parse( "/wiki/$char" );
+ $this->assertEquals( $matches, array( 'title' => "$char" ) );
+ }
+
+ /**
+ * Make sure the router handles characters like +&() properly
+ */
+ public function testCharacters() {
+ $matches = $this->basicRouter->parse( "/wiki/Plus+And&Dollar\\Stuff();[]{}*" );
+ $this->assertEquals( $matches, array( 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ) );
+ }
+
+ /**
+ * Make sure the router handles unicode characters correctly
+ * @depends testSpecial
+ * @depends testUrlencoding
+ * @depends testCharacters
+ */
+ public function testUnicode() {
+ $matches = $this->basicRouter->parse( "/wiki/Spécial:Modifications_récentes" );
+ $this->assertEquals( $matches, array( 'title' => "Spécial:Modifications_récentes" ) );
+
+ $matches = $this->basicRouter->parse( "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes" );
+ $this->assertEquals( $matches, array( 'title' => "Spécial:Modifications_récentes" ) );
+ }
+
+ /**
+ * Ensure the router doesn't choke on long paths.
+ */
+ public function testLength() {
+ $matches = $this->basicRouter->parse( "/wiki/Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum." );
+ $this->assertEquals( $matches, array( 'title' => "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum." ) );
+ }
+
+
+ /**
+ * Ensure that the php passed site of parameter values are not urldecoded
+ */
+ public function testPatternUrlencoding() {
+ $router = new PathRouter;
+ $router->add( "/wiki/$1", array( 'title' => '%20:$1' ) );
+ $matches = $router->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => '%20:Foo' ) );
+ }
+
+ /**
+ * Ensure that raw parameter values do not have any variable replacements or urldecoding
+ */
+ public function testRawParamValue() {
+ $router = new PathRouter;
+ $router->add( "/wiki/$1", array( 'title' => array( 'value' => 'bar%20$1' ) ) );
+ $matches = $router->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => 'bar%20$1' ) );
+ }
+
+}
diff --git a/tests/phpunit/includes/Providers.php b/tests/phpunit/includes/Providers.php
index 02898673..f451f8a0 100644
--- a/tests/phpunit/includes/Providers.php
+++ b/tests/phpunit/includes/Providers.php
@@ -2,8 +2,8 @@
/**
* Generic providers for the MediaWiki PHPUnit test suite
*
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
* @file
*/
diff --git a/tests/phpunit/includes/ResourceLoaderTest.php b/tests/phpunit/includes/ResourceLoaderTest.php
index 30a69c5e..ab704839 100644
--- a/tests/phpunit/includes/ResourceLoaderTest.php
+++ b/tests/phpunit/includes/ResourceLoaderTest.php
@@ -1,6 +1,6 @@
<?php
-class ResourceLoaderTest extends PHPUnit_Framework_TestCase {
+class ResourceLoaderTest extends MediaWikiTestCase {
protected static $resourceLoaderRegisterModulesHook;
diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php
index 40d6cf77..b76aa5c7 100644
--- a/tests/phpunit/includes/SanitizerTest.php
+++ b/tests/phpunit/includes/SanitizerTest.php
@@ -109,5 +109,48 @@ class SanitizerTest extends MediaWikiTestCase {
$this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&amp;&quot;' ), array( 'foo' => '&"' ), 'Special chars can be provided as entities' );
$this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&foobar;' ), array( 'foo' => '&foobar;' ), 'Entity-like items are accepted' );
}
+
+ function testDeprecatedAttributes() {
+ $GLOBALS['wgCleanupPresentationalAttributes'] = true;
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' style="clear: left;"', 'Deprecated attributes are converted to styles when enabled.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="all"', 'br' ), ' style="clear: both;"', 'clear=all is converted to clear: both; not clear: all;' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'CLEAR="ALL"', 'br' ), ' style="clear: both;"', 'clear=ALL is not treated differently from clear=all' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'width="100"', 'td' ), ' style="width: 100px;"', 'Numeric sizes use pixels instead of numbers.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'width="100%"', 'td' ), ' style="width: 100%;"', 'Units are allowed in sizes.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'WIDTH="100%"', 'td' ), ' style="width: 100%;"', 'Uppercase WIDTH is treated as lowercase width.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'WiDTh="100%"', 'td' ), ' style="width: 100%;"', 'Mixed case does not break WiDTh.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute is output as white-space: nowrap; not something else.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap=""', 'td' ), ' style="white-space: nowrap;"', 'nowrap="" is considered true, not false' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'NOWRAP="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when uppercase.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'NoWrAp="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when mixed-case.' );
+ $GLOBALS['wgCleanupPresentationalAttributes'] = false;
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' clear="left"', 'Deprecated attributes are not converted to styles when enabled.' );
+ }
+
+ /**
+ * @dataProvider provideCssCommentsFixtures
+ */
+ function testCssCommentsChecking( $expected, $css, $message = '' ) {
+ $this->assertEquals(
+ $expected,
+ Sanitizer::checkCss( $css ),
+ $message
+ );
+ }
+
+ function provideCssCommentsFixtures() {
+ /** array( <expected>, <css>, [message] ) */
+ return array(
+ array( ' ', '/**/' ),
+ array( ' ', '/****/' ),
+ array( ' ', '/* comment */' ),
+ array( ' ', "\\2f\\2a foo \\2a\\2f",
+ 'Backslash-escaped comments must be stripped (bug 28450)' ),
+ array( '', '/* unfinished comment structure',
+ 'Remove anything after a comment-start token' ),
+ array( '', "\\2f\\2a unifinished comment'",
+ 'Remove anything after a backslash-escaped comment-start token' ),
+ );
+ }
}
diff --git a/tests/phpunit/includes/UserIsValidEmailAddrTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php
index 99bf718e..14d799cf 100644
--- a/tests/phpunit/includes/UserIsValidEmailAddrTest.php
+++ b/tests/phpunit/includes/SanitizerValidateEmailTest.php
@@ -1,12 +1,12 @@
<?php
-class UserIsValidEmailAddrTest extends MediaWikiTestCase {
+class SanitizerValidateEmailTest extends MediaWikiTestCase {
private function checkEmail( $addr, $expected = true, $msg = '') {
if( $msg == '' ) { $msg = "Testing $addr"; }
$this->assertEquals(
$expected,
- User::isValidEmailAddr( $addr ),
+ Sanitizer::validateEmail( $addr ),
$msg
);
}
diff --git a/tests/phpunit/includes/SeleniumConfigurationTest.php b/tests/phpunit/includes/SeleniumConfigurationTest.php
index 750524eb..8589c188 100644
--- a/tests/phpunit/includes/SeleniumConfigurationTest.php
+++ b/tests/phpunit/includes/SeleniumConfigurationTest.php
@@ -2,13 +2,13 @@
class SeleniumConfigurationTest extends MediaWikiTestCase {
- /*
+ /**
* The file where the test temporarity stores the selenium config.
* This should be cleaned up as part of teardown.
*/
private $tempFileName;
- /*
+ /**
* String containing the a sample selenium settings
*/
private $testConfig0 =
@@ -32,14 +32,14 @@ runAgainstGrid = false
testSuite[SimpleSeleniumTestSuite] = "tests/selenium/SimpleSeleniumTestSuite.php"
testSuite[TestSuiteName] = "testSuitePath"
';
- /*
+ /**
* Array of expected browsers from $testConfig0
*/
private $testBrowsers0 = array( 'firefox' => '*firefox',
'iexplorer' => '*iexploreproxy',
'chrome' => '*chrome'
);
- /*
+ /**
* Array of expected selenium settings from $testConfig0
*/
private $testSettings0 = array(
@@ -55,7 +55,7 @@ testSuite[TestSuiteName] = "testSuitePath"
'jUnitLogFile' => null,
'runAgainstGrid' => null
);
- /*
+ /**
* Array of expected testSuites from $testConfig0
*/
private $testSuites0 = array(
@@ -64,7 +64,7 @@ testSuite[TestSuiteName] = "testSuitePath"
);
- /*
+ /**
* Another sample selenium settings file contents
*/
private $testConfig1 =
@@ -73,11 +73,11 @@ testSuite[TestSuiteName] = "testSuitePath"
host = "localhost"
testBrowser = "firefox"
';
- /*
+ /**
* Expected browsers from $testConfig1
*/
private $testBrowsers1 = null;
- /*
+ /**
* Expected selenium settings from $testConfig1
*/
private $testSettings1 = array(
@@ -93,7 +93,7 @@ testBrowser = "firefox"
'jUnitLogFile' => null,
'runAgainstGrid' => null
);
- /*
+ /**
* Expected test suites from $testConfig1
*/
private $testSuites1 = null;
@@ -105,7 +105,7 @@ testBrowser = "firefox"
}
}
- /*
+ /**
* Clean up the temporary file used to store the selenium settings.
*/
public function tearDown() {
@@ -199,7 +199,7 @@ testBrowser = "firefox"
}
- /*
+ /**
* create a temp file and write text to it.
* @param $testToWrite the text to write to the temp file
*/
@@ -210,7 +210,7 @@ testBrowser = "firefox"
fclose($tempFile);
}
- /*
+ /**
* Returns an array containing:
* The contents of the selenium cingiguration ini file
* The expected selenium configuration array that getSeleniumSettings should return
diff --git a/tests/phpunit/includes/TemplateCategoriesTest.php b/tests/phpunit/includes/TemplateCategoriesTest.php
new file mode 100644
index 00000000..de9d6dc6
--- /dev/null
+++ b/tests/phpunit/includes/TemplateCategoriesTest.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @group Database
+ */
+require dirname( __FILE__ ) . "/../../../maintenance/runJobs.php";
+
+class TemplateCategoriesTest extends MediaWikiLangTestCase {
+
+ function testTemplateCategories() {
+ global $wgUser;
+
+ $title = Title::newFromText( "Categorized from template" );
+ $article = new Article( $title );
+ $wgUser = new User();
+ $wgUser->mRights['*'] = array( 'createpage', 'edit', 'purge' );
+
+ $status = $article->doEdit( '{{Categorising template}}', 'Create a page with a template', 0 );
+ $this->assertEquals(
+ array()
+ , $title->getParentCategories()
+ );
+
+ $template = new Article( Title::newFromText( 'Template:Categorising template' ) );
+ $status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0 );
+
+ // Run the job queue
+ $jobs = new RunJobs;
+ $jobs->loadParamsAndArgs( null, array( 'quiet' => true ), null );
+ $jobs->execute();
+
+ $this->assertEquals(
+ array( 'Category:Solved_bugs' => $title->getPrefixedText() )
+ , $title->getParentCategories()
+ );
+ }
+
+}
diff --git a/tests/phpunit/includes/TitleMethodsTest.php b/tests/phpunit/includes/TitleMethodsTest.php
new file mode 100644
index 00000000..2f1103e8
--- /dev/null
+++ b/tests/phpunit/includes/TitleMethodsTest.php
@@ -0,0 +1,78 @@
+<?php
+
+class TitleMethodsTest extends MediaWikiTestCase {
+
+ public function dataEquals() {
+ return array(
+ array( 'Main Page', 'Main Page', true ),
+ array( 'Main Page', 'Not The Main Page', false ),
+ array( 'Main Page', 'Project:Main Page', false ),
+ array( 'File:Example.png', 'Image:Example.png', true ),
+ array( 'Special:Version', 'Special:Version', true ),
+ array( 'Special:Version', 'Special:Recentchanges', false ),
+ array( 'Special:Version', 'Main Page', false ),
+ );
+ }
+
+ /**
+ * @dataProvider dataEquals
+ */
+ public function testEquals( $titleA, $titleB, $expectedBool ) {
+ $titleA = Title::newFromText( $titleA );
+ $titleB = Title::newFromText( $titleB );
+
+ $this->assertEquals( $titleA->equals( $titleB ), $expectedBool );
+ $this->assertEquals( $titleB->equals( $titleA ), $expectedBool );
+ }
+
+ public function dataInNamespace() {
+ return array(
+ array( 'Main Page', NS_MAIN, true ),
+ array( 'Main Page', NS_TALK, false ),
+ array( 'Main Page', NS_USER, false ),
+ array( 'User:Foo', NS_USER, true ),
+ array( 'User:Foo', NS_USER_TALK, false ),
+ array( 'User:Foo', NS_TEMPLATE, false ),
+ array( 'User_talk:Foo', NS_USER_TALK, true ),
+ array( 'User_talk:Foo', NS_USER, false ),
+ );
+ }
+
+ /**
+ * @dataProvider dataInNamespace
+ */
+ public function testInNamespace( $title, $ns, $expectedBool ) {
+ $title = Title::newFromText( $title );
+ $this->assertEquals( $title->inNamespace( $ns ), $expectedBool );
+ }
+
+ public function testInNamespaces() {
+ $mainpage = Title::newFromText( 'Main Page' );
+ $this->assertTrue( $mainpage->inNamespaces( NS_MAIN, NS_USER ) );
+ $this->assertTrue( $mainpage->inNamespaces( array( NS_MAIN, NS_USER ) ) );
+ $this->assertTrue( $mainpage->inNamespaces( array( NS_USER, NS_MAIN ) ) );
+ $this->assertFalse( $mainpage->inNamespaces( array( NS_PROJECT, NS_TEMPLATE ) ) );
+ }
+
+ public function dataHasSubjectNamespace() {
+ return array(
+ array( 'Main Page', NS_MAIN, true ),
+ array( 'Main Page', NS_TALK, true ),
+ array( 'Main Page', NS_USER, false ),
+ array( 'User:Foo', NS_USER, true ),
+ array( 'User:Foo', NS_USER_TALK, true ),
+ array( 'User:Foo', NS_TEMPLATE, false ),
+ array( 'User_talk:Foo', NS_USER_TALK, true ),
+ array( 'User_talk:Foo', NS_USER, true ),
+ );
+ }
+
+ /**
+ * @dataProvider dataHasSubjectNamespace
+ */
+ public function testHasSubjectNamespace( $title, $ns, $expectedBool ) {
+ $title = Title::newFromText( $title );
+ $this->assertEquals( $title->hasSubjectNamespace( $ns ), $expectedBool );
+ }
+
+}
diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php
index 1b179686..f62ac5dd 100644
--- a/tests/phpunit/includes/TitlePermissionTest.php
+++ b/tests/phpunit/includes/TitlePermissionTest.php
@@ -5,12 +5,16 @@
*/
class TitlePermissionTest extends MediaWikiLangTestCase {
protected $title;
- protected $user;
- protected $anonUser;
- protected $userUser;
- protected $altUser;
- protected $userName;
- protected $altUserName;
+
+ /**
+ * @var User
+ */
+ protected $user, $anonUser, $userUser, $altUser;
+
+ /**
+ * @var string
+ */
+ protected $userName, $altUserName;
function setUp() {
global $wgLocaltimezone, $wgLocalTZoffset, $wgMemc, $wgContLang, $wgLang;
@@ -56,6 +60,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
}
function setUserPerm( $perm ) {
+ // Setting member variables is evil!!!
+
if ( is_array( $perm ) ) {
$this->user->mRights = $perm;
} else {
@@ -299,7 +305,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->assertEquals( $check[$action][3],
$this->title->userCan( $action, true ) );
$this->assertEquals( $check[$action][3],
- $this->title->quickUserCan( $action, false ) );
+ $this->title->quickUserCan( $action ) );
# count( User::getGroupsWithPermissions( $action ) ) < 1
}
@@ -451,7 +457,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->user ) );
$this->assertEquals( true,
- $this->title->quickUserCan( 'edit', false ) );
+ $this->title->quickUserCan( 'edit' ) );
$this->title->mRestrictions = array( "edit" => array( 'bogus', "sysop", "protect", "" ),
"bogus" => array( 'bogus', "sysop", "protect", "" ) );
@@ -491,9 +497,9 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->user ) );
$this->title->mCascadeRestriction = true;
$this->assertEquals( false,
- $this->title->quickUserCan( 'bogus', false ) );
+ $this->title->quickUserCan( 'bogus' ) );
$this->assertEquals( false,
- $this->title->quickUserCan( 'edit', false ) );
+ $this->title->quickUserCan( 'edit' ) );
$this->assertEquals( array( array( 'badaccess-group0' ),
array( 'protectedpagetext', 'bogus' ),
array( 'protectedpagetext', 'protect' ),
@@ -537,7 +543,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$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'] = Block::infinity();
+ $this->title->mTitleProtection['pt_expiry'] = wfGetDB( DB_SLAVE )->getInfinity();
$this->title->mTitleProtection['pt_reason'] = 'test';
$this->title->mCascadeRestriction = false;
@@ -574,7 +580,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->title->userCan( 'move' ) );
$this->title->mInterwiki = "no";
- $this->assertEquals( array( array( 'immobile-page' ) ),
+ $this->assertEquals( array( array( 'immobile-source-page' ) ),
$this->title->getUserPermissionsErrors( 'move', $this->user ) );
$this->assertEquals( false,
$this->title->userCan( 'move' ) );
@@ -623,7 +629,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$prev = time();
$now = time() + 120;
$this->user->mBlockedby = $this->user->getId();
- $this->user->mBlock = new Block( '127.0.8.1', $this->user->getId(), $this->user->getId(),
+ $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(),
'no reason given', $prev + 3600, 1, 0 );
$this->user->mBlock->mTimestamp = 0;
$this->assertEquals( array( array( 'autoblockedtext',
@@ -640,7 +646,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
global $wgLocalTZoffset;
$wgLocalTZoffset = -60;
$this->user->mBlockedby = $this->user->getName();
- $this->user->mBlock = new Block( '127.0.8.1', 2, 1, 'no reason given', $now, 0, 10 );
+ $this->user->mBlock = new Block( '127.0.8.1', 0, 1, 'no reason given', $now, 0, 10 );
$this->assertEquals( array( array( 'blockedtext',
'[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php
index e7bb98ac..1c8be5f9 100644
--- a/tests/phpunit/includes/TitleTest.php
+++ b/tests/phpunit/includes/TitleTest.php
@@ -41,10 +41,10 @@ class TitleTest extends MediaWikiTestCase {
/**
* Auth-less test of Title::isValidMoveOperation
*
+ * @group Database
* @param string $source
* @param string $target
- * @param array|string|true $requiredErrors
- * @group Database
+ * @param array|string|true $expected Required error
* @dataProvider dataTestIsValidMoveOperation
*/
function testIsValidMoveOperation( $source, $target, $expected ) {
diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php
index df91aca8..ef03e835 100644
--- a/tests/phpunit/includes/UserTest.php
+++ b/tests/phpunit/includes/UserTest.php
@@ -1,47 +1,67 @@
<?php
+define( 'NS_UNITTEST', 5600 );
+define( 'NS_UNITTEST_TALK', 5601 );
+
+/**
+ * @group Database
+ */
class UserTest extends MediaWikiTestCase {
protected $savedGroupPermissions, $savedRevokedPermissions;
-
+
+ /**
+ * @var User
+ */
+ protected $user;
+
public function setUp() {
parent::setUp();
-
+
$this->savedGroupPermissions = $GLOBALS['wgGroupPermissions'];
$this->savedRevokedPermissions = $GLOBALS['wgRevokePermissions'];
-
+
$this->setUpPermissionGlobals();
+ $this->setUpUser();
}
private function setUpPermissionGlobals() {
global $wgGroupPermissions, $wgRevokePermissions;
-
+
+ # Data for regular $wgGroupPermissions test
$wgGroupPermissions['unittesters'] = array(
+ 'test' => true,
'runtest' => true,
'writetest' => false,
'nukeworld' => false,
);
$wgGroupPermissions['testwriters'] = array(
+ 'test' => true,
'writetest' => true,
'modifytest' => true,
);
-
+ # Data for regular $wgRevokePermissions test
$wgRevokePermissions['formertesters'] = array(
'runtest' => true,
);
}
+ private function setUpUser() {
+ $this->user = new User;
+ $this->user->addGroup( 'unittesters' );
+ }
+
public function tearDown() {
parent::tearDown();
-
+
$GLOBALS['wgGroupPermissions'] = $this->savedGroupPermissions;
$GLOBALS['wgRevokePermissions'] = $this->savedRevokedPermissions;
}
-
+
public function testGroupPermissions() {
$rights = User::getGroupPermissions( array( 'unittesters' ) );
$this->assertContains( 'runtest', $rights );
$this->assertNotContains( 'writetest', $rights );
$this->assertNotContains( 'modifytest', $rights );
$this->assertNotContains( 'nukeworld', $rights );
-
+
$rights = User::getGroupPermissions( array( 'unittesters', 'testwriters' ) );
$this->assertContains( 'runtest', $rights );
$this->assertContains( 'writetest', $rights );
@@ -53,6 +73,71 @@ class UserTest extends MediaWikiTestCase {
$this->assertNotContains( 'runtest', $rights );
$this->assertNotContains( 'writetest', $rights );
$this->assertNotContains( 'modifytest', $rights );
- $this->assertNotContains( 'nukeworld', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ public function testUserPermissions() {
+ $rights = $this->user->getRights();
+ $this->assertContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertNotContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ /**
+ * @dataProvider provideGetGroupsWithPermission
+ */
+ public function testGetGroupsWithPermission( $expected, $right ) {
+ $result = User::getGroupsWithPermission( $right );
+ sort( $result );
+ sort( $expected );
+
+ $this->assertEquals( $expected, $result, "Groups with permission $right" );
+ }
+
+ public function provideGetGroupsWithPermission() {
+ return array(
+ array(
+ array( 'unittesters', 'testwriters' ),
+ 'test'
+ ),
+ array(
+ array( 'unittesters' ),
+ 'runtest'
+ ),
+ array(
+ array( 'testwriters' ),
+ 'writetest'
+ ),
+ array(
+ array( 'testwriters' ),
+ 'modifytest'
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider provideUserNames
+ */
+ public function testIsValidUserName( $username, $result, $message ) {
+ $this->assertEquals( $this->user->isValidUserName( $username ), $result, $message );
+ }
+
+ public function provideUserNames() {
+ return array(
+ array( '', false, 'Empty string' ),
+ array( ' ', false, 'Blank space' ),
+ array( 'abcd', false, 'Starts with small letter' ),
+ array( 'Ab/cd', false, 'Contains slash' ),
+ array( 'Ab cd' , true, 'Whitespace' ),
+ array( '192.168.1.1', false, 'IP' ),
+ array( 'User:Abcd', false, 'Reserved Namespace' ),
+ array( '12abcd232' , true , 'Starts with Numbers' ),
+ array( '?abcd' , true, 'Start with ? mark' ),
+ array( '#abcd', false, 'Start with #' ),
+ array( 'Abcdകഖഗഘ', true, ' Mixed scripts' ),
+ array( 'ജോസ്‌തോമസ്', false, 'ZWNJ- Format control character' ),
+ array( 'Ab cd', false, ' Ideographic space' ),
+ );
}
-} \ No newline at end of file
+}
diff --git a/tests/phpunit/includes/WebRequestTest.php b/tests/phpunit/includes/WebRequestTest.php
index 1cfbd3fc..e72408f6 100644
--- a/tests/phpunit/includes/WebRequestTest.php
+++ b/tests/phpunit/includes/WebRequestTest.php
@@ -85,4 +85,101 @@ class WebRequestTest extends MediaWikiTestCase {
),
);
}
+
+ /**
+ * @dataProvider provideGetIP
+ */
+ function testGetIP( $expected, $input, $squid, $private, $description ) {
+ global $wgSquidServersNoPurge, $wgUsePrivateIPs;
+ $oldServer = $_SERVER;
+ $_SERVER = $input;
+ $wgSquidServersNoPurge = $squid;
+ $wgUsePrivateIPs = $private;
+ $request = new WebRequest();
+ $result = $request->getIP();
+ $_SERVER = $oldServer;
+ $this->assertEquals( $expected, $result, $description );
+ }
+
+ function provideGetIP() {
+ return array(
+ array(
+ '127.0.0.1',
+ array(
+ 'REMOTE_ADDR' => '127.0.0.1'
+ ),
+ array(),
+ false,
+ 'Simple IPv4'
+ ),
+ array(
+ '::1',
+ array(
+ 'REMOTE_ADDR' => '::1'
+ ),
+ array(),
+ false,
+ 'Simple IPv6'
+ ),
+ array(
+ '12.0.0.3',
+ array(
+ 'REMOTE_ADDR' => '12.0.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2'
+ ),
+ array( '12.0.0.1', '12.0.0.2' ),
+ false,
+ 'With X-Forwaded-For'
+ ),
+ array(
+ '12.0.0.1',
+ array(
+ 'REMOTE_ADDR' => '12.0.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2'
+ ),
+ array(),
+ false,
+ 'With X-Forwaded-For and disallowed server'
+ ),
+ array(
+ '12.0.0.2',
+ array(
+ 'REMOTE_ADDR' => '12.0.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2'
+ ),
+ array( '12.0.0.1' ),
+ false,
+ 'With multiple X-Forwaded-For and only one allowed server'
+ ),
+ array(
+ '12.0.0.2',
+ array(
+ 'REMOTE_ADDR' => '12.0.0.2',
+ 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2'
+ ),
+ array( '12.0.0.1', '12.0.0.2' ),
+ false,
+ 'With X-Forwaded-For and private IP'
+ ),
+ array(
+ '10.0.0.3',
+ array(
+ 'REMOTE_ADDR' => '12.0.0.2',
+ 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2'
+ ),
+ array( '12.0.0.1', '12.0.0.2' ),
+ true,
+ 'With X-Forwaded-For and private IP (allowed)'
+ ),
+ );
+ }
+
+ /**
+ * @expectedException MWException
+ */
+ function testGetIpLackOfRemoteAddrThrowAnException() {
+ $request = new WebRequest();
+ # Next call throw an exception about lacking an IP
+ $request->getIP();
+ }
}
diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php
index bf761e3d..2407c151 100644
--- a/tests/phpunit/includes/XmlSelectTest.php
+++ b/tests/phpunit/includes/XmlSelectTest.php
@@ -80,7 +80,7 @@ class XmlSelectTest extends MediaWikiTestCase {
$this->select->addOption( 'foo2' );
$this->assertEquals(
'<select><option value="foo1">foo1</option>' . "\n" .
-'<option value="bar1" selected="selected">bar1</option>' . "\n" .
+'<option value="bar1" selected="">bar1</option>' . "\n" .
'<option value="foo2">foo2</option></select>', $this->select->getHTML() );
}
@@ -96,7 +96,7 @@ class XmlSelectTest extends MediaWikiTestCase {
$this->select->setDefault( 'bar1' ); # setting default after adding options
$this->assertEquals(
'<select><option value="foo1">foo1</option>' . "\n" .
-'<option value="bar1" selected="selected">bar1</option>' . "\n" .
+'<option value="bar1" selected="">bar1</option>' . "\n" .
'<option value="foo2">foo2</option></select>', $this->select->getHTML() );
}
diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php
index a6058ef6..1d9361f2 100644
--- a/tests/phpunit/includes/XmlTest.php
+++ b/tests/phpunit/includes/XmlTest.php
@@ -2,19 +2,43 @@
class XmlTest extends MediaWikiTestCase {
private static $oldLang;
+ private static $oldNamespaces;
public function setUp() {
- global $wgLang, $wgLanguageCode;
-
+ global $wgLang, $wgContLang;
+
self::$oldLang = $wgLang;
- $wgLanguageCode = 'en';
- $wgLang = Language::factory( $wgLanguageCode );
+ $wgLang = Language::factory( 'en' );
+
+ // Hardcode namespaces during test runs,
+ // so that html output based on existing namespaces
+ // can be properly evaluated.
+ self::$oldNamespaces = $wgContLang->getNamespaces();
+ $wgContLang->setNamespaces( array(
+ -2 => 'Media',
+ -1 => 'Special',
+ 0 => '',
+ 1 => 'Talk',
+ 2 => 'User',
+ 3 => 'User_talk',
+ 4 => 'MyWiki',
+ 5 => 'MyWiki_Talk',
+ 6 => 'File',
+ 7 => 'File_talk',
+ 8 => 'MediaWiki',
+ 9 => 'MediaWiki_talk',
+ 10 => 'Template',
+ 11 => 'Template_talk',
+ 100 => 'Custom',
+ 101 => 'Custom_talk',
+ ) );
}
-
+
public function tearDown() {
- global $wgLang, $wgLanguageCode;
+ global $wgLang, $wgContLang;
$wgLang = self::$oldLang;
- $wgLanguageCode = $wgLang->getCode();
+
+ $wgContLang->setNamespaces( self::$oldNamespaces );
}
public function testExpandAttributes() {
@@ -88,6 +112,9 @@ class XmlTest extends MediaWikiTestCase {
$this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' );
}
+ /**
+ * @group Broken
+ */
public function testDateMenu( ) {
$curYear = intval(gmdate('Y'));
$prevYear = $curYear - 1;
@@ -98,11 +125,10 @@ class XmlTest extends MediaWikiTestCase {
$nextMonth = $curMonth + 1;
if( $nextMonth == 13 ) { $nextMonth = 1; }
-
$this->assertEquals(
'<label for="year">From year (and earlier):</label> <input name="year" size="4" value="2011" id="year" maxlength="4" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" .
'<option value="1">January</option>' . "\n" .
-'<option value="2" selected="selected">February</option>' . "\n" .
+'<option value="2" selected="">February</option>' . "\n" .
'<option value="3">March</option>' . "\n" .
'<option value="4">April</option>' . "\n" .
'<option value="5">May</option>' . "\n" .
@@ -139,7 +165,6 @@ class XmlTest extends MediaWikiTestCase {
"Date menu year is the current one when not specified"
);
- $this->markTestIncomplete( "Broken" );
// @todo FIXME: next month can be in the next year
// test failing because it is now december
$this->assertEquals(
@@ -163,11 +188,57 @@ class XmlTest extends MediaWikiTestCase {
'<option value="10">October</option>' . "\n" .
'<option value="11">November</option>' . "\n" .
'<option value="12">December</option></select>',
- Xml::dateMenu( '', ''),
+ Xml::dateMenu( '', '' ),
"Date menu with neither year or month"
);
}
+ function testNamespaceSelector() {
+ $this->assertEquals(
+ '<select class="namespaceselector" id="namespace" name="namespace">' . "\n" .
+'<option value="0">(Main)</option>' . "\n" .
+'<option value="1">Talk</option>' . "\n" .
+'<option value="2">User</option>' . "\n" .
+'<option value="3">User talk</option>' . "\n" .
+'<option value="4">MyWiki</option>' . "\n" .
+'<option value="5">MyWiki Talk</option>' . "\n" .
+'<option value="6">File</option>' . "\n" .
+'<option value="7">File talk</option>' . "\n" .
+'<option value="8">MediaWiki</option>' . "\n" .
+'<option value="9">MediaWiki talk</option>' . "\n" .
+'<option value="10">Template</option>' . "\n" .
+'<option value="11">Template talk</option>' . "\n" .
+'<option value="100">Custom</option>' . "\n" .
+'<option value="101">Custom talk</option>' . "\n" .
+'</select>',
+ Xml::namespaceSelector(),
+ 'Basic namespace selector without custom options'
+ );
+ $this->assertEquals(
+ '<label for="namespace">Select a namespace:</label>' .
+'&#160;<select class="namespaceselector" id="namespace" name="myname">' . "\n" .
+'<option value="all">all</option>' . "\n" .
+'<option value="0">(Main)</option>' . "\n" .
+'<option value="1">Talk</option>' . "\n" .
+'<option value="2" selected="">User</option>' . "\n" .
+'<option value="3">User talk</option>' . "\n" .
+'<option value="4">MyWiki</option>' . "\n" .
+'<option value="5">MyWiki Talk</option>' . "\n" .
+'<option value="6">File</option>' . "\n" .
+'<option value="7">File talk</option>' . "\n" .
+'<option value="8">MediaWiki</option>' . "\n" .
+'<option value="9">MediaWiki talk</option>' . "\n" .
+'<option value="10">Template</option>' . "\n" .
+'<option value="11">Template talk</option>' . "\n" .
+'<option value="100">Custom</option>' . "\n" .
+'<option value="101">Custom talk</option>' . "\n" .
+'</select>',
+ Xml::namespaceSelector( $selected = '2', $all = 'all', $element_name = 'myname', $label = 'Select a namespace:' ),
+ 'Basic namespace selector with custom values'
+ );
+ }
+
+
#
# textarea
#
@@ -255,12 +326,12 @@ class XmlTest extends MediaWikiTestCase {
function testEncodeJsVarArray() {
$this->assertEquals(
- '["a", 1]',
+ '["a",1]',
Xml::encodeJsVar( array( 'a', 1 ) ),
'encodeJsVar() with array'
);
$this->assertEquals(
- '{"a": "a", "b": 1}',
+ '{"a":"a","b":1}',
Xml::encodeJsVar( array( 'a' => 'a', 'b' => 1 ) ),
'encodeJsVar() with associative array'
);
@@ -268,7 +339,7 @@ class XmlTest extends MediaWikiTestCase {
function testEncodeJsVarObject() {
$this->assertEquals(
- '{"a": "a", "b": 1}',
+ '{"a":"a","b":1}',
Xml::encodeJsVar( (object)array( 'a' => 'a', 'b' => 1 ) ),
'encodeJsVar() with object'
);
diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php
index 227555eb..b95d8214 100644
--- a/tests/phpunit/includes/api/ApiBlockTest.php
+++ b/tests/phpunit/includes/api/ApiBlockTest.php
@@ -15,24 +15,34 @@ class ApiBlockTest extends ApiTestCase {
}
function addDBData() {
- $user = User::newFromName( 'UTBlockee' );
+ $user = User::newFromName( 'UTApiBlockee' );
if ( $user->getId() == 0 ) {
$user->addToDatabase();
- $user->setPassword( 'UTBlockeePassword' );
+ $user->setPassword( 'UTApiBlockeePassword' );
$user->saveSettings();
}
}
+ /**
+ * This test has probably always been broken and use an invalid token
+ * Bug tracking brokenness is https://bugzilla.wikimedia.org/35646
+ *
+ * Root cause is https://gerrit.wikimedia.org/r/3434
+ * Which made the Block/Unblock API to actually verify the token
+ * previously always considered valid (bug 34212).
+ *
+ * @group Broken
+ */
function testMakeNormalBlock() {
$data = $this->getTokens();
- $user = User::newFromName( 'UTBlockee' );
+ $user = User::newFromName( 'UTApiBlockee' );
if ( !$user->getId() ) {
- $this->markTestIncomplete( "The user UTBlockee does not exist" );
+ $this->markTestIncomplete( "The user UTApiBlockee does not exist" );
}
if( !isset( $data[0]['query']['pages'] ) ) {
@@ -45,15 +55,15 @@ class ApiBlockTest extends ApiTestCase {
$data = $this->doApiRequest( array(
'action' => 'block',
- 'user' => 'UTBlockee',
- 'reason' => BlockTest::REASON,
- 'token' => $pageinfo['blocktoken'] ), $data );
+ 'user' => 'UTApiBlockee',
+ 'reason' => 'Some reason',
+ 'token' => $pageinfo['blocktoken'] ), $data, false, self::$users['sysop']->user );
- $block = Block::newFromTarget('UTBlockee');
+ $block = Block::newFromTarget('UTApiBlockee');
$this->assertTrue( !is_null( $block ), 'Block is valid' );
- $this->assertEquals( 'UTBlockee', (string)$block->getTarget() );
+ $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() );
$this->assertEquals( 'Some reason', $block->mReason );
$this->assertEquals( 'infinity', $block->mExpiry );
diff --git a/tests/phpunit/includes/api/ApiPurgeTest.php b/tests/phpunit/includes/api/ApiPurgeTest.php
index db1563e9..70c20746 100644
--- a/tests/phpunit/includes/api/ApiPurgeTest.php
+++ b/tests/phpunit/includes/api/ApiPurgeTest.php
@@ -9,33 +9,31 @@ class ApiPurgeTest extends ApiTestCase {
parent::setUp();
$this->doLogin();
}
-
+
+ /**
+ * @group Broken
+ */
function testPurgeMainPage() {
-
if ( !Title::newFromText( 'UTPage' )->exists() ) {
$this->markTestIncomplete( "The article [[UTPage]] does not exist" );
}
-
+
$somePage = mt_rand();
$data = $this->doApiRequest( array(
'action' => 'purge',
'titles' => 'UTPage|' . $somePage . '|%5D' ) );
-
- $this->assertArrayHasKey( 'purge', $data[0] );
-
- $this->assertArrayHasKey( 0, $data[0]['purge'] );
- $this->assertArrayHasKey( 'purged', $data[0]['purge'][0] );
- $this->assertEquals( 'UTPage', $data[0]['purge'][0]['title'] );
-
- $this->assertArrayHasKey( 1, $data[0]['purge'] );
- $this->assertArrayHasKey( 'missing', $data[0]['purge'][1] );
- $this->assertEquals( $somePage, $data[0]['purge'][1]['title'] );
-
- $this->assertArrayHasKey( 2, $data[0]['purge'] );
- $this->assertArrayHasKey( 'invalid', $data[0]['purge'][2] );
- $this->assertEquals( '%5D', $data[0]['purge'][2]['title'] );
-
+
+ $this->assertArrayHasKey( 'purge', $data[0],
+ "Must receive a 'purge' result from API" );
+
+ $this->assertEquals( 3, count( $data[0]['purge'] ),
+ "Purge request for three articles should give back three results received: " . var_export( $data[0]['purge'], true ) );
+
+ $pages = array( 'UTPage' => 'purged', $somePage => 'missing', '%5D' => 'invalid' );
+ foreach( $data[0]['purge'] as $v ) {
+ $this->assertArrayHasKey( $pages[$v['title']], $v );
+ }
}
}
diff --git a/tests/phpunit/includes/api/ApiQueryTest.php b/tests/phpunit/includes/api/ApiQueryTest.php
index 114eadf3..ae05a30a 100644
--- a/tests/phpunit/includes/api/ApiQueryTest.php
+++ b/tests/phpunit/includes/api/ApiQueryTest.php
@@ -22,10 +22,13 @@ class ApiQueryTest extends ApiTestCase {
$this->assertArrayHasKey( 'query', $data[0] );
$this->assertArrayHasKey( 'normalized', $data[0]['query'] );
+ // Forge a normalized title
+ $to = Title::newFromText( $wgMetaNamespace.':ArticleA' );
+
$this->assertEquals(
array(
'from' => 'Project:articleA',
- 'to' => $wgMetaNamespace . ':ArticleA'
+ 'to' => $to->getPrefixedText(),
),
$data[0]['query']['normalized'][0]
);
@@ -50,7 +53,6 @@ class ApiQueryTest extends ApiTestCase {
'action' => 'query',
'titles' => $title . '|Talk:' ) );
-
$this->assertArrayHasKey( 'query', $data[0] );
$this->assertArrayHasKey( 'pages', $data[0]['query'] );
$this->assertEquals( 2, count( $data[0]['query']['pages'] ) );
@@ -60,8 +62,6 @@ class ApiQueryTest extends ApiTestCase {
$this->assertArrayHasKey( 'missing', $data[0]['query']['pages'][-2] );
$this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] );
-
-
}
}
diff --git a/tests/phpunit/includes/api/ApiTest.php b/tests/phpunit/includes/api/ApiTest.php
index a587e6b1..1d9c3238 100644
--- a/tests/phpunit/includes/api/ApiTest.php
+++ b/tests/phpunit/includes/api/ApiTest.php
@@ -96,7 +96,7 @@ class ApiTest extends ApiTestCase {
"lgtoken" => $token,
"lgname" => $user->username,
"lgpassword" => "badnowayinhell",
- )
+ ), $ret[2]
);
$result = $ret[0];
@@ -137,7 +137,7 @@ class ApiTest extends ApiTestCase {
"lgtoken" => $token,
"lgname" => $user->username,
"lgpassword" => $user->password,
- )
+ ), $ret[2]
);
$result = $ret[0];
@@ -148,6 +148,9 @@ class ApiTest extends ApiTestCase {
$this->assertEquals( "Success", $a );
}
+ /**
+ * @group Broken
+ */
function testApiGotCookie() {
$this->markTestIncomplete( "The server can't do external HTTP requests, and the internal one won't give cookies" );
@@ -192,24 +195,23 @@ class ApiTest extends ApiTestCase {
}
/**
- * @depends testApiGotCookie
+ * @todo Finish filling me out...what are we trying to test here?
*/
- function testApiListPages( CookieJar $cj ) {
- $this->markTestIncomplete( "Not done with this yet" );
+ function testApiListPages() {
global $wgServer;
-
- if ( $wgServer == "http://localhost" ) {
+ if ( !isset( $wgServer ) ) {
$this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
}
- $req = MWHttpRequest::factory( self::$apiUrl . "?action=query&format=xml&prop=revisions&" .
- "titles=Main%20Page&rvprop=timestamp|user|comment|content" );
- $req->setCookieJar( $cj );
- $req->execute();
- libxml_use_internal_errors( true );
- $sxe = simplexml_load_string( $req->getContent() );
- $this->assertNotInternalType( "bool", $sxe );
- $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) );
- $a = $sxe->query[0]->pages[0]->page[0]->attributes();
+
+ $ret = $this->doApiRequest( array(
+ 'action' => 'query',
+ 'prop' => 'revisions',
+ 'titles' => 'Main Page',
+ 'rvprop' => 'timestamp|user|comment|content',
+ ) );
+
+ $result = $ret[0]['query']['pages'];
+ $this->markTestIncomplete( "Somebody needs to finish loving me" );
}
function testRunLogin() {
@@ -228,7 +230,7 @@ class ApiTest extends ApiTestCase {
'action' => 'login',
"lgtoken" => $token,
"lgname" => $sysopUser->username,
- "lgpassword" => $sysopUser->password ), $data );
+ "lgpassword" => $sysopUser->password ), $data[2] );
$this->assertArrayHasKey( "login", $data[0] );
$this->assertArrayHasKey( "result", $data[0]['login'] );
diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php
index 2917c880..8801391f 100644
--- a/tests/phpunit/includes/api/ApiTestCase.php
+++ b/tests/phpunit/includes/api/ApiTestCase.php
@@ -7,6 +7,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
public static $users;
protected static $apiUrl;
+ /**
+ * @var ApiTestContext
+ */
+ protected $apiContext;
+
function setUp() {
global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser, $wgServer;
@@ -21,31 +26,37 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
'sysop' => new ApiTestUser(
'Apitestsysop',
'Api Test Sysop',
- 'api_test_sysop@sample.com',
+ 'api_test_sysop@example.com',
array( 'sysop' )
),
'uploader' => new ApiTestUser(
'Apitestuser',
'Api Test User',
- 'api_test_user@sample.com',
+ 'api_test_user@example.com',
array()
)
);
$wgUser = self::$users['sysop']->user;
+ $this->apiContext = new ApiTestContext();
+
}
- protected function doApiRequest( $params, $session = null, $appendModule = false ) {
+ protected function doApiRequest( $params, $session = null, $appendModule = false, $user = null ) {
if ( is_null( $session ) ) {
$session = array();
}
- $request = new FauxRequest( $params, true, $session );
- $module = new ApiMain( $request, true );
+ $context = $this->apiContext->newTestContext( $params, $session, $user );
+ $module = new ApiMain( $context, true );
$module->execute();
- $results = array( $module->getResultData(), $request, $request->getSessionArray() );
+ $results = array(
+ $module->getResultData(),
+ $context->getRequest(),
+ $context->getRequest()->getSessionArray()
+ );
if( $appendModule ) {
$results[] = $module;
}
@@ -59,14 +70,15 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
* request, without actually requesting a "real" edit token
* @param $params: key-value API params
* @param $session: session array
+ * @param $user String|null A User object for the context
*/
- protected function doApiRequestWithToken( $params, $session ) {
+ protected function doApiRequestWithToken( $params, $session, $user = null ) {
if ( $session['wsToken'] ) {
// add edit token to fake session
$session['wsEditToken'] = $session['wsToken'];
// add token to request parameters
$params['token'] = md5( $session['wsToken'] ) . User::EDIT_TOKEN_SUFFIX;
- return $this->doApiRequest( $params, $session );
+ return $this->doApiRequest( $params, $session, false, $user );
} else {
throw new Exception( "request data not in right format" );
}
@@ -91,12 +103,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
}
protected function getTokenList( $user ) {
- $GLOBALS['wgUser'] = $user->user;
$data = $this->doApiRequest( array(
'action' => 'query',
'titles' => 'Main Page',
'intoken' => 'edit|delete|protect|move|block|unblock',
- 'prop' => 'info' ) );
+ 'prop' => 'info' ), false, $user->user );
return $data;
}
}
@@ -137,3 +148,23 @@ class MockApi extends ApiBase {
);
}
}
+
+class ApiTestContext extends RequestContext {
+
+ /**
+ * Returns a DerivativeContext with the request variables in place
+ *
+ * @param $params Array key-value API params
+ * @param $session Array session data
+ * @param $user User or null
+ * @return DerivativeContext
+ */
+ public function newTestContext( $params, $session, $user = null ) {
+ $context = new DerivativeContext( $this );
+ $context->setRequest( new FauxRequest( $params, true, $session ) );
+ if ( $user !== null ) {
+ $context->setUser( $user );
+ }
+ return $context;
+ }
+}
diff --git a/tests/phpunit/includes/api/ApiTestCaseUpload.php b/tests/phpunit/includes/api/ApiTestCaseUpload.php
index e51e7214..39c79547 100644
--- a/tests/phpunit/includes/api/ApiTestCaseUpload.php
+++ b/tests/phpunit/includes/api/ApiTestCaseUpload.php
@@ -19,6 +19,10 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
$this->clearFakeUploads();
}
+ public function tearDown() {
+ $this->clearTempUpload();
+ }
+
/**
* Helper function -- remove files and associated articles by Title
* @param $title Title: title to be removed
@@ -33,8 +37,8 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
if ( !$status->isGood() ) {
return false;
}
- $article = new Article( $title );
- $article->doDeleteArticle( "removing for test" );
+ $page = WikiPage::factory( $title );
+ $page->doDeleteArticle( "removing for test" );
// see if it now doesn't exist; reload
$title = Title::newFromText( $title->getText(), NS_FILE );
@@ -56,7 +60,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
* @param $filePath String: path to file on the filesystem
*/
public function deleteFileByContent( $filePath ) {
- $hash = File::sha1Base36( $filePath );
+ $hash = FSFile::getSha1Base36FromPath( $filePath );
$dupes = RepoGroup::singleton()->findBySha1( $hash );
$success = true;
foreach ( $dupes as $dupe ) {
@@ -100,6 +104,36 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
return true;
}
+ function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ){
+ $tmpName = tempnam( wfTempDir(), "" );
+ // copy the chunk data to temp location:
+ if ( !file_put_contents( $tmpName, $chunkData ) ) {
+ throw new Exception( "couldn't copy chunk data to $tmpName" );
+ }
+
+ clearstatcache();
+ $size = filesize( $tmpName );
+ if ( $size === false ) {
+ throw new Exception( "couldn't stat $tmpName" );
+ }
+
+ $_FILES[ $fieldName ] = array(
+ 'name' => $fileName,
+ 'type' => $type,
+ 'tmp_name' => $tmpName,
+ 'size' => $size,
+ 'error' => null
+ );
+ }
+
+ 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/ApiTestUser.php b/tests/phpunit/includes/api/ApiTestUser.php
index df60682f..8d5f61a7 100644
--- a/tests/phpunit/includes/api/ApiTestUser.php
+++ b/tests/phpunit/includes/api/ApiTestUser.php
@@ -8,7 +8,7 @@ class ApiTestUser {
public $groups;
public $user;
- function __construct( $username, $realname = 'Real Name', $email = 'sample@sample.com', $groups = array() ) {
+ function __construct( $username, $realname = 'Real Name', $email = 'sample@example.com', $groups = array() ) {
$this->username = $username;
$this->realname = $realname;
$this->email = $email;
diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php
index 5c929784..7a700326 100644
--- a/tests/phpunit/includes/api/ApiUploadTest.php
+++ b/tests/phpunit/includes/api/ApiUploadTest.php
@@ -19,6 +19,9 @@ require_once( 'ApiTestCaseUpload.php' );
/**
* @group Database
+ * @group Broken
+ * Broken test, reports false errors from time to time.
+ * See https://bugzilla.wikimedia.org/26169
*
* This is pretty sucky... needs to be prettified.
*/
@@ -54,6 +57,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
$this->assertEquals( "Success", $result['login']['result'] );
$this->assertArrayHasKey( 'lgtoken', $result['login'] );
+ $this->assertNotEmpty( $session, 'API Login must return a session' );
return $session;
}
@@ -78,14 +82,11 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUploadMissingParams( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$exception = false;
try {
$this->doApiRequestWithToken( array(
'action' => 'upload',
- ), $session );
+ ), $session, self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
$this->assertEquals( "One of the parameters filekey, file, url, statuskey is required",
@@ -99,20 +100,17 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUpload( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$extension = 'png';
$mimeType = 'image/png';
try {
$randomImageGenerator = new RandomImageGenerator();
+ $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
}
catch ( Exception $e ) {
$this->markTestIncomplete( $e->getMessage() );
}
- $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
$filePath = $filePaths[0];
$fileSize = filesize( $filePath );
$fileName = basename( $filePath );
@@ -135,7 +133,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, , ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
}
@@ -155,9 +154,6 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUploadZeroLength( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$mimeType = 'image/png';
$filePath = tempnam( wfTempDir(), "" );
@@ -179,7 +175,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- $this->doApiRequestWithToken( $params, $session );
+ $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user );
} catch ( UsageException $e ) {
$this->assertContains( 'The file you submitted was empty', $e->getMessage() );
$exception = true;
@@ -196,20 +192,17 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUploadSameFileName( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$extension = 'png';
$mimeType = 'image/png';
try {
$randomImageGenerator = new RandomImageGenerator();
+ $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() );
}
catch ( Exception $e ) {
$this->markTestIncomplete( $e->getMessage() );
}
- $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() );
// we'll reuse this filename
$fileName = basename( $filePaths[0] );
@@ -233,7 +226,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
}
@@ -249,7 +243,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, , ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user ); // FIXME: leaks a temporary file
} catch ( UsageException $e ) {
$exception = true;
}
@@ -270,19 +265,17 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUploadSameContent( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$extension = 'png';
$mimeType = 'image/png';
try {
$randomImageGenerator = new RandomImageGenerator();
+ $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
}
catch ( Exception $e ) {
$this->markTestIncomplete( $e->getMessage() );
}
- $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
+
$fileNames[0] = basename( $filePaths[0] );
$fileNames[1] = "SameContentAs" . $fileNames[0];
@@ -307,7 +300,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
}
@@ -332,7 +326,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user ); // FIXME: leaks a temporary file
} catch ( UsageException $e ) {
$exception = true;
}
@@ -354,19 +349,19 @@ class ApiUploadTest extends ApiTestCaseUpload {
*/
public function testUploadStash( $session ) {
global $wgUser;
- $wgUser = self::$users['uploader']->user;
+ $wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere
$extension = 'png';
$mimeType = 'image/png';
try {
$randomImageGenerator = new RandomImageGenerator();
+ $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
}
catch ( Exception $e ) {
$this->markTestIncomplete( $e->getMessage() );
}
- $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
$filePath = $filePaths[0];
$fileSize = filesize( $filePath );
$fileName = basename( $filePath );
@@ -389,7 +384,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user ); // FIXME: leaks a temporary file
} catch ( UsageException $e ) {
$exception = true;
}
@@ -417,17 +413,156 @@ class ApiUploadTest extends ApiTestCaseUpload {
$this->clearFakeUploads();
$exception = false;
try {
- list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
}
$this->assertTrue( isset( $result['upload'] ) );
$this->assertEquals( 'Success', $result['upload']['result'] );
- $this->assertFalse( $exception );
+ $this->assertFalse( $exception, "No UsageException exception." );
// clean up
$this->deleteFileByFilename( $fileName );
unlink( $filePath );
}
-}
+
+
+ /**
+ * @depends testLogin
+ */
+ public function testUploadChunks( $session ) {
+ global $wgUser;
+ $wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere
+
+ $chunkSize = 1048576;
+ // Download a large image file
+ // ( using RandomImageGenerator for large files is not stable )
+ $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';
+ try {
+ // Only download if the file is not avaliable in the temp location:
+ if( !is_file( $filePath ) ){
+ copy($url, $filePath);
+ }
+ }
+ catch ( Exception $e ) {
+ $this->markTestIncomplete( $e->getMessage() );
+ }
+ $fileSize = filesize( $filePath );
+ $fileName = basename( $filePath );
+
+ $this->deleteFileByFileName( $fileName );
+ $this->deleteFileByContent( $filePath );
+
+ // Base upload params:
+ $params = array(
+ 'action' => 'upload',
+ 'stash' => 1,
+ 'filename' => $fileName,
+ 'filesize' => $fileSize,
+ 'offset' => 0,
+ );
+
+ // Upload chunks
+ $chunkSessionKey = false;
+ $resultOffset = 0;
+ // Open the file:
+ $handle = @fopen ($filePath, "r");
+ if( $handle === false ){
+ $this->markTestIncomplete( "could not open file: $filePath" );
+ }
+ while (!feof ($handle)) {
+ // Get the current chunk
+ $chunkData = @fread( $handle, $chunkSize );
+
+ // Upload the current chunk into the $_FILE object:
+ $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData );
+
+ // Check for chunkSessionKey
+ if( !$chunkSessionKey ){
+ // Upload fist chunk ( and get the session key )
+ try {
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
+ } catch ( UsageException $e ) {
+ $this->markTestIncomplete( $e->getMessage() );
+ }
+ // Make sure we got a valid chunk continue:
+ $this->assertTrue( isset( $result['upload'] ) );
+ $this->assertTrue( isset( $result['upload']['filekey'] ) );
+ // If we don't get a session key mark test incomplete.
+ if( ! isset( $result['upload']['filekey'] ) ){
+ $this->markTestIncomplete( "no filekey provided" );
+ }
+ $chunkSessionKey = $result['upload']['filekey'];
+ $this->assertEquals( 'Continue', $result['upload']['result'] );
+ // First chunk should have chunkSize == offset
+ $this->assertEquals( $chunkSize, $result['upload']['offset'] );
+ $resultOffset = $result['upload']['offset'];
+ continue;
+ }
+ // Filekey set to chunk session
+ $params['filekey'] = $chunkSessionKey;
+ // Update the offset ( always add chunkSize for subquent chunks should be in-sync with $result['upload']['offset'] )
+ $params['offset'] += $chunkSize;
+ // Make sure param offset is insync with resultOffset:
+ $this->assertEquals( $resultOffset, $params['offset'] );
+ // Upload current chunk
+ try {
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
+ } catch ( UsageException $e ) {
+ $this->markTestIncomplete( $e->getMessage() );
+ }
+ // Make sure we got a valid chunk continue:
+ $this->assertTrue( isset( $result['upload'] ) );
+ $this->assertTrue( isset( $result['upload']['filekey'] ) );
+
+ // Check if we were on the last chunk:
+ if( $params['offset'] + $chunkSize >= $fileSize ){
+ $this->assertEquals( 'Success', $result['upload']['result'] );
+ break;
+ } else {
+ $this->assertEquals( 'Continue', $result['upload']['result'] );
+ // update $resultOffset
+ $resultOffset = $result['upload']['offset'];
+ }
+ }
+ fclose ($handle);
+
+ // Check that we got a valid file result:
+ wfDebug( __METHOD__ . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n");
+ $this->assertEquals( $fileSize, $result['upload']['imageinfo']['size'] );
+ $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
+ $this->assertTrue( isset( $result['upload']['filekey'] ) );
+ $filekey = $result['upload']['filekey'];
+
+ // Now we should try to release the file from stash
+ $params = array(
+ 'action' => 'upload',
+ 'filekey' => $filekey,
+ 'filename' => $fileName,
+ 'comment' => 'dummy comment',
+ 'text' => "This is the page text for $fileName, altered",
+ );
+ $this->clearFakeUploads();
+ $exception = false;
+ try {
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
+ } catch ( UsageException $e ) {
+ $exception = true;
+ }
+ $this->assertTrue( isset( $result['upload'] ) );
+ $this->assertEquals( 'Success', $result['upload']['result'] );
+ $this->assertFalse( $exception );
+
+ // clean up
+ $this->deleteFileByFilename( $fileName );
+ // don't remove downloaded temporary file for fast subquent tests.
+ //unlink( $filePath );
+ }
+}
diff --git a/tests/phpunit/includes/api/ApiWatchTest.php b/tests/phpunit/includes/api/ApiWatchTest.php
index 3c7ff304..b7803746 100644
--- a/tests/phpunit/includes/api/ApiWatchTest.php
+++ b/tests/phpunit/includes/api/ApiWatchTest.php
@@ -41,6 +41,7 @@ class ApiWatchTest extends ApiTestCase {
/**
* @depends testWatchEdit
+ * @group Broken
*/
function testWatchClear() {
@@ -92,7 +93,9 @@ class ApiWatchTest extends ApiTestCase {
$this->assertArrayHasKey( 'edit', $data[0]['protect']['protections'][0] );
}
-
+ /**
+ * @group Broken
+ */
function testGetRollbackToken() {
$data = $this->getTokens();
diff --git a/tests/phpunit/includes/api/RandomImageGenerator.php b/tests/phpunit/includes/api/RandomImageGenerator.php
index ae349978..86c0a828 100644
--- a/tests/phpunit/includes/api/RandomImageGenerator.php
+++ b/tests/phpunit/includes/api/RandomImageGenerator.php
@@ -1,6 +1,6 @@
<?php
-/*
+/**
* RandomImageGenerator -- does what it says on the tin.
* Requires Imagick, the ImageMagick library for PHP, or the command line equivalent (usually 'convert').
*
@@ -27,12 +27,11 @@
class RandomImageGenerator {
private $dictionaryFile;
- private $minWidth = 400;
- private $maxWidth = 800;
- private $minHeight = 400;
- private $maxHeight = 800;
- private $shapesToDraw = 5;
- private $imageWriteMethod;
+ private $minWidth = 400 ;
+ private $maxWidth = 800 ;
+ private $minHeight = 400 ;
+ private $maxHeight = 800 ;
+ private $shapesToDraw = 5 ;
/**
* Orientations: 0th row, 0th column, EXIF orientation code, rotation 2x2 matrix that is opposite of orientation
@@ -41,35 +40,35 @@ class RandomImageGenerator {
* (we also would need a non-symmetric shape for the images to test those, like a letter F)
*/
private static $orientations = array(
- array(
- '0thRow' => 'top',
- '0thCol' => 'left',
- 'exifCode' => 1,
- 'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) )
+ array(
+ '0thRow' => 'top',
+ '0thCol' => 'left',
+ 'exifCode' => 1,
+ 'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) )
),
- array(
+ array(
'0thRow' => 'bottom',
- '0thCol' => 'right',
- 'exifCode' => 3,
- 'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) )
+ '0thCol' => 'right',
+ 'exifCode' => 3,
+ 'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) )
),
- array(
- '0thRow' => 'right',
- '0thCol' => 'top',
- 'exifCode' => 6,
- 'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) )
+ array(
+ '0thRow' => 'right',
+ '0thCol' => 'top',
+ 'exifCode' => 6,
+ 'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) )
),
- array(
- '0thRow' => 'left',
- '0thCol' => 'bottom',
- 'exifCode' => 8,
- 'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) )
+ array(
+ '0thRow' => 'left',
+ '0thCol' => 'bottom',
+ 'exifCode' => 8,
+ 'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) )
)
);
public function __construct( $options = array() ) {
- foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxHeight', 'shapesToDraw' ) as $property ) {
+ foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight', 'shapesToDraw' ) as $property ) {
if ( isset( $options[$property] ) ) {
$this->$property = $options[$property];
}
@@ -77,10 +76,10 @@ class RandomImageGenerator {
// find the dictionary file, to generate random names
if ( !isset( $this->dictionaryFile ) ) {
- foreach ( array(
- '/usr/share/dict/words',
- '/usr/dict/words',
- dirname( __FILE__ ) . '/words.txt' )
+ foreach ( array(
+ '/usr/share/dict/words',
+ '/usr/dict/words',
+ dirname( __FILE__ ) . '/words.txt' )
as $dictionaryFile ) {
if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) {
$this->dictionaryFile = $dictionaryFile;
@@ -91,14 +90,6 @@ class RandomImageGenerator {
if ( !isset( $this->dictionaryFile ) ) {
throw new Exception( "RandomImageGenerator: dictionary file not found or not specified properly" );
}
-
- if ( !class_exists( 'Imagick' ) ) {
- throw new Exception( 'No Imagick extension' );
- }
- global $wgExiv2Command;
- if ( !$wgExiv2Command || !is_executable( $wgExiv2Command ) ) {
- throw new Exception( 'exiv2 not executable or $wgExiv2Command not set' );
- }
}
/**
@@ -125,15 +116,16 @@ class RandomImageGenerator {
*/
function getImageWriteMethod( $format ) {
global $wgUseImageMagick, $wgImageMagickConvertCommand;
- if ( $format === 'svg' ) {
+ if ( $format === 'svg' ) {
return 'writeSvg';
} else {
// figure out how to write images
- if ( class_exists( 'Imagick' ) ) {
+ global $wgExiv2Command;
+ if ( class_exists( 'Imagick' ) && $wgExiv2Command && is_executable( $wgExiv2Command ) ) {
return 'writeImageWithApi';
} elseif ( $wgUseImageMagick && $wgImageMagickConvertCommand && is_executable( $wgImageMagickConvertCommand ) ) {
return 'writeImageWithCommandLine';
- }
+ }
}
throw new Exception( "RandomImageGenerator: could not find a suitable method to write images in '$format' format" );
}
@@ -219,7 +211,7 @@ class RandomImageGenerator {
*/
static function shapePointsToString( $shape ) {
$points = array();
- foreach ( $shape as $point ) {
+ foreach ( $shape as $point ) {
$points[] = $point['x'] . ',' . $point['y'];
}
return join( " ", $points );
@@ -232,16 +224,16 @@ class RandomImageGenerator {
* @param $format: file format to write (which is obviously always svg here)
* @param $filename: filename to write to
*/
- public function writeSvg( $spec, $format, $filename ) {
+ public function writeSvg( $spec, $format, $filename ) {
$svg = new SimpleXmlElement( '<svg/>' );
$svg->addAttribute( 'xmlns', 'http://www.w3.org/2000/svg' );
- $svg->addAttribute( 'version', '1.1' );
- $svg->addAttribute( 'width', $spec['width'] );
- $svg->addAttribute( 'height', $spec['height'] );
+ $svg->addAttribute( 'version', '1.1' );
+ $svg->addAttribute( 'width', $spec['width'] );
+ $svg->addAttribute( 'height', $spec['height'] );
$g = $svg->addChild( 'g' );
foreach ( $spec['draws'] as $drawSpec ) {
$shape = $g->addChild( 'polygon' );
- $shape->addAttribute( 'fill', $drawSpec['fill'] );
+ $shape->addAttribute( 'fill', $drawSpec['fill'] );
$shape->addAttribute( 'points', self::shapePointsToString( $drawSpec['shape'] ) );
};
if ( ! $fh = fopen( $filename, 'w' ) ) {
@@ -260,20 +252,20 @@ class RandomImageGenerator {
* @param $filename: filename to write to
*/
public function writeImageWithApi( $spec, $format, $filename ) {
- // this is a hack because I can't get setImageOrientation() to work. See below.
+ // this is a hack because I can't get setImageOrientation() to work. See below.
global $wgExiv2Command;
$image = new Imagick();
/**
- * If the format is 'jpg', will also add a random orientation -- the image will be drawn rotated with triangle points
+ * If the format is 'jpg', will also add a random orientation -- the image will be drawn rotated with triangle points
* facing in some direction (0, 90, 180 or 270 degrees) and a countering rotation should turn the triangle points upward again
*/
$orientation = self::$orientations[0]; // default is normal orientation
if ( $format == 'jpg' ) {
$orientation = self::$orientations[ array_rand( self::$orientations ) ];
- $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] );
+ $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] );
}
-
+
$image->newImage( $spec['width'], $spec['height'], new ImagickPixel( $spec['fill'] ) );
foreach ( $spec['draws'] as $drawSpec ) {
@@ -296,7 +288,7 @@ class RandomImageGenerator {
$cmd = wfEscapeShellArg( $wgExiv2Command )
. " -M "
. wfEscapeShellArg( "set Exif.Image.Orientation " . $orientation['exifCode'] )
- . " "
+ . " "
. wfEscapeShellArg( $filename );
$retval = 0;
@@ -304,15 +296,13 @@ class RandomImageGenerator {
if ( $retval !== 0 ) {
print "Error with $cmd: $retval, $err\n";
}
- }
-
-
+ }
}
/**
* Given an image specification, produce rotated version
* This is used when simulating a rotated image capture with EXIF orientation
- * @param $spec Object returned by getImageSpec
+ * @param $spec Object returned by getImageSpec
* @param $matrix 2x2 transformation matrix
* @return transformed Spec
*/
@@ -323,8 +313,8 @@ class RandomImageGenerator {
$correctionY = 0;
if ( $dims['x'] < 0 ) {
$correctionX = abs( $dims['x'] );
- }
- if ( $dims['y'] < 0 ) {
+ }
+ if ( $dims['y'] < 0 ) {
$correctionY = abs( $dims['y'] );
}
$tSpec['width'] = abs( $dims['x'] );
@@ -332,7 +322,7 @@ class RandomImageGenerator {
$tSpec['fill'] = $spec['fill'];
$tSpec['draws'] = array();
foreach( $spec['draws'] as $draw ) {
- $tDraw = array(
+ $tDraw = array(
'fill' => $draw['fill'],
'shape' => array()
);
@@ -349,13 +339,13 @@ class RandomImageGenerator {
/**
* Given a matrix and a pair of images, return new position
- * @param $matrix: 2x2 rotation matrix
+ * @param $matrix: 2x2 rotation matrix
* @param $x: x-coordinate number
* @param $y: y-coordinate number
- * @return Array transformed with properties x, y
+ * @return Array transformed with properties x, y
*/
private static function matrixMultiply2x2( $matrix, $x, $y ) {
- return array(
+ return array(
'x' => $x * $matrix[0][0] + $y * $matrix[0][1],
'y' => $x * $matrix[1][0] + $y * $matrix[1][1]
);
@@ -366,10 +356,10 @@ class RandomImageGenerator {
* Based on an image specification, write such an image to disk, using the command line ImageMagick program ('convert').
*
* Sample command line:
- * $ convert -size 100x60 xc:rgb(90,87,45) \
- * -draw 'fill rgb(12,34,56) polygon 41,39 44,57 50,57 41,39' \
- * -draw 'fill rgb(99,123,231) circle 59,39 56,57' \
- * -draw 'fill rgb(240,12,32) circle 50,21 50,3' filename.png
+ * $ convert -size 100x60 xc:rgb(90,87,45) \
+ * -draw 'fill rgb(12,34,56) polygon 41,39 44,57 50,57 41,39' \
+ * -draw 'fill rgb(99,123,231) circle 59,39 56,57' \
+ * -draw 'fill rgb(240,12,32) circle 50,21 50,3' filename.png
*
* @param $spec: spec describing background and shapes to draw
* @param $format: file format to write (unused by this method but kept so it has the same signature as writeImageWithApi)
diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php
index 914ab27c..067c731a 100644
--- a/tests/phpunit/includes/db/DatabaseSqliteTest.php
+++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php
@@ -19,6 +19,7 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone {
/**
* @group sqlite
+ * @group Database
*/
class DatabaseSqliteTest extends MediaWikiTestCase {
var $db;
@@ -98,7 +99,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) );
$this->assertEquals( 'foobar', $db->tableName( 'bar' ) );
}
-
+
public function testDuplicateTableStructure() {
$db = new DatabaseSqliteStandalone( ':memory:' );
$db->query( 'CREATE TABLE foo(foo, barfoo)' );
@@ -119,7 +120,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
'Create a temporary duplicate only'
);
}
-
+
public function testDuplicateTableStructureVirtual() {
$db = new DatabaseSqliteStandalone( ':memory:' );
if ( $db->getFulltextSearchModule() != 'FTS3' ) {
@@ -191,13 +192,14 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
'1.15',
'1.16',
'1.17',
+ '1.18',
);
// Mismatches for these columns we can safely ignore
$ignoredColumns = array(
'user_newtalk.user_last_timestamp', // r84185
);
-
+
$currentDB = new DatabaseSqliteStandalone( ':memory:' );
$currentDB->sourceFile( "$IP/maintenance/tables.sql" );
$currentTables = $this->getTables( $currentDB );
@@ -254,9 +256,10 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$maint = new FakeMaintenance();
$maint->loadParamsAndArgs( null, array( 'quiet' => 1 ) );
}
-
+
+ global $IP;
$db = new DatabaseSqliteStandalone( ':memory:' );
- $db->sourceFile( dirname( __FILE__ ) . "/sqlite/tables-$version.sql" );
+ $db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" );
$updater = DatabaseUpdater::newForDB( $db, false, $maint );
$updater->doUpdates( array( 'core' ) );
return $db;
@@ -266,6 +269,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$list = array_flip( $db->listTables() );
$excluded = array(
'math', // moved out of core in 1.18
+ 'trackbacks', // removed from core in 1.19
'searchindex',
'searchindex_content',
'searchindex_segments',
diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php
index d480ac6e..672e6645 100644
--- a/tests/phpunit/includes/db/DatabaseTest.php
+++ b/tests/phpunit/includes/db/DatabaseTest.php
@@ -2,12 +2,20 @@
/**
* @group Database
+ * @group DatabaseBase
*/
class DatabaseTest extends MediaWikiTestCase {
- var $db;
+ var $db, $functionTest = false;
function setUp() {
- $this->db = wfGetDB( DB_SLAVE );
+ $this->db = wfGetDB( DB_MASTER );
+ }
+
+ function tearDown() {
+ if ( $this->functionTest ) {
+ $this->dropFunctions();
+ $this->functionTest = false;
+ }
}
function testAddQuotesNull() {
@@ -90,6 +98,26 @@ class DatabaseTest extends MediaWikiTestCase {
$sql );
}
+ /**
+ * @group Broken
+ */
+ function testStoredFunctions() {
+ if ( !in_array( wfGetDB( DB_MASTER )->getType(), array( 'mysql', 'postgres' ) ) ) {
+ $this->markTestSkipped( 'MySQL or Postgres required' );
+ }
+ global $IP;
+ $this->dropFunctions();
+ $this->functionTest = true;
+ $this->assertTrue( $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" ) );
+ $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ );
+ $this->assertEquals( 42, $res->fetchObject()->test );
+ }
+
+ private function dropFunctions() {
+ $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function'
+ . ( $this->db->getType() == 'postgres' ? '()' : '' )
+ );
+ }
}
diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php
new file mode 100644
index 00000000..5a4e66d4
--- /dev/null
+++ b/tests/phpunit/includes/debug/MWDebugTest.php
@@ -0,0 +1,63 @@
+<?php
+
+class MWDebugTest extends MediaWikiTestCase {
+
+
+ function setUp() {
+ // Make sure MWDebug class is enabled
+ static $MWDebugEnabled = false;
+ if( !$MWDebugEnabled ) {
+ MWDebug::init();
+ $MWDebugEnabled = true;
+ }
+ /** Clear log before each test */
+ MWDebug::clearLog();
+ }
+
+ function testAddLog() {
+ MWDebug::log( 'logging a string' );
+ $this->assertEquals( array( array(
+ 'msg' => 'logging a string',
+ 'type' => 'log',
+ 'caller' => __METHOD__ ,
+ ) ),
+ MWDebug::getLog()
+ );
+ }
+
+ function testAddWarning() {
+ MWDebug::warning( 'Warning message' );
+ $this->assertEquals( array( array(
+ 'msg' => 'Warning message',
+ 'type' => 'warn',
+ 'caller' => 'MWDebug::warning',
+ ) ),
+ MWDebug::getLog()
+ );
+ }
+
+ function testAvoidDuplicateDeprecations() {
+ MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+ MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+
+ // assertCount() not available on WMF integration server
+ $this->assertEquals( 1,
+ count( MWDebug::getLog() ),
+ "Only one deprecated warning per function should be kept"
+ );
+ }
+
+ function testAvoidNonConsecutivesDuplicateDeprecations() {
+ MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+ MWDebug::warning( 'some warning' );
+ MWDebug::log( 'we could have logged something too' );
+ // Another deprecation
+ MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+
+ // assertCount() not available on WMF integration server
+ $this->assertEquals( 3,
+ count( MWDebug::getLog() ),
+ "Only one deprecated warning per function should be kept"
+ );
+ }
+}
diff --git a/tests/phpunit/includes/filerepo/FileBackendTest.php b/tests/phpunit/includes/filerepo/FileBackendTest.php
new file mode 100644
index 00000000..da44797a
--- /dev/null
+++ b/tests/phpunit/includes/filerepo/FileBackendTest.php
@@ -0,0 +1,1358 @@
+<?php
+
+/**
+ * @group FileRepo
+ * @group FileBackend
+ */
+class FileBackendTest extends MediaWikiTestCase {
+ private $backend, $multiBackend;
+ private $filesToPrune = array();
+ private $dirsToPrune = array();
+ private static $backendToUse;
+
+ function setUp() {
+ global $wgFileBackends;
+ parent::setUp();
+ $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . time() . '-' . mt_rand();
+ if ( $this->getCliArg( 'use-filebackend=' ) ) {
+ if ( self::$backendToUse ) {
+ $this->singleBackend = self::$backendToUse;
+ } else {
+ $name = $this->getCliArg( 'use-filebackend=' );
+ $useConfig = array();
+ foreach ( $wgFileBackends as $conf ) {
+ if ( $conf['name'] == $name ) {
+ $useConfig = $conf;
+ }
+ }
+ $useConfig['name'] = 'localtesting'; // swap name
+ $class = $conf['class'];
+ self::$backendToUse = new $class( $useConfig );
+ $this->singleBackend = self::$backendToUse;
+ }
+ } else {
+ $this->singleBackend = new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'fsLockManager',
+ 'containerPaths' => array(
+ 'unittest-cont1' => "{$tmpPrefix}-localtesting-cont1",
+ 'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2" )
+ ) );
+ }
+ $this->multiBackend = new FileBackendMultiWrite( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'fsLockManager',
+ 'backends' => array(
+ array(
+ 'name' => 'localmutlitesting1',
+ 'class' => 'FSFileBackend',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array(
+ 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti1-cont1",
+ 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti1-cont2" ),
+ 'isMultiMaster' => false
+ ),
+ array(
+ 'name' => 'localmutlitesting2',
+ 'class' => 'FSFileBackend',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array(
+ 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti2-cont1",
+ 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti2-cont2" ),
+ 'isMultiMaster' => true
+ )
+ )
+ ) );
+ $this->filesToPrune = array();
+ }
+
+ private function baseStorePath() {
+ return 'mwstore://localtesting';
+ }
+
+ private function backendClass() {
+ return get_class( $this->backend );
+ }
+
+ /**
+ * @dataProvider provider_testIsStoragePath
+ */
+ public function testIsStoragePath( $path, $isStorePath ) {
+ $this->assertEquals( $isStorePath, FileBackend::isStoragePath( $path ),
+ "FileBackend::isStoragePath on path '$path'" );
+ }
+
+ function provider_testIsStoragePath() {
+ return array(
+ array( 'mwstore://', true ),
+ array( 'mwstore://backend', true ),
+ array( 'mwstore://backend/container', true ),
+ array( 'mwstore://backend/container/', true ),
+ array( 'mwstore://backend/container/path', true ),
+ array( 'mwstore://backend//container/', true ),
+ array( 'mwstore://backend//container//', true ),
+ array( 'mwstore://backend//container//path', true ),
+ array( 'mwstore:///', true ),
+ array( 'mwstore:/', false ),
+ array( 'mwstore:', false ),
+ );
+ }
+
+ /**
+ * @dataProvider provider_testSplitStoragePath
+ */
+ public function testSplitStoragePath( $path, $res ) {
+ $this->assertEquals( $res, FileBackend::splitStoragePath( $path ),
+ "FileBackend::splitStoragePath on path '$path'" );
+ }
+
+ function provider_testSplitStoragePath() {
+ return array(
+ array( 'mwstore://backend/container', array( 'backend', 'container', '' ) ),
+ array( 'mwstore://backend/container/', array( 'backend', 'container', '' ) ),
+ array( 'mwstore://backend/container/path', array( 'backend', 'container', 'path' ) ),
+ array( 'mwstore://backend/container//path', array( 'backend', 'container', '/path' ) ),
+ array( 'mwstore://backend//container/path', array( null, null, null ) ),
+ array( 'mwstore://backend//container//path', array( null, null, null ) ),
+ array( 'mwstore://', array( null, null, null ) ),
+ array( 'mwstore://backend', array( null, null, null ) ),
+ array( 'mwstore:///', array( null, null, null ) ),
+ array( 'mwstore:/', array( null, null, null ) ),
+ array( 'mwstore:', array( null, null, null ) )
+ );
+ }
+
+ /**
+ * @dataProvider provider_normalizeStoragePath
+ */
+ public function testNormalizeStoragePath( $path, $res ) {
+ $this->assertEquals( $res, FileBackend::normalizeStoragePath( $path ),
+ "FileBackend::normalizeStoragePath on path '$path'" );
+ }
+
+ function provider_normalizeStoragePath() {
+ return array(
+ array( 'mwstore://backend/container', 'mwstore://backend/container' ),
+ array( 'mwstore://backend/container/', 'mwstore://backend/container' ),
+ array( 'mwstore://backend/container/path', 'mwstore://backend/container/path' ),
+ array( 'mwstore://backend/container//path', 'mwstore://backend/container/path' ),
+ array( 'mwstore://backend/container///path', 'mwstore://backend/container/path' ),
+ array( 'mwstore://backend/container///path//to///obj', 'mwstore://backend/container/path/to/obj',
+ array( 'mwstore://', null ),
+ array( 'mwstore://backend', null ),
+ array( 'mwstore://backend//container/path', null ),
+ array( 'mwstore://backend//container//path', null ),
+ array( 'mwstore:///', null ),
+ array( 'mwstore:/', null ),
+ array( 'mwstore:', null ), )
+ );
+ }
+
+ /**
+ * @dataProvider provider_testParentStoragePath
+ */
+ public function testParentStoragePath( $path, $res ) {
+ $this->assertEquals( $res, FileBackend::parentStoragePath( $path ),
+ "FileBackend::parentStoragePath on path '$path'" );
+ }
+
+ function provider_testParentStoragePath() {
+ return array(
+ array( 'mwstore://backend/container/path/to/obj', 'mwstore://backend/container/path/to' ),
+ array( 'mwstore://backend/container/path/to', 'mwstore://backend/container/path' ),
+ array( 'mwstore://backend/container/path', 'mwstore://backend/container' ),
+ array( 'mwstore://backend/container', null ),
+ array( 'mwstore://backend/container/path/to/obj/', 'mwstore://backend/container/path/to' ),
+ array( 'mwstore://backend/container/path/to/', 'mwstore://backend/container/path' ),
+ array( 'mwstore://backend/container/path/', 'mwstore://backend/container' ),
+ array( 'mwstore://backend/container/', null ),
+ );
+ }
+
+ /**
+ * @dataProvider provider_testExtensionFromPath
+ */
+ public function testExtensionFromPath( $path, $res ) {
+ $this->assertEquals( $res, FileBackend::extensionFromPath( $path ),
+ "FileBackend::extensionFromPath on path '$path'" );
+ }
+
+ function provider_testExtensionFromPath() {
+ return array(
+ array( 'mwstore://backend/container/path.txt', 'txt' ),
+ array( 'mwstore://backend/container/path.svg.png', 'png' ),
+ array( 'mwstore://backend/container/path', '' ),
+ array( 'mwstore://backend/container/path.', '' ),
+ );
+ }
+
+ /**
+ * @dataProvider provider_testStore
+ */
+ public function testStore( $op ) {
+ $this->filesToPrune[] = $op['src'];
+
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestStore( $op );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestStore( $op );
+ $this->filesToPrune[] = $op['src']; # avoid file leaking
+ $this->tearDownFiles();
+ }
+
+ function doTestStore( $op ) {
+ $backendName = $this->backendClass();
+
+ $source = $op['src'];
+ $dest = $op['dst'];
+ $this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+ file_put_contents( $source, "Unit test file" );
+
+ if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
+ $this->backend->store( $op );
+ }
+
+ $status = $this->backend->doOperation( $op );
+
+ $this->assertEquals( array(), $status->errors,
+ "Store from $source to $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( array(), $status->errors,
+ "Store from $source to $dest succeeded ($backendName)." );
+ $this->assertEquals( array( 0 => true ), $status->success,
+ "Store from $source to $dest has proper 'success' field in Status ($backendName)." );
+ $this->assertEquals( true, file_exists( $source ),
+ "Source file $source still exists ($backendName)." );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+ "Destination file $dest exists ($backendName)." );
+
+ $this->assertEquals( filesize( $source ),
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has correct size ($backendName)." );
+
+ $props1 = FSFile::getPropsFromPath( $source );
+ $props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
+ $this->assertEquals( $props1, $props2,
+ "Source and destination have the same props ($backendName)." );
+ }
+
+ public function provider_testStore() {
+ $cases = array();
+
+ $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath();
+ $toPath = $this->baseStorePath() . '/unittest-cont1/fun/obj1.txt';
+ $op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath );
+ $cases[] = array(
+ $op, // operation
+ $tmpName, // source
+ $toPath, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwrite'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $tmpName, // source
+ $toPath, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwriteSame'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $tmpName, // source
+ $toPath, // dest
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testCopy
+ */
+ public function testCopy( $op ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestCopy( $op );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestCopy( $op );
+ $this->tearDownFiles();
+ }
+
+ function doTestCopy( $op ) {
+ $backendName = $this->backendClass();
+
+ $source = $op['src'];
+ $dest = $op['dst'];
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+ $this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+
+ if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
+ $this->backend->copy( $op );
+ }
+
+ $status = $this->backend->doOperation( $op );
+
+ $this->assertEquals( array(), $status->errors,
+ "Copy from $source to $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Copy from $source to $dest succeeded ($backendName)." );
+ $this->assertEquals( array( 0 => true ), $status->success,
+ "Copy from $source to $dest has proper 'success' field in Status ($backendName)." );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $source ) ),
+ "Source file $source still exists ($backendName)." );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+ "Destination file $dest exists after copy ($backendName)." );
+
+ $this->assertEquals(
+ $this->backend->getFileSize( array( 'src' => $source ) ),
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has correct size ($backendName)." );
+
+ $props1 = $this->backend->getFileProps( array( 'src' => $source ) );
+ $props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
+ $this->assertEquals( $props1, $props2,
+ "Source and destination have the same props ($backendName)." );
+ }
+
+ public function provider_testCopy() {
+ $cases = array();
+
+ $source = $this->baseStorePath() . '/unittest-cont1/file.txt';
+ $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt';
+
+ $op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest );
+ $cases[] = array(
+ $op, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwrite'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwriteSame'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testMove
+ */
+ public function testMove( $op ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestMove( $op );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestMove( $op );
+ $this->tearDownFiles();
+ }
+
+ private function doTestMove( $op ) {
+ $backendName = $this->backendClass();
+
+ $source = $op['src'];
+ $dest = $op['dst'];
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+ $this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+
+ if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
+ $this->backend->copy( $op );
+ }
+
+ $status = $this->backend->doOperation( $op );
+ $this->assertEquals( array(), $status->errors,
+ "Move from $source to $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Move from $source to $dest succeeded ($backendName)." );
+ $this->assertEquals( array( 0 => true ), $status->success,
+ "Move from $source to $dest has proper 'success' field in Status ($backendName)." );
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ),
+ "Source file $source does not still exists ($backendName)." );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+ "Destination file $dest exists after move ($backendName)." );
+
+ $this->assertNotEquals(
+ $this->backend->getFileSize( array( 'src' => $source ) ),
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has correct size ($backendName)." );
+
+ $props1 = $this->backend->getFileProps( array( 'src' => $source ) );
+ $props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
+ $this->assertEquals( false, $props1['fileExists'],
+ "Source file does not exist accourding to props ($backendName)." );
+ $this->assertEquals( true, $props2['fileExists'],
+ "Destination file exists accourding to props ($backendName)." );
+ }
+
+ public function provider_testMove() {
+ $cases = array();
+
+ $source = $this->baseStorePath() . '/unittest-cont1/file.txt';
+ $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt';
+
+ $op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest );
+ $cases[] = array(
+ $op, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwrite'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwriteSame'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testDelete
+ */
+ public function testDelete( $op, $withSource, $okStatus ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestDelete( $op, $withSource, $okStatus );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestDelete( $op, $withSource, $okStatus );
+ $this->tearDownFiles();
+ }
+
+ private function doTestDelete( $op, $withSource, $okStatus ) {
+ $backendName = $this->backendClass();
+
+ $source = $op['src'];
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+
+ if ( $withSource ) {
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+ }
+
+ $status = $this->backend->doOperation( $op );
+ if ( $okStatus ) {
+ $this->assertEquals( array(), $status->errors,
+ "Deletion of file at $source succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Deletion of file at $source succeeded ($backendName)." );
+ $this->assertEquals( array( 0 => true ), $status->success,
+ "Deletion of file at $source has proper 'success' field in Status ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Deletion of file at $source failed ($backendName)." );
+ }
+
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ),
+ "Source file $source does not exist after move ($backendName)." );
+
+ $this->assertFalse(
+ $this->backend->getFileSize( array( 'src' => $source ) ),
+ "Source file $source has correct size (false) ($backendName)." );
+
+ $props1 = $this->backend->getFileProps( array( 'src' => $source ) );
+ $this->assertFalse( $props1['fileExists'],
+ "Source file $source does not exist according to props ($backendName)." );
+ }
+
+ public function provider_testDelete() {
+ $cases = array();
+
+ $source = $this->baseStorePath() . '/unittest-cont1/myfacefile.txt';
+
+ $op = array( 'op' => 'delete', 'src' => $source );
+ $cases[] = array(
+ $op, // operation
+ true, // with source
+ true // succeeds
+ );
+
+ $cases[] = array(
+ $op, // operation
+ false, // without source
+ false // fails
+ );
+
+ $op['ignoreMissingSource'] = true;
+ $cases[] = array(
+ $op, // operation
+ false, // without source
+ true // succeeds
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testCreate
+ */
+ public function testCreate( $op, $alreadyExists, $okStatus, $newSize ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
+ $this->tearDownFiles();
+ }
+
+ private function doTestCreate( $op, $alreadyExists, $okStatus, $newSize ) {
+ $backendName = $this->backendClass();
+
+ $dest = $op['dst'];
+ $this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+ $oldText = 'blah...blah...waahwaah';
+ if ( $alreadyExists ) {
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => $oldText, 'dst' => $dest ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $dest succeeded ($backendName)." );
+ }
+
+ $status = $this->backend->doOperation( $op );
+ if ( $okStatus ) {
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Creation of file at $dest succeeded ($backendName)." );
+ $this->assertEquals( array( 0 => true ), $status->success,
+ "Creation of file at $dest has proper 'success' field in Status ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Creation of file at $dest failed ($backendName)." );
+ }
+
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+ "Destination file $dest exists after creation ($backendName)." );
+
+ $props1 = $this->backend->getFileProps( array( 'src' => $dest ) );
+ $this->assertEquals( true, $props1['fileExists'],
+ "Destination file $dest exists according to props ($backendName)." );
+ if ( $okStatus ) { // file content is what we saved
+ $this->assertEquals( $newSize, $props1['size'],
+ "Destination file $dest has expected size according to props ($backendName)." );
+ $this->assertEquals( $newSize,
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has correct size ($backendName)." );
+ } else { // file content is some other previous text
+ $this->assertEquals( strlen( $oldText ), $props1['size'],
+ "Destination file $dest has original size according to props ($backendName)." );
+ $this->assertEquals( strlen( $oldText ),
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has original size according to props ($backendName)." );
+ }
+ }
+
+ /**
+ * @dataProvider provider_testCreate
+ */
+ public function provider_testCreate() {
+ $cases = array();
+
+ $dest = $this->baseStorePath() . '/unittest-cont2/myspacefile.txt';
+
+ $op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest );
+ $cases[] = array(
+ $op, // operation
+ false, // no dest already exists
+ true, // succeeds
+ strlen( $op['content'] )
+ );
+
+ $op2 = $op;
+ $op2['content'] = "\n";
+ $cases[] = array(
+ $op2, // operation
+ false, // no dest already exists
+ true, // succeeds
+ strlen( $op2['content'] )
+ );
+
+ $op2 = $op;
+ $op2['content'] = "fsf\n waf 3kt";
+ $cases[] = array(
+ $op2, // operation
+ true, // dest already exists
+ false, // fails
+ strlen( $op2['content'] )
+ );
+
+ $op2 = $op;
+ $op2['content'] = "egm'g gkpe gpqg eqwgwqg";
+ $op2['overwrite'] = true;
+ $cases[] = array(
+ $op2, // operation
+ true, // dest already exists
+ true, // succeeds
+ strlen( $op2['content'] )
+ );
+
+ $op2 = $op;
+ $op2['content'] = "39qjmg3-qg";
+ $op2['overwriteSame'] = true;
+ $cases[] = array(
+ $op2, // operation
+ true, // dest already exists
+ false, // succeeds
+ strlen( $op2['content'] )
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @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->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus );
+ $this->filesToPrune[] = $op['dst']; # avoid file leaking
+ $this->tearDownFiles();
+ }
+
+ public function doTestConcatenate( $params, $srcs, $srcsContent, $alreadyExists, $okStatus ) {
+ $backendName = $this->backendClass();
+
+ $expContent = '';
+ // Create sources
+ $ops = array();
+ foreach ( $srcs as $i => $source ) {
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+ $ops[] = array(
+ 'op' => 'create', // operation
+ 'dst' => $source, // source
+ 'content' => $srcsContent[$i]
+ );
+ $expContent .= $srcsContent[$i];
+ }
+ $status = $this->backend->doOperations( $ops );
+
+ $this->assertEquals( array(), $status->errors,
+ "Creation of source files succeeded ($backendName)." );
+
+ $dest = $params['dst'];
+ if ( $alreadyExists ) {
+ $ok = file_put_contents( $dest, 'blah...blah...waahwaah' ) !== false;
+ $this->assertEquals( true, $ok,
+ "Creation of file at $dest succeeded ($backendName)." );
+ } else {
+ $ok = file_put_contents( $dest, '' ) !== false;
+ $this->assertEquals( true, $ok,
+ "Creation of 0-byte file at $dest succeeded ($backendName)." );
+ }
+
+ // Combine the files into one
+ $status = $this->backend->concatenate( $params );
+ if ( $okStatus ) {
+ $this->assertEquals( array(), $status->errors,
+ "Creation of concat file at $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Creation of concat file at $dest succeeded ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Creation of concat file at $dest failed ($backendName)." );
+ }
+
+ if ( $okStatus ) {
+ $this->assertEquals( true, is_file( $dest ),
+ "Dest concat file $dest exists after creation ($backendName)." );
+ } else {
+ $this->assertEquals( true, is_file( $dest ),
+ "Dest concat file $dest exists after failed creation ($backendName)." );
+ }
+
+ $contents = file_get_contents( $dest );
+ $this->assertNotEquals( false, $contents, "File at $dest exists ($backendName)." );
+
+ if ( $okStatus ) {
+ $this->assertEquals( $expContent, $contents,
+ "Concat file at $dest has correct contents ($backendName)." );
+ } else {
+ $this->assertNotEquals( $expContent, $contents,
+ "Concat file at $dest has correct contents ($backendName)." );
+ }
+ }
+
+ function provider_testConcatenate() {
+ $cases = array();
+
+ $rand = mt_rand( 0, 2000000000 ) . time();
+ $dest = wfTempDir() . "/randomfile!$rand.txt";
+ $srcs = array(
+ $this->baseStorePath() . '/unittest-cont1/file1.txt',
+ $this->baseStorePath() . '/unittest-cont1/file2.txt',
+ $this->baseStorePath() . '/unittest-cont1/file3.txt',
+ $this->baseStorePath() . '/unittest-cont1/file4.txt',
+ $this->baseStorePath() . '/unittest-cont1/file5.txt',
+ $this->baseStorePath() . '/unittest-cont1/file6.txt',
+ $this->baseStorePath() . '/unittest-cont1/file7.txt',
+ $this->baseStorePath() . '/unittest-cont1/file8.txt',
+ $this->baseStorePath() . '/unittest-cont1/file9.txt',
+ $this->baseStorePath() . '/unittest-cont1/file10.txt'
+ );
+ $content = array(
+ 'egfage',
+ 'ageageag',
+ 'rhokohlr',
+ 'shgmslkg',
+ 'kenga',
+ 'owagmal',
+ 'kgmae',
+ 'g eak;g',
+ 'lkaem;a',
+ 'legma'
+ );
+ $params = array( 'srcs' => $srcs, 'dst' => $dest );
+
+ $cases[] = array(
+ $params, // operation
+ $srcs, // sources
+ $content, // content for each source
+ false, // no dest already exists
+ true, // succeeds
+ );
+
+ $cases[] = array(
+ $params, // operation
+ $srcs, // sources
+ $content, // content for each source
+ true, // dest already exists
+ false, // succeeds
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testGetFileStat
+ */
+ public function testGetFileStat( $path, $content, $alreadyExists ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileStat( $path, $content, $alreadyExists );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileStat( $path, $content, $alreadyExists );
+ $this->tearDownFiles();
+ }
+
+ private function doTestGetFileStat( $path, $content, $alreadyExists ) {
+ $backendName = $this->backendClass();
+
+ if ( $alreadyExists ) {
+ $this->prepare( array( 'dir' => dirname( $path ) ) );
+ $status = $this->backend->create( array( 'dst' => $path, 'content' => $content ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $path succeeded ($backendName)." );
+
+ $size = $this->backend->getFileSize( array( 'src' => $path ) );
+ $time = $this->backend->getFileTimestamp( array( 'src' => $path ) );
+ $stat = $this->backend->getFileStat( array( 'src' => $path ) );
+
+ $this->assertEquals( strlen( $content ), $size,
+ "Correct file size of '$path'" );
+ $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5,
+ "Correct file timestamp of '$path'" );
+
+ $size = $stat['size'];
+ $time = $stat['mtime'];
+ $this->assertEquals( strlen( $content ), $size,
+ "Correct file size of '$path'" );
+ $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5,
+ "Correct file timestamp of '$path'" );
+ } else {
+ $size = $this->backend->getFileSize( array( 'src' => $path ) );
+ $time = $this->backend->getFileTimestamp( array( 'src' => $path ) );
+ $stat = $this->backend->getFileStat( array( 'src' => $path ) );
+
+ $this->assertFalse( $size, "Correct file size of '$path'" );
+ $this->assertFalse( $time, "Correct file timestamp of '$path'" );
+ $this->assertFalse( $stat, "Correct file stat of '$path'" );
+ }
+ }
+
+ function provider_testGetFileStat() {
+ $cases = array();
+
+ $base = $this->baseStorePath();
+ $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents", true );
+ $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "", true );
+ $cases[] = array( "$base/unittest-cont1/b/some-diff_file.txt", null, false );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testGetFileContents
+ */
+ public function testGetFileContents( $source, $content ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileContents( $source, $content );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileContents( $source, $content );
+ $this->tearDownFiles();
+ }
+
+ public function doTestGetFileContents( $source, $content ) {
+ $backendName = $this->backendClass();
+
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => $content, 'dst' => $source ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Creation of file at $source succeeded with OK status ($backendName)." );
+
+ $newContents = $this->backend->getFileContents( array( 'src' => $source, 'latest' => 1 ) );
+ $this->assertNotEquals( false, $newContents,
+ "Read of file at $source succeeded ($backendName)." );
+
+ $this->assertEquals( $content, $newContents,
+ "Contents read match data at $source ($backendName)." );
+ }
+
+ function provider_testGetFileContents() {
+ $cases = array();
+
+ $base = $this->baseStorePath();
+ $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents" );
+ $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "more file contents" );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testGetLocalCopy
+ */
+ public function testGetLocalCopy( $source, $content ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetLocalCopy( $source, $content );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetLocalCopy( $source, $content );
+ $this->tearDownFiles();
+ }
+
+ public function doTestGetLocalCopy( $source, $content ) {
+ $backendName = $this->backendClass();
+
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => $content, 'dst' => $source ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+
+ $tmpFile = $this->backend->getLocalCopy( array( 'src' => $source ) );
+ $this->assertNotNull( $tmpFile,
+ "Creation of local copy of $source succeeded ($backendName)." );
+
+ $contents = file_get_contents( $tmpFile->getPath() );
+ $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." );
+ }
+
+ function provider_testGetLocalCopy() {
+ $cases = array();
+
+ $base = $this->baseStorePath();
+ $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" );
+ $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testGetLocalReference
+ */
+ public function testGetLocalReference( $source, $content ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetLocalReference( $source, $content );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetLocalReference( $source, $content );
+ $this->tearDownFiles();
+ }
+
+ private function doTestGetLocalReference( $source, $content ) {
+ $backendName = $this->backendClass();
+
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => $content, 'dst' => $source ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+
+ $tmpFile = $this->backend->getLocalReference( array( 'src' => $source ) );
+ $this->assertNotNull( $tmpFile,
+ "Creation of local copy of $source succeeded ($backendName)." );
+
+ $contents = file_get_contents( $tmpFile->getPath() );
+ $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." );
+ }
+
+ function provider_testGetLocalReference() {
+ $cases = array();
+
+ $base = $this->baseStorePath();
+ $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" );
+ $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testPrepareAndClean
+ */
+ public function testPrepareAndClean( $path, $isOK ) {
+ $this->backend = $this->singleBackend;
+ $this->doTestPrepareAndClean( $path, $isOK );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->doTestPrepareAndClean( $path, $isOK );
+ $this->tearDownFiles();
+ }
+
+ function provider_testPrepareAndClean() {
+ $base = $this->baseStorePath();
+ return array(
+ array( "$base/unittest-cont1/a/z/some_file1.txt", true ),
+ array( "$base/unittest-cont2/a/z/some_file2.txt", true ),
+ # Specific to FS backend with no basePath field set
+ #array( "$base/unittest-cont3/a/z/some_file3.txt", false ),
+ );
+ }
+
+ function doTestPrepareAndClean( $path, $isOK ) {
+ $backendName = $this->backendClass();
+
+ $status = $this->prepare( array( 'dir' => dirname( $path ) ) );
+ if ( $isOK ) {
+ $this->assertEquals( array(), $status->errors,
+ "Preparing dir $path succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Preparing dir $path succeeded ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Preparing dir $path failed ($backendName)." );
+ }
+
+ $status = $this->backend->clean( array( 'dir' => dirname( $path ) ) );
+ if ( $isOK ) {
+ $this->assertEquals( array(), $status->errors,
+ "Cleaning dir $path succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Cleaning dir $path succeeded ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Cleaning dir $path failed ($backendName)." );
+ }
+ }
+
+ // @TODO: testSecure
+
+ public function testDoOperations() {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestDoOperations();
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestDoOperations();
+ $this->tearDownFiles();
+
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestDoOperationsFailing();
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestDoOperationsFailing();
+ $this->tearDownFiles();
+
+ // @TODO: test some cases where the ops should fail
+ }
+
+ function doTestDoOperations() {
+ $base = $this->baseStorePath();
+
+ $fileA = "$base/unittest-cont1/a/b/fileA.txt";
+ $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
+ $fileB = "$base/unittest-cont1/a/b/fileB.txt";
+ $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
+ $fileC = "$base/unittest-cont1/a/b/fileC.txt";
+ $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
+ $fileD = "$base/unittest-cont1/a/b/fileD.txt";
+
+ $this->prepare( array( 'dir' => dirname( $fileA ) ) );
+ $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
+ $this->prepare( array( 'dir' => dirname( $fileB ) ) );
+ $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) );
+ $this->prepare( array( 'dir' => dirname( $fileC ) ) );
+ $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) );
+
+ $status = $this->backend->doOperations( array(
+ array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>)
+ array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty>
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwrite' => 1 ),
+ // Now: A:<A>, B:<B>, C:<empty>, D:<A>
+ array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ),
+ // Now: A:<A>, B:<empty>, C:<B>, D:<A>
+ array( 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ),
+ // Now: A:<A>, B:<empty>, C:<B>, D:<empty>
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwrite' => 1 ),
+ // Now: A:<B>, B:<empty>, C:<empty>, D:<empty>
+ array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ),
+ // Now: A:<B>, B:<empty>, C:<B>, D:<empty>
+ array( 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ),
+ // Now: A:<empty>, B:<empty>, C:<B>, D:<empty>
+ array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
+ // Does nothing
+ array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
+ // Does nothing
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
+ // Does nothing
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
+ // Does nothing
+ array( 'op' => 'null' ),
+ // Does nothing
+ ) );
+
+ $this->assertEquals( array(), $status->errors, "Operation batch succeeded" );
+ $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
+ $this->assertEquals( 13, count( $status->success ),
+ "Operation batch has correct success array" );
+
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ),
+ "File does not exist at $fileA" );
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
+ "File does not exist at $fileB" );
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
+ "File does not exist at $fileD" );
+
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
+ "File exists at $fileC" );
+ $this->assertEquals( $fileBContents,
+ $this->backend->getFileContents( array( 'src' => $fileC ) ),
+ "Correct file contents of $fileC" );
+ $this->assertEquals( strlen( $fileBContents ),
+ $this->backend->getFileSize( array( 'src' => $fileC ) ),
+ "Correct file size of $fileC" );
+ $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
+ $this->backend->getFileSha1Base36( array( 'src' => $fileC ) ),
+ "Correct file SHA-1 of $fileC" );
+ }
+
+ function doTestDoOperationsFailing() {
+ $base = $this->baseStorePath();
+
+ $fileA = "$base/unittest-cont2/a/b/fileA.txt";
+ $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
+ $fileB = "$base/unittest-cont2/a/b/fileB.txt";
+ $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
+ $fileC = "$base/unittest-cont2/a/b/fileC.txt";
+ $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
+ $fileD = "$base/unittest-cont2/a/b/fileD.txt";
+
+ $this->prepare( array( 'dir' => dirname( $fileA ) ) );
+ $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
+ $this->prepare( array( 'dir' => dirname( $fileB ) ) );
+ $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) );
+ $this->prepare( array( 'dir' => dirname( $fileC ) ) );
+ $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) );
+
+ $status = $this->backend->doOperations( array(
+ array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>)
+ array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty>
+ array( 'op' => 'copy', 'src' => $fileB, 'dst' => $fileD, 'overwrite' => 1 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<B>
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
+ array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC, 'overwriteSame' => 1 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
+ array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileA, 'overwrite' => 1 ),
+ // Now: A:<B>, B:<empty>, C:<A>, D:<empty>
+ array( 'op' => 'delete', 'src' => $fileD ),
+ // Now: A:<B>, B:<empty>, C:<A>, D:<empty>
+ array( 'op' => 'null' ),
+ // Does nothing
+ ), array( 'force' => 1 ) );
+
+ $this->assertNotEquals( array(), $status->errors, "Operation had warnings" );
+ $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
+ $this->assertEquals( 8, count( $status->success ),
+ "Operation batch has correct success array" );
+
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
+ "File does not exist at $fileB" );
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
+ "File does not exist at $fileD" );
+
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileA ) ),
+ "File does not exist at $fileA" );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
+ "File exists at $fileC" );
+ $this->assertEquals( $fileBContents,
+ $this->backend->getFileContents( array( 'src' => $fileA ) ),
+ "Correct file contents of $fileA" );
+ $this->assertEquals( strlen( $fileBContents ),
+ $this->backend->getFileSize( array( 'src' => $fileA ) ),
+ "Correct file size of $fileA" );
+ $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
+ $this->backend->getFileSha1Base36( array( 'src' => $fileA ) ),
+ "Correct file SHA-1 of $fileA" );
+ }
+
+ public function testGetFileList() {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileList();
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileList();
+ $this->tearDownFiles();
+ }
+
+ private function doTestGetFileList() {
+ $backendName = $this->backendClass();
+
+ $base = $this->baseStorePath();
+ $files = array(
+ "$base/unittest-cont1/test1.txt",
+ "$base/unittest-cont1/test2.txt",
+ "$base/unittest-cont1/test3.txt",
+ "$base/unittest-cont1/subdir1/test1.txt",
+ "$base/unittest-cont1/subdir1/test2.txt",
+ "$base/unittest-cont1/subdir2/test3.txt",
+ "$base/unittest-cont1/subdir2/test4.txt",
+ "$base/unittest-cont1/subdir2/subdir/test1.txt",
+ "$base/unittest-cont1/subdir2/subdir/test2.txt",
+ "$base/unittest-cont1/subdir2/subdir/test3.txt",
+ "$base/unittest-cont1/subdir2/subdir/test4.txt",
+ "$base/unittest-cont1/subdir2/subdir/test5.txt",
+ "$base/unittest-cont1/subdir2/subdir/sub/test0.txt",
+ "$base/unittest-cont1/subdir2/subdir/sub/120-px-file.txt",
+ );
+
+ // Add the files
+ $ops = array();
+ foreach ( $files as $file ) {
+ $this->prepare( array( 'dir' => dirname( $file ) ) );
+ $ops[] = array( 'op' => 'create', 'content' => 'xxy', 'dst' => $file );
+ }
+ $status = $this->backend->doOperations( $ops );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of files succeeded ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Creation of files succeeded with OK status ($backendName)." );
+
+ // Expected listing
+ $expected = array(
+ "test1.txt",
+ "test2.txt",
+ "test3.txt",
+ "subdir1/test1.txt",
+ "subdir1/test2.txt",
+ "subdir2/test3.txt",
+ "subdir2/test4.txt",
+ "subdir2/subdir/test1.txt",
+ "subdir2/subdir/test2.txt",
+ "subdir2/subdir/test3.txt",
+ "subdir2/subdir/test4.txt",
+ "subdir2/subdir/test5.txt",
+ "subdir2/subdir/sub/test0.txt",
+ "subdir2/subdir/sub/120-px-file.txt",
+ );
+ sort( $expected );
+
+ // Actual listing (no trailing slash)
+ $list = array();
+ $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1" ) );
+ foreach ( $iter as $file ) {
+ $list[] = $file;
+ }
+ sort( $list );
+
+ $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
+
+ // Actual listing (with trailing slash)
+ $list = array();
+ $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/" ) );
+ foreach ( $iter as $file ) {
+ $list[] = $file;
+ }
+ sort( $list );
+
+ $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
+
+ // Expected listing
+ $expected = array(
+ "test1.txt",
+ "test2.txt",
+ "test3.txt",
+ "test4.txt",
+ "test5.txt",
+ "sub/test0.txt",
+ "sub/120-px-file.txt",
+ );
+ sort( $expected );
+
+ // Actual listing (no trailing slash)
+ $list = array();
+ $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir" ) );
+ foreach ( $iter as $file ) {
+ $list[] = $file;
+ }
+ sort( $list );
+
+ $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
+
+ // Actual listing (with trailing slash)
+ $list = array();
+ $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir/" ) );
+ foreach ( $iter as $file ) {
+ $list[] = $file;
+ }
+ sort( $list );
+
+ $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
+
+ // Actual listing (using iterator second time)
+ $list = array();
+ foreach ( $iter as $file ) {
+ $list[] = $file;
+ }
+ sort( $list );
+
+ $this->assertEquals( $expected, $list, "Correct file listing ($backendName), second iteration." );
+
+ foreach ( $files as $file ) { // clean up
+ $this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) );
+ }
+
+ $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/not/exists" ) );
+ foreach ( $iter as $iter ) {} // no errors
+ }
+
+ // test helper wrapper for backend prepare() function
+ private function prepare( array $params ) {
+ $this->dirsToPrune[] = $params['dir'];
+ return $this->backend->prepare( $params );
+ }
+
+ function tearDownFiles() {
+ foreach ( $this->filesToPrune as $file ) {
+ @unlink( $file );
+ }
+ $containers = array( 'unittest-cont1', 'unittest-cont2', 'unittest-cont3' );
+ foreach ( $containers as $container ) {
+ $this->deleteFiles( $container );
+ }
+ foreach ( $this->dirsToPrune as $dir ) {
+ $this->recursiveClean( $dir );
+ }
+ $this->filesToPrune = $this->dirsToPrune = array();
+ }
+
+ private function deleteFiles( $container ) {
+ $base = $this->baseStorePath();
+ $iter = $this->backend->getFileList( array( 'dir' => "$base/$container" ) );
+ if ( $iter ) {
+ foreach ( $iter as $file ) {
+ $this->backend->delete( array( 'src' => "$base/$container/$file" ), array( 'force' => 1 ) );
+ }
+ }
+ }
+
+ private function recursiveClean( $dir ) {
+ do {
+ if ( !$this->backend->clean( array( 'dir' => $dir ) )->isOK() ) {
+ break;
+ }
+ } while ( $dir = FileBackend::parentStoragePath( $dir ) );
+ }
+
+ function tearDown() {
+ parent::tearDown();
+ }
+}
diff --git a/tests/phpunit/includes/filerepo/FileRepoTest.php b/tests/phpunit/includes/filerepo/FileRepoTest.php
new file mode 100644
index 00000000..0f023138
--- /dev/null
+++ b/tests/phpunit/includes/filerepo/FileRepoTest.php
@@ -0,0 +1,41 @@
+<?php
+
+class FileRepoTest extends MediaWikiTestCase {
+
+ /**
+ * @expectedException MWException
+ */
+ function testFileRepoConstructionOptionCanNotBeNull() {
+ $f = new FileRepo();
+ }
+ /**
+ * @expectedException MWException
+ */
+ function testFileRepoConstructionOptionCanNotBeAnEmptyArray() {
+ $f = new FileRepo( array() );
+ }
+ /**
+ * @expectedException MWException
+ */
+ function testFileRepoConstructionOptionNeedNameKey() {
+ $f = new FileRepo( array(
+ 'backend' => 'foobar'
+ ) );
+ }
+ /**
+ * @expectedException MWException
+ */
+ function testFileRepoConstructionOptionNeedBackendKey() {
+ $f = new FileRepo( array(
+ 'name' => 'foobar'
+ ) );
+ }
+
+ function testFileRepoConstructionWithRequiredOptions() {
+ $f = new FileRepo( array(
+ 'name' => 'FileRepoTestRepository',
+ 'backend' => 'local-backend',
+ ));
+ $this->assertInstanceOf( 'FileRepo', $f );
+ }
+}
diff --git a/tests/phpunit/includes/filerepo/StoreBatchTest.php b/tests/phpunit/includes/filerepo/StoreBatchTest.php
new file mode 100644
index 00000000..6abceeb3
--- /dev/null
+++ b/tests/phpunit/includes/filerepo/StoreBatchTest.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * @group FileRepo
+ */
+class StoreBatchTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ global $wgFileBackends;
+ parent::setUp();
+
+ # Forge a FSRepo object to not have to rely on local wiki settings
+ $tmpPrefix = wfTempDir() . '/storebatch-test-' . time() . '-' . mt_rand();
+ if ( $this->getCliArg( 'use-filebackend=' ) ) {
+ $name = $this->getCliArg( 'use-filebackend=' );
+ $useConfig = array();
+ foreach ( $wgFileBackends as $conf ) {
+ if ( $conf['name'] == $name ) {
+ $useConfig = $conf;
+ }
+ }
+ $useConfig['name'] = 'local-testing'; // swap name
+ $class = $useConfig['class'];
+ $backend = new $class( $useConfig );
+ } else {
+ $backend = new FSFileBackend( array(
+ 'name' => 'local-testing',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array(
+ 'unittests-public' => "{$tmpPrefix}-public",
+ 'unittests-thumb' => "{$tmpPrefix}-thumb",
+ 'unittests-temp' => "{$tmpPrefix}-temp",
+ 'unittests-deleted' => "{$tmpPrefix}-deleted",
+ )
+ ) );
+ }
+ $this->repo = new FileRepo( array(
+ 'name' => 'unittests',
+ 'backend' => $backend
+ ) );
+
+ $this->date = gmdate( "YmdHis" );
+ $this->createdFiles = array();
+ }
+
+ /**
+ * Store a file or virtual URL source into a media file name.
+ *
+ * @param $originalName string The title of the image
+ * @param $srcPath string The filepath or virtual URL
+ * @param $flags integer Flags to pass into repo::store().
+ */
+ private function storeit($originalName, $srcPath, $flags) {
+ $hashPath = $this->repo->getHashPath( $originalName );
+ $dstRel = "$hashPath{$this->date}!$originalName";
+ $dstUrlRel = $hashPath . $this->date . '!' . rawurlencode( $originalName );
+
+ $result = $this->repo->store( $srcPath, 'temp', $dstRel, $flags );
+ $result->value = $this->repo->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
+ $this->createdFiles[] = $result->value;
+ return $result;
+ }
+
+ /**
+ * Test storing a file using different flags.
+ *
+ * @param $fn string The title of the image
+ * @param $infn string The name of the file (in the filesystem)
+ * @param $otherfn string The name of the different file (in the filesystem)
+ * @param $fromrepo logical 'true' if we want to copy from a virtual URL out of the Repo.
+ */
+ private function storecohort($fn, $infn, $otherfn, $fromrepo) {
+ $f = $this->storeit( $fn, $infn, 0 );
+ $this->assertTrue( $f->isOK(), 'failed to store a new file' );
+ $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" );
+ $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" );
+ if ( $fromrepo ) {
+ $f = $this->storeit( "Other-$fn", $infn, FileRepo::OVERWRITE);
+ $infn = $f->value;
+ }
+ // This should work because we're allowed to overwrite
+ $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE );
+ $this->assertTrue( $f->isOK(), 'We should be allowed to overwrite' );
+ $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" );
+ $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" );
+ // This should fail because we're overwriting.
+ $f = $this->storeit( $fn, $infn, 0 );
+ $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite' );
+ $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" );
+ $this->assertEquals( $f->successCount, 0 , "counts wrong {$f->successCount} {$f->failCount}" );
+ // This should succeed because we're overwriting the same content.
+ $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE_SAME );
+ $this->assertTrue( $f->isOK(), 'We should be able to overwrite the same content' );
+ $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" );
+ $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" );
+ // This should fail because we're overwriting different content.
+ if ( $fromrepo ) {
+ $f = $this->storeit( "Other-$fn", $otherfn, FileRepo::OVERWRITE);
+ $otherfn = $f->value;
+ }
+ $f = $this->storeit( $fn, $otherfn, FileRepo::OVERWRITE_SAME );
+ $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite different content' );
+ $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" );
+ $this->assertEquals( $f->successCount, 0 , "counts wrong {$f->successCount} {$f->failCount}" );
+ }
+
+ public function teststore() {
+ global $IP;
+ $this->storecohort( "Test1.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", false );
+ $this->storecohort( "Test2.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", true );
+ }
+
+ public 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 ) );
+ }
+ }
+ parent::tearDown();
+ }
+}
diff --git a/tests/phpunit/includes/json/ServicesJsonTest.php b/tests/phpunit/includes/json/ServicesJsonTest.php
new file mode 100644
index 00000000..8f2421a2
--- /dev/null
+++ b/tests/phpunit/includes/json/ServicesJsonTest.php
@@ -0,0 +1,93 @@
+<?php
+/*
+ * Test cases for our Services_Json library. Requires PHP json support as well,
+ * so we can compare output
+ */
+class ServicesJsonTest extends MediaWikiTestCase {
+ /**
+ * Test to make sure core json_encode() and our Services_Json()->encode()
+ * produce the same output
+ *
+ * @dataProvider provideValuesToEncode
+ */
+ public function testJsonEncode( $input, $desc ) {
+ if ( !function_exists( 'json_encode' ) ) {
+ $this->markTestIncomplete( 'No PHP json support, unable to test' );
+ return;
+ } elseif( strtolower( json_encode( "\xf0\xa0\x80\x80" ) ) != '"\ud840\udc00"' ) {
+ $this->markTestIncomplete( 'Have buggy PHP json support, unable to test' );
+ return;
+ } else {
+ $jsonObj = new Services_JSON();
+ $this->assertEquals(
+ $jsonObj->encode( $input ),
+ json_encode( $input ),
+ $desc
+ );
+ }
+ }
+
+ /**
+ * Test to make sure core json_decode() and our Services_Json()->decode()
+ * produce the same output
+ *
+ * @dataProvider provideValuesToDecode
+ */
+ public function testJsonDecode( $input, $desc ) {
+ if ( !function_exists( 'json_decode' ) ) {
+ $this->markTestIncomplete( 'No PHP json support, unable to test' );
+ return;
+ } else {
+ $jsonObj = new Services_JSON();
+ $this->assertEquals(
+ $jsonObj->decode( $input ),
+ json_decode( $input ),
+ $desc
+ );
+ }
+ }
+
+ function provideValuesToEncode() {
+ $obj = new stdClass();
+ $obj->property = 'value';
+ $obj->property2 = null;
+ $obj->property3 = 1.234;
+ return array(
+ array( 1, 'basic integer' ),
+ array( -1, 'negative integer' ),
+ array( 1.1, 'basic float' ),
+ array( true, 'basic bool true' ),
+ array( false, 'basic bool false' ),
+ array( 'some string', 'basic string test' ),
+ array( "some string\nwith newline", 'newline string test' ),
+ array( '♥ü', 'unicode string test' ),
+ array( array( 'some', 'string', 'values' ), 'basic array of strings' ),
+ array( array( 'key1' => 'val1', 'key2' => 'val2' ), 'array with string keys' ),
+ array( array( 1 => 'val1', 3 => 'val2', '2' => 'val3' ), 'out of order numbered array test' ),
+ array( array(), 'empty array test' ),
+ array( $obj, 'basic object test' ),
+ array( new stdClass, 'empty object test' ),
+ array( null, 'null test' ),
+ );
+ }
+
+ function provideValuesToDecode() {
+ return array(
+ array( '1', 'basic integer' ),
+ array( '-1', 'negative integer' ),
+ array( '1.1', 'basic float' ),
+ array( '1.1e1', 'scientific float' ),
+ array( 'true', 'basic bool true' ),
+ array( 'false', 'basic bool false' ),
+ array( '"some string"', 'basic string test' ),
+ array( '"some string\nwith newline"', 'newline string test' ),
+ array( '"♥ü"', 'unicode character string test' ),
+ array( '"\u2665"', 'unicode \\u string test' ),
+ array( '["some","string","values"]', 'basic array of strings' ),
+ array( '[]', 'empty array test' ),
+ array( '{"key":"value"}', 'Basic key => value test' ),
+ array( '{}', 'empty object test' ),
+ array( 'null', 'null test' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
index aa05500e..d2bfeedf 100644
--- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
+++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
@@ -84,6 +84,13 @@ class JavaScriptMinifierTest extends MediaWikiTestCase {
// And also per spec unicode char escape values should work in identifiers,
// as long as it's a valid char. In future it might get normalized.
array( "var Ka\\u015dSkatolVal = {}", 'var Ka\\u015dSkatolVal={}'),
+
+ /* Some structures that might look invalid at first sight */
+ array( "var a = 5.;", "var a=5.;" ),
+ array( "5.0.toString();", "5.0.toString();" ),
+ array( "5..toString();", "5..toString();" ),
+ array( "5...toString();", false ),
+ array( "5.\n.toString();", '5..toString();' ),
);
}
@@ -102,4 +109,40 @@ class JavaScriptMinifierTest extends MediaWikiTestCase {
$this->assertEquals( $expectedOutput, $minified, "Minified output should be in the form expected." );
}
+
+ /**
+ * @dataProvider provideBug32548
+ */
+ function testBug32548Exponent($num) {
+ // Long line breaking was being incorrectly done between the base and
+ // exponent part of a number, causing a syntax error. The line should
+ // instead break at the start of the number.
+ $prefix = 'var longVarName' . str_repeat('_', 973) . '=';
+ $suffix = ',shortVarName=0;';
+
+ $input = $prefix . $num . $suffix;
+ $expected = $prefix . "\n" . $num . $suffix;
+
+ $minified = JavaScriptMinifier::minify( $input );
+
+ $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent");
+ }
+
+ function provideBug32548() {
+ return array(
+ array(
+ // This one gets interpreted all together by the prior code;
+ // no break at the 'E' happens.
+ '1.23456789E55',
+ ),
+ array(
+ // This one breaks under the bad code; splits between 'E' and '+'
+ '1.23456789E+5',
+ ),
+ array(
+ // This one breaks under the bad code; splits between 'E' and '-'
+ '1.23456789E-5',
+ ),
+ );
+ }
}
diff --git a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
index a0d5cd86..f4f52dd8 100644
--- a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
+++ b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
@@ -14,10 +14,15 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
* translation (to en) where XMP should win.
*/
public function testMultilingualCascade() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
+ }
+ if ( !wfDl( 'xml' ) ) {
+ $this->markTestSkipped( "This test needs the xml extension." );
}
+ global $wgShowEXIF;
+ $oldExif = $wgShowEXIF;
+ $wgShowEXIF = true;
$meta = BitmapMetadataHandler::Jpeg( $this->filePath .
'/Xmp-exif-multilingual_test.jpg' );
@@ -32,6 +37,8 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
'Did not extract any ImageDescription info?!' );
$this->assertEquals( $expected, $meta['ImageDescription'] );
+
+ $wgShowEXIF = $oldExif;
}
/**
@@ -49,6 +56,16 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
$meta['JPEGFileComment'][0] );
}
+ /**
+ * Make sure a bad iptc block doesn't stop the other metadata
+ * from being extracted.
+ */
+ public function testBadIPTC() {
+ $meta = BitmapMetadataHandler::Jpeg( $this->filePath .
+ 'iptc-invalid-psir.jpg' );
+ $this->assertEquals( 'Created with GIMP', $meta['JPEGFileComment'][0] );
+ }
+
public function testIPTCDates() {
$meta = BitmapMetadataHandler::Jpeg( $this->filePath .
'iptc-timetest.jpg' );
@@ -95,6 +112,9 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
}
public function testPNGXMP() {
+ if ( !wfDl( 'xml' ) ) {
+ $this->markTestSkipped( "This test needs the xml extension." );
+ }
$handler = new BitmapMetadataHandler();
$result = $handler->png( $this->filePath . 'xmp.png' );
$expected = array (
diff --git a/tests/phpunit/includes/media/BitmapScalingTest.php b/tests/phpunit/includes/media/BitmapScalingTest.php
index 5bcd3232..11d9dc47 100644
--- a/tests/phpunit/includes/media/BitmapScalingTest.php
+++ b/tests/phpunit/includes/media/BitmapScalingTest.php
@@ -3,13 +3,16 @@
class BitmapScalingTest extends MediaWikiTestCase {
function setUp() {
- global $wgMaxImageArea;
+ global $wgMaxImageArea, $wgCustomConvertCommand;
$this->oldMaxImageArea = $wgMaxImageArea;
+ $this->oldCustomConvertCommand = $wgCustomConvertCommand;
$wgMaxImageArea = 1.25e7; // 3500x3500
+ $wgCustomConvertCommand = 'dummy'; // Set so that we don't get client side rendering
}
function tearDown() {
- global $wgMaxImageArea;
+ global $wgMaxImageArea, $wgCustomConvertCommand;
$wgMaxImageArea = $this->oldMaxImageArea;
+ $wgCustomConvertCommand = $this->oldCustomConvertCommand;
}
/**
* @dataProvider provideNormaliseParams
@@ -105,14 +108,22 @@ class BitmapScalingTest extends MediaWikiTestCase {
$file = new FakeDimensionFile( array( 4000, 4000 ) );
$handler = new BitmapHandler;
$params = array( 'width' => '3700' ); // Still bigger than max size.
- $this->assertFalse( $handler->normaliseParams( $file, $params ) );
+ $this->assertEquals( 'TransformParameterError',
+ get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
}
function testTooBigMustRenderImage() {
$file = new FakeDimensionFile( array( 4000, 4000 ) );
$file->mustRender = true;
$handler = new BitmapHandler;
$params = array( 'width' => '5000' ); // Still bigger than max size.
- $this->assertFalse( $handler->normaliseParams( $file, $params ) );
+ $this->assertEquals( 'TransformParameterError',
+ get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
+ }
+
+ function testImageArea() {
+ $file = new FakeDimensionFile( array( 7, 9 ) );
+ $handler = new BitmapHandler;
+ $this->assertEquals( 63, $handler->getImageArea( $file ) );
}
}
@@ -120,7 +131,8 @@ class FakeDimensionFile extends File {
public $mustRender = false;
public function __construct( $dimensions ) {
- parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), null );
+ parent::__construct( Title::makeTitle( NS_FILE, 'Test' ),
+ new NullRepo( null ) );
$this->dimensions = $dimensions;
}
@@ -133,4 +145,7 @@ class FakeDimensionFile extends File {
public function mustRender() {
return $this->mustRender;
}
+ public function getPath() {
+ return '';
+ }
}
diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php
index 4282d3c8..b2f6b7ba 100644
--- a/tests/phpunit/includes/media/ExifBitmapTest.php
+++ b/tests/phpunit/includes/media/ExifBitmapTest.php
@@ -1,4 +1,5 @@
<?php
+
class ExifBitmapTest extends MediaWikiTestCase {
public function setUp() {
@@ -17,42 +18,23 @@ class ExifBitmapTest extends MediaWikiTestCase {
}
public function testIsOldBroken() {
- if ( !wfDl( 'exif' ) ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE );
$this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
}
public function testIsBrokenFile() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE );
$this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
}
public function testIsInvalid() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' );
$this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
}
public function testGoodMetadata() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
$res = $this->handler->isMetadataValid( null, $meta );
$this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
}
public function testIsOldGood() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}';
$res = $this->handler->isMetadataValid( null, $meta );
$this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
@@ -60,10 +42,6 @@ class ExifBitmapTest extends MediaWikiTestCase {
// Handle metadata from paged tiff handler (gotten via instant commons)
// gracefully.
public function testPagedTiffHandledGracefully() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$meta = 'a:6:{s:9:"page_data";a:1:{i:1;a:5:{s:5:"width";i:643;s:6:"height";i:448;s:5:"alpha";s:4:"true";s:4:"page";i:1;s:6:"pixels";i:288064;}}s:10:"page_count";i:1;s:10:"first_page";i:1;s:9:"last_page";i:1;s:4:"exif";a:9:{s:10:"ImageWidth";i:643;s:11:"ImageLength";i:448;s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:4;s:12:"RowsPerStrip";i:50;s:19:"PlanarConfiguration";i:1;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}s:21:"TIFF_METADATA_VERSION";s:3:"1.4";}';
$res = $this->handler->isMetadataValid( null, $meta );
$this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
diff --git a/tests/phpunit/includes/media/ExifRotationTest.php b/tests/phpunit/includes/media/ExifRotationTest.php
index 639091d0..25149a05 100644
--- a/tests/phpunit/includes/media/ExifRotationTest.php
+++ b/tests/phpunit/includes/media/ExifRotationTest.php
@@ -5,15 +5,26 @@
*/
class ExifRotationTest extends MediaWikiTestCase {
+ /** track directories creations. Content will be deleted. */
+ private $createdDirs = array();
+
function setUp() {
parent::setUp();
- $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
$this->handler = new BitmapHandler();
- $this->repo = new FSRepo(array(
- 'name' => 'temp',
- 'directory' => wfTempDir() . '/exif-test-' . time() . '-' . mt_rand(),
- 'url' => 'http://localhost/thumbtest'
- ));
+ $filePath = dirname( __FILE__ ) . '/../../data/media';
+
+ $tmpDir = wfTempDir() . '/exif-test-' . time() . '-' . mt_rand();
+ $this->createdDirs[] = $tmpDir;
+
+ $this->repo = new FSRepo( array(
+ 'name' => 'temp',
+ 'url' => 'http://localhost/thumbtest',
+ 'backend' => new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array( 'temp-thumb' => $tmpDir, 'data' => $filePath )
+ ) )
+ ) );
if ( !wfDl( 'exif' ) ) {
$this->markTestSkipped( "This test needs the exif extension." );
}
@@ -25,10 +36,23 @@ class ExifRotationTest extends MediaWikiTestCase {
$this->oldAuto = $wgEnableAutoRotation;
$wgEnableAutoRotation = true;
}
+
public function tearDown() {
global $wgShowEXIF, $wgEnableAutoRotation;
$wgShowEXIF = $this->show;
$wgEnableAutoRotation = $this->oldAuto;
+
+ $this->tearDownFiles();
+ }
+
+ private function tearDownFiles() {
+ foreach( $this->createdDirs as $dir ) {
+ wfRecursiveRemoveDir( $dir );
+ }
+ }
+
+ function __destruct() {
+ $this->tearDownFiles();
}
/**
@@ -39,7 +63,7 @@ class ExifRotationTest extends MediaWikiTestCase {
if ( !BitmapHandler::canRotate() ) {
$this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." );
}
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type );
+ $file = $this->dataFile( $name, $type );
$this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
$this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
}
@@ -66,13 +90,13 @@ class ExifRotationTest extends MediaWikiTestCase {
throw new MWException('bogus test data format ' . $size);
}
- $file = $this->localFile( $name, $type );
- $thumb = $file->transform( $params, File::RENDER_NOW );
+ $file = $this->dataFile( $name, $type );
+ $thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE );
$this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" );
$this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" );
- $gis = getimagesize( $thumb->getPath() );
+ $gis = getimagesize( $thumb->getLocalCopyPath() );
if ($out[0] > $info['width']) {
// Physical image won't be scaled bigger than the original.
$this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size");
@@ -84,8 +108,9 @@ class ExifRotationTest extends MediaWikiTestCase {
}
}
- private function localFile( $name, $type ) {
- return new UnregisteredLocalFile( false, $this->repo, $this->filePath . $name, $type );
+ private function dataFile( $name, $type ) {
+ return new UnregisteredLocalFile( false, $this->repo,
+ "mwstore://localtesting/data/$name", $type );
}
function providerFiles() {
@@ -129,7 +154,7 @@ class ExifRotationTest extends MediaWikiTestCase {
global $wgEnableAutoRotation;
$wgEnableAutoRotation = false;
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type );
+ $file = $this->dataFile( $name, $type );
$this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
$this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
@@ -158,13 +183,13 @@ class ExifRotationTest extends MediaWikiTestCase {
throw new MWException('bogus test data format ' . $size);
}
- $file = $this->localFile( $name, $type );
- $thumb = $file->transform( $params, File::RENDER_NOW );
+ $file = $this->dataFile( $name, $type );
+ $thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE );
$this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" );
$this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" );
- $gis = getimagesize( $thumb->getPath() );
+ $gis = getimagesize( $thumb->getLocalCopyPath() );
if ($out[0] > $info['width']) {
// Physical image won't be scaled bigger than the original.
$this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size");
@@ -242,7 +267,7 @@ class ExifRotationTest extends MediaWikiTestCase {
array(
270,
array( self::TEST_HEIGHT, self::TEST_WIDTH )
- ),
+ ),
);
}
}
diff --git a/tests/phpunit/includes/media/ExifTest.php b/tests/phpunit/includes/media/ExifTest.php
index 9b490e92..b39c15e4 100644
--- a/tests/phpunit/includes/media/ExifTest.php
+++ b/tests/phpunit/includes/media/ExifTest.php
@@ -4,6 +4,9 @@ class ExifTest extends MediaWikiTestCase {
public function setUp() {
$this->mediaPath = dirname( __FILE__ ) . '/../../data/media/';
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
+ }
global $wgShowEXIF;
$this->showExif = $wgShowEXIF;
$wgShowEXIF = true;
@@ -14,9 +17,6 @@ class ExifTest extends MediaWikiTestCase {
}
public function testGPSExtraction() {
- if ( !wfDl( 'exif' ) ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$filename = $this->mediaPath . 'exif-gps.jpg';
$seg = JpegMetadataExtractor::segmentSplitter( $filename );
@@ -32,9 +32,6 @@ class ExifTest extends MediaWikiTestCase {
$this->assertEquals( $expected, $data, '', 0.0000000001 );
}
public function testUnicodeUserComment() {
- if ( !wfDl( 'exif' ) ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$filename = $this->mediaPath . 'exif-user-comment.jpg';
$seg = JpegMetadataExtractor::segmentSplitter( $filename );
diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php
index db36dea3..8a632f52 100644
--- a/tests/phpunit/includes/media/FormatMetadataTest.php
+++ b/tests/phpunit/includes/media/FormatMetadataTest.php
@@ -1,13 +1,31 @@
<?php
class FormatMetadataTest extends MediaWikiTestCase {
- public function testInvalidDate() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
+ public function setUp() {
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
}
-
- $file = UnregisteredLocalFile::newFromPath( dirname( __FILE__ ) .
- '/../../data/media/broken_exif_date.jpg', 'image/jpeg' );
+ $filePath = dirname( __FILE__ ) . '/../../data/media';
+ $this->backend = new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array( 'data' => $filePath )
+ ) );
+ $this->repo = new FSRepo( array(
+ 'name' => 'temp',
+ 'url' => 'http://localhost/thumbtest',
+ 'backend' => $this->backend
+ ) );
+ global $wgShowEXIF;
+ $this->show = $wgShowEXIF;
+ $wgShowEXIF = true;
+ }
+ public function tearDown() {
+ global $wgShowEXIF;
+ $wgShowEXIF = $this->show;
+ }
+
+ public function testInvalidDate() {
+ $file = $this->dataFile( 'broken_exif_date.jpg', 'image/jpeg' );
// Throws an error if bug hit
$meta = $file->formatMetadata();
@@ -26,4 +44,9 @@ class FormatMetadataTest extends MediaWikiTestCase {
$meta['visible'][$dateIndex]['value'],
'File with invalid date metadata (bug 29471)' );
}
-} \ No newline at end of file
+
+ private function dataFile( $name, $type ) {
+ return new UnregisteredLocalFile( false, $this->repo,
+ "mwstore://localtesting/data/$name", $type );
+ }
+}
diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
index 59b30441..47fc368b 100644
--- a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
+++ b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
@@ -63,6 +63,7 @@ class GIFMetadataExtractorTest extends MediaWikiTestCase {
<?xpacket end='w'?>
EOF;
+ $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat
return array(
array( 'nonanimated.gif', array(
diff --git a/tests/phpunit/includes/media/GIFTest.php b/tests/phpunit/includes/media/GIFTest.php
index 42c25ca5..36658358 100644
--- a/tests/phpunit/includes/media/GIFTest.php
+++ b/tests/phpunit/includes/media/GIFTest.php
@@ -2,12 +2,22 @@
class GIFHandlerTest extends MediaWikiTestCase {
public function setUp() {
- $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media';
+ $this->backend = new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array( 'data' => $this->filePath )
+ ) );
+ $this->repo = new FSRepo( array(
+ 'name' => 'temp',
+ 'url' => 'http://localhost/thumbtest',
+ 'backend' => $this->backend
+ ) );
$this->handler = new GIFHandler();
}
public function testInvalidFile() {
- $res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+ $res = $this->handler->getMetadata( null, $this->filePath . '/README' );
$this->assertEquals( GIFHandler::BROKEN_FILE, $res );
}
/**
@@ -16,8 +26,7 @@ class GIFHandlerTest extends MediaWikiTestCase {
* @dataProvider dataIsAnimated
*/
public function testIsAnimanted( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/gif' );
+ $file = $this->dataFile( $filename, 'image/gif' );
$actual = $this->handler->isAnimatedImage( $file );
$this->assertEquals( $expected, $actual );
}
@@ -34,8 +43,7 @@ class GIFHandlerTest extends MediaWikiTestCase {
* @dataProvider dataGetImageArea
*/
public function testGetImageArea( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/gif' );
+ $file = $this->dataFile( $filename, 'image/gif' );
$actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
$this->assertEquals( $expected, $actual );
}
@@ -71,15 +79,20 @@ class GIFHandlerTest extends MediaWikiTestCase {
* @dataProvider dataGetMetadata
*/
public function testGetMetadata( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/gif' );
- $actual = $this->handler->getMetadata( $file, $this->filePath . $filename );
+ $file = $this->dataFile( $filename, 'image/gif' );
+ $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" );
$this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
}
+
public function dataGetMetadata() {
return array(
array( 'nonanimated.gif', 'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}' ),
array( 'animated-xmp.gif', 'a:4:{s:10:"frameCount";i:4;s:6:"looped";b:1;s:8:"duration";d:2.399999999999999911182158029987476766109466552734375;s:8:"metadata";a:5:{s:6:"Artist";s:7:"Bawolff";s:16:"ImageDescription";a:2:{s:9:"x-default";s:18:"A file to test GIF";s:5:"_type";s:4:"lang";}s:15:"SublocationDest";s:13:"The interwebs";s:14:"GIFFileComment";a:1:{i:0;s:16:"GIƒ·test·file";}s:15:"_MW_GIF_VERSION";i:1;}}' ),
);
}
+
+ private function dataFile( $name, $type ) {
+ return new UnregisteredLocalFile( false, $this->repo,
+ "mwstore://localtesting/data/$name", $type );
+ }
}
diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
index 61fc9c81..f48382a4 100644
--- a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
+++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* @todo Could use a test of extended XMP segments. Hard to find programs that
* create example files, and creating my own in vim propbably wouldn't
* serve as a very good "test". (Adobe photoshop probably creates such files
@@ -59,7 +59,7 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase {
public function testPSIRExtraction() {
$res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
$expected = '50686f746f73686f7020332e30003842494d04040000000000181c02190004746573741c02190003666f6f1c020000020004';
- $this->assertEquals( $expected, bin2hex( $res['PSIR'] ) );
+ $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
}
public function testXMPExtractionAltAppId() {
$res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
@@ -70,19 +70,19 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase {
public function testIPTCHashComparisionNoHash() {
$segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
$this->assertEquals( 'iptc-no-hash', $res );
}
public function testIPTCHashComparisionBadHash() {
$segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
$this->assertEquals( 'iptc-bad-hash', $res );
}
public function testIPTCHashComparisionGoodHash() {
$segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
$this->assertEquals( 'iptc-good-hash', $res );
}
diff --git a/tests/phpunit/includes/media/JpegTest.php b/tests/phpunit/includes/media/JpegTest.php
index 713a3410..ddabf5b8 100644
--- a/tests/phpunit/includes/media/JpegTest.php
+++ b/tests/phpunit/includes/media/JpegTest.php
@@ -3,22 +3,24 @@ class JpegTest extends MediaWikiTestCase {
public function setUp() {
$this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
+ }
+ global $wgShowEXIF;
+ $this->show = $wgShowEXIF;
+ $wgShowEXIF = true;
+ }
+ public function tearDown() {
+ global $wgShowEXIF;
+ $wgShowEXIF = $this->show;
}
public function testInvalidFile() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$jpeg = new JpegHandler;
$res = $jpeg->getMetadata( null, $this->filePath . 'README' );
$this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
}
public function testJpegMetadataExtraction() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$h = new JpegHandler;
$res = $h->getMetadata( null, $this->filePath . 'test.jpg' );
$expected = 'a:7:{s:16:"ImageDescription";s:9:"Test file";s:11:"XResolution";s:4:"72/1";s:11:"YResolution";s:4:"72/1";s:14:"ResolutionUnit";i:2;s:16:"YCbCrPositioning";i:1;s:15:"JPEGFileComment";a:1:{i:0;s:17:"Created with GIMP";}s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
diff --git a/tests/phpunit/includes/ImageFunctionsTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php
index cb7e67f3..99df4f80 100644
--- a/tests/phpunit/includes/ImageFunctionsTest.php
+++ b/tests/phpunit/includes/media/MediaHandlerTest.php
@@ -1,6 +1,6 @@
<?php
-class ImageFunctionsTest extends MediaWikiTestCase {
+class MediaHandlerTest extends MediaWikiTestCase {
function testFitBoxWidth() {
$vals = array(
array(
@@ -32,10 +32,12 @@ class ImageFunctionsTest extends MediaWikiTestCase {
17 => 4,
18 => 4 ) ) );
foreach ( $vals as $row ) {
- extract( $row );
+ $tests = $row['tests'];
+ $height = $row['height'];
+ $width = $row['width'];
foreach ( $tests as $max => $expected ) {
$y = round( $expected * $height / $width );
- $result = wfFitBoxWidth( $width, $height, $max );
+ $result = MediaHandler::fitBoxWidth( $width, $height, $max );
$y2 = round( $result * $height / $width );
$this->assertEquals( $expected,
$result,
diff --git a/tests/phpunit/includes/media/PNGTest.php b/tests/phpunit/includes/media/PNGTest.php
index b782918c..b6f911fd 100644
--- a/tests/phpunit/includes/media/PNGTest.php
+++ b/tests/phpunit/includes/media/PNGTest.php
@@ -2,12 +2,22 @@
class PNGHandlerTest extends MediaWikiTestCase {
public function setUp() {
- $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media';
+ $this->backend = new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array( 'data' => $this->filePath )
+ ) );
+ $this->repo = new FSRepo( array(
+ 'name' => 'temp',
+ 'url' => 'http://localhost/thumbtest',
+ 'backend' => $this->backend
+ ) );
$this->handler = new PNGHandler();
}
public function testInvalidFile() {
- $res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+ $res = $this->handler->getMetadata( null, $this->filePath . '/README' );
$this->assertEquals( PNGHandler::BROKEN_FILE, $res );
}
/**
@@ -16,8 +26,7 @@ class PNGHandlerTest extends MediaWikiTestCase {
* @dataProvider dataIsAnimated
*/
public function testIsAnimanted( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/png' );
+ $file = $this->dataFile( $filename, 'image/png' );
$actual = $this->handler->isAnimatedImage( $file );
$this->assertEquals( $expected, $actual );
}
@@ -34,8 +43,7 @@ class PNGHandlerTest extends MediaWikiTestCase {
* @dataProvider dataGetImageArea
*/
public function testGetImageArea( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/png' );
+ $file = $this->dataFile($filename, 'image/png' );
$actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
$this->assertEquals( $expected, $actual );
}
@@ -73,9 +81,8 @@ class PNGHandlerTest extends MediaWikiTestCase {
* @dataProvider dataGetMetadata
*/
public function testGetMetadata( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/png' );
- $actual = $this->handler->getMetadata( $file, $this->filePath . $filename );
+ $file = $this->dataFile( $filename, 'image/png' );
+ $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" );
// $this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
$this->assertEquals( ( $expected ), ( $actual ) );
}
@@ -85,4 +92,9 @@ class PNGHandlerTest extends MediaWikiTestCase {
array( 'xmp.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}' ),
);
}
+
+ private function dataFile( $name, $type ) {
+ return new UnregisteredLocalFile( false, $this->repo,
+ "mwstore://localtesting/data/$name", $type );
+ }
}
diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
index c2c81b98..526beae8 100644
--- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
+++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
@@ -62,23 +62,33 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase {
'height' => 60
)
),
+ array(
+ "$base/Toll_Texas_1.svg",
+ // This file triggered bug 31719, needs entity expansion in the xmlns checks
+ array(
+ 'width' => 385,
+ 'height' => 385
+ )
+ )
);
}
function providerSvgFilesWithXMLMetadata() {
$base = dirname( __FILE__ ) . '/../../data/media';
- return array(
- array(
- "$base/US_states_by_total_state_tax_revenue.svg",
- array(
- 'height' => 593,
- 'metadata' =>
+ $metadata =
'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about="">
<ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format>
<ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</ns4:Work>
- </rdf:RDF>',
+ </rdf:RDF>';
+ $metadata = str_replace( "\r", '', $metadata ); // Windows compat
+ return array(
+ array(
+ "$base/US_states_by_total_state_tax_revenue.svg",
+ array(
+ 'height' => 593,
+ 'metadata' => $metadata,
'width' => 959
)
),
diff --git a/tests/phpunit/includes/media/TiffTest.php b/tests/phpunit/includes/media/TiffTest.php
index 0a7e8e8c..d4cf503b 100644
--- a/tests/phpunit/includes/media/TiffTest.php
+++ b/tests/phpunit/includes/media/TiffTest.php
@@ -15,16 +15,15 @@ class TiffTest extends MediaWikiTestCase {
}
public function testInvalidFile() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
+ if ( !wfDl( 'exif' ) ) {
$this->markTestIncomplete( "This test needs the exif extension." );
}
$res = $this->handler->getMetadata( null, $this->filePath . 'README' );
$this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
}
+
public function testTiffMetadataExtraction() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
+ if ( !wfDl( 'exif' ) ) {
$this->markTestIncomplete( "This test needs the exif extension." );
}
$res = $this->handler->getMetadata( null, $this->filePath . 'test.tiff' );
diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php
index d1ec4767..c952b23c 100644
--- a/tests/phpunit/includes/media/XMPTest.php
+++ b/tests/phpunit/includes/media/XMPTest.php
@@ -1,6 +1,12 @@
<?php
class XMPTest extends MediaWikiTestCase {
+ function setUp() {
+ if ( !wfDl( 'xml' ) ) {
+ $this->markTestSkipped( 'Requires libxml to do XMP parsing' );
+ }
+ }
+
/**
* Put XMP in, compare what comes out...
*
@@ -11,9 +17,6 @@ class XMPTest extends MediaWikiTestCase {
* @dataProvider dataXMPParse
*/
public function testXMPParse( $xmp, $expected, $info ) {
- if ( !function_exists( 'xml_parser_create_ns' ) ) {
- $this->markIncomplete( 'Requires libxml to do XMP parsing' );
- }
if ( !is_string( $xmp ) || !is_array( $expected ) ) {
throw new Exception( "Invalid data provided to " . __METHOD__ );
}
diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php
new file mode 100644
index 00000000..e2bb8d8d
--- /dev/null
+++ b/tests/phpunit/includes/media/XMPValidateTest.php
@@ -0,0 +1,47 @@
+<?php
+class XMPValidateTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider providerDate
+ */
+ function testValidateDate( $value, $expected ) {
+ // The method should modify $value.
+ XMPValidate::validateDate( array(), $value, true );
+ $this->assertEquals( $expected, $value );
+ }
+
+ function providerDate() {
+ /* For reference valid date formats are:
+ * YYYY
+ * YYYY-MM
+ * YYYY-MM-DD
+ * YYYY-MM-DDThh:mmTZD
+ * YYYY-MM-DDThh:mm:ssTZD
+ * YYYY-MM-DDThh:mm:ss.sTZD
+ * (Time zone is optional)
+ */
+ return array(
+ array( '1992', '1992' ),
+ array( '1992-04', '1992:04' ),
+ array( '1992-02-01', '1992:02:01' ),
+ array( '2011-09-29', '2011:09:29' ),
+ array( '1982-12-15T20:12', '1982:12:15 20:12' ),
+ array( '1982-12-15T20:12Z', '1982:12:15 20:12' ),
+ array( '1982-12-15T20:12+02:30', '1982:12:15 22:42' ),
+ array( '1982-12-15T01:12-02:30', '1982:12:14 22:42' ),
+ array( '1982-12-15T20:12:11', '1982:12:15 20:12:11' ),
+ array( '1982-12-15T20:12:11Z', '1982:12:15 20:12:11' ),
+ array( '1982-12-15T20:12:11+01:10', '1982:12:15 21:22:11' ),
+ array( '2045-12-15T20:12:11', '2045:12:15 20:12:11' ),
+ array( '1867-06-01T15:00:00', '1867:06:01 15:00:00' ),
+ /* some invalid ones */
+ array( '2001--12', null ),
+ array( '2001-5-12', null ),
+ array( '2001-5-12TZ', null ),
+ array( '2001-05-12T15', null ),
+ array( '2001-12T15:13', null ),
+ );
+
+ }
+
+}
diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php
index a47653e3..31645313 100644
--- a/tests/phpunit/includes/parser/MagicVariableTest.php
+++ b/tests/phpunit/includes/parser/MagicVariableTest.php
@@ -6,8 +6,8 @@
* As of february 2011, it only tests some revisions and date related
* magic variables.
*
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
* @file
*/
@@ -38,6 +38,12 @@ class MagicVariableTest extends MediaWikiTestCase {
# initialize parser output
$this->testParser->clearState();
+
+ # Needs a title to do magic word stuff
+ $title = Title::newFromText( 'Tests' );
+ $title->mRedirect = false; # Else it needs a db connection just to check if it's a redirect (when deciding the page language)
+
+ $this->testParser->setTitle( $title );
}
/** destroy parser (TODO: is it really neded?)*/
diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php
index 18510d9a..816c017a 100644
--- a/tests/phpunit/includes/parser/MediaWikiParserTest.php
+++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php
@@ -1,10 +1,8 @@
<?php
-
-require_once( dirname( __FILE__ ) . '/ParserHelpers.php' );
require_once( dirname( __FILE__ ) . '/NewParserTest.php' );
/**
- * The UnitTest must be either a class that inherits from PHPUnit_Framework_TestCase
+ * The UnitTest must be either a class that inherits from MediaWikiTestCase
* or a class that provides a public static suite() method which returns
* an PHPUnit_Framework_Test object
*
@@ -20,9 +18,13 @@ class MediaWikiParserTest {
foreach ( $wgParserTestFiles as $filename ) {
$testsName = basename( $filename, '.txt' );
- $className = /*ucfirst( basename( dirname( $filename ) ) ) .*/ ucfirst( basename( $filename, '.txt' ) );
+ /* This used to be ucfirst( basename( dirname( $filename ) ) )
+ * and then was ucfirst( basename( $filename, '.txt' )
+ * but that didn't work with names like foo.tests.txt
+ */
+ $className = str_replace( '.', '_', ucfirst( basename( $filename, '.txt' ) ) );
- eval( "/** @group Database\n@group Parser\n*/ class $className extends NewParserTest { protected \$file = \"" . addslashes( $filename ) . "\"; } " );
+ eval( "/** @group Database\n@group Parser\n*/ class $className extends NewParserTest { protected \$file = '" . strtr( $filename, array( "'" => "\\'", '\\' => '\\\\' ) ) . "'; } " );
$parserTester = new $className( $testsName );
$suite->addTestSuite( new ReflectionClass ( $parserTester ) );
diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php
index f4d5f757..d1221ca8 100644
--- a/tests/phpunit/includes/parser/NewParserTest.php
+++ b/tests/phpunit/includes/parser/NewParserTest.php
@@ -8,13 +8,12 @@
* @group Stub
*/
class Ne