Merge "resourceloader: Speed up dependency checks in structure/ResourcesTest"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 15 Jul 2019 02:30:01 +0000 (02:30 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 15 Jul 2019 02:30:01 +0000 (02:30 +0000)
90 files changed:
.phpcs.xml
includes/api/i18n/fr.json
includes/api/i18n/mk.json
includes/api/i18n/zh-hans.json
includes/installer/i18n/be-tarask.json
includes/installer/i18n/it.json
includes/installer/i18n/sv.json
includes/installer/i18n/uk.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/ckb.json
languages/i18n/co.json
languages/i18n/da.json
languages/i18n/dsb.json
languages/i18n/he.json
languages/i18n/ja.json
languages/i18n/ko.json
languages/i18n/min.json
languages/i18n/nap.json
languages/i18n/nqo.json
tests/phpunit/bootstrap.php
tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php [deleted file]
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/PathRouterTest.php [deleted file]
tests/phpunit/includes/Rest/ResponseFactoryTest.php [deleted file]
tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php [deleted file]
tests/phpunit/includes/Revision/SlotRecordTest.php [deleted file]
tests/phpunit/includes/TitleArrayFromResultTest.php [deleted file]
tests/phpunit/includes/WikiReferenceTest.php [deleted file]
tests/phpunit/includes/debug/logger/monolog/CeeFormatterTest.php [deleted file]
tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php [deleted file]
tests/phpunit/includes/diff/SlotDiffRendererTest.php [deleted file]
tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php [deleted file]
tests/phpunit/includes/filerepo/file/ForeignDBFileTest.php [deleted file]
tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php [deleted file]
tests/phpunit/includes/http/HttpTest.php
tests/phpunit/includes/json/FormatJsonTest.php [deleted file]
tests/phpunit/includes/media/JpegMetadataExtractorTest.php [deleted file]
tests/phpunit/includes/page/ArticleTest.php [deleted file]
tests/phpunit/includes/session/SessionTest.php
tests/phpunit/includes/session/TokenTest.php [deleted file]
tests/phpunit/includes/shell/FirejailCommandTest.php [deleted file]
tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php [deleted file]
tests/phpunit/includes/utils/ZipDirectoryReaderTest.php [deleted file]
tests/phpunit/unit/includes/GlobalFunctions/wfAppendQueryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfArrayPlus2dTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfAssembleUrlTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfBaseNameTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfEscapeShellArgTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfGetCallerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfShorthandToIntegerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfStringToBoolTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfTimestampTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfUrlencodeTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/PathRouterTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/ResponseFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Revision/MainSlotRoleHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Revision/SlotRecordTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/TitleArrayFromResultTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/WikiReferenceTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/debug/logger/monolog/CeeFormatterTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/diff/DifferenceEngineSlotDiffRendererTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/diff/SlotDiffRendererTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/filerepo/FileBackendDBRepoWrapperTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/filerepo/file/ForeignDBFileTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/htmlform/HTMLCheckMatrixTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/http/HttpUnitTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/json/FormatJsonTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/language/LanguageCodeTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/language/SpecialPageAliasTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/media/JpegMetadataExtractorTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/page/ArticleTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/session/SessionUnitTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/session/TokenTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/shell/FirejailCommandTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/site/MediaWikiPageNameNormalizerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/utils/ZipDirectoryReaderTest.php [new file with mode: 0644]
tests/phpunit/unit/languages/LanguageCodeTest.php [deleted file]
tests/phpunit/unit/languages/SpecialPageAliasTest.php [deleted file]

index 8f3bd8c..9f11ebc 100644 (file)
                <exclude-pattern>*/maintenance/storage/recompressTracked\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/storage/trackBlobs\.php</exclude-pattern>
                <!-- Skip violations in some tests for now -->
+               <exclude-pattern>*/tests/phpunit/unit/includes/GlobalFunctions/*\.php</exclude-pattern>
                <exclude-pattern>*/tests/phpunit/includes/GlobalFunctions/*\.php</exclude-pattern>
                <exclude-pattern>*/tests/phpunit/maintenance/*\.php</exclude-pattern>
                <exclude-pattern>*/tests/phpunit/integration/includes/GlobalFunctions/*\.php</exclude-pattern>
index 640ddfa..b04ad1b 100644 (file)
@@ -33,7 +33,8 @@
                        "Kenjiraw",
                        "Framawiki",
                        "Epok",
-                       "Derugon"
+                       "Derugon",
+                       "Lucas Werkmeister (WMDE)"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Documentation]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> L’API MediaWiki est une interface stable et mature qui est supportée et améliorée de façon active. Bien que nous essayions de l’éviter, nous pouvons avoir parfois besoin de faire des modifications impactantes ; inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un entête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet entête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:Special:MyLanguage/API:Errors_and_warnings|API:Errors and warnings]].\n\n<p class=\"mw-apisandbox-link\"><strong>Test :</strong> Pour faciliter le test des requêtes à l’API, voyez [[Special:ApiSandbox]].</p>",
        "apihelp-query+languageinfo-paramvalue-prop-code": "Le code de langue (ce code est spécifique à MédiaWiki, bien qu’il y ait des recouvrements avec d’autres standards).",
        "apihelp-query+languageinfo-paramvalue-prop-bcp47": "Le code de langue BCP-47.",
        "apihelp-query+languageinfo-paramvalue-prop-dir": "La direction d’écriture de la langue (<code>ltr</code> ou <code>rtl</code>).",
-       "apihelp-query+languageinfo-paramvalue-prop-autonym": "L’autonyme d'une langue, c’est-à-dire son nom dans cette langue.",
+       "apihelp-query+languageinfo-paramvalue-prop-autonym": "L’autonyme dune langue, c’est-à-dire son nom dans cette langue.",
        "apihelp-query+languageinfo-paramvalue-prop-name": "Le nom de la langue dans la langue spécifiée par le paramètre <var>lilang</var>, avec application des langues de secours si besoin.",
        "apihelp-query+languageinfo-paramvalue-prop-fallbacks": "Les codes de langue des langues de secours configurées pour cette langue. Le secours implicite final en 'en' n’est pas inclus (mais certaines langues peuvent avoir 'en' en secours explicitement).",
        "apihelp-query+languageinfo-paramvalue-prop-variants": "Les codes de langue des variantes supportées par cette langue.",
index 26c7f10..f91cf3d 100644 (file)
@@ -20,7 +20,7 @@
        "apihelp-main-param-uselang": "Јазик за преведување на пораките. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> со <kbd>siprop=languages</kbd> дава список на јазични кодови, или укажете <kbd>user</kbd> за да го користите тековно зададениот јазик корисникот, или пак укажете <kbd>content</kbd> за да го користите јазикот на содржината на ова вики.",
        "apihelp-block-summary": "Блокирај корисник.",
        "apihelp-block-param-user": "Корисничко име, IP-адреса или IP-опсег ако сакате да блокирате. Не може да се користи заедно со <var>$1userid</var>",
-       "apihelp-block-param-expiry": "Време на истек. Може да биде релативно (на пр. <kbd>5 months</kbd> или „2 недели“) или пак апсолутно (на пр. <kbd>2014-09-18T12:34:56Z</kbd>). Ако го зададете <kbd>infinite</kbd>, <kbd>indefinite</kbd> или <kbd>never</kbd>, блокот ќе трае засекогаш.",
+       "apihelp-block-param-expiry": "Време на истек. Може да биде релативно (на пр. <kbd>5 months</kbd> или <kbd>2 weeks</kbd>) или пак апсолутно (на пр. <kbd>2014-09-18T12:34:56Z</kbd>). Ако го зададете <kbd>infinite</kbd>, <kbd>indefinite</kbd> или <kbd>never</kbd>, блокот ќе трае засекогаш.",
        "apihelp-block-param-reason": "Причина за блокирање.",
        "apihelp-block-param-anononly": "Блокирај само анонимни корисници (т.е. оневозможи анонимно уредување од оваа IP-адреса).",
        "apihelp-block-param-nocreate": "Оневозможи создавање кориснички сметки.",
        "apihelp-opensearch-param-search": "Низа за пребарување.",
        "apihelp-opensearch-param-limit": "Највеќе ставки за прикажување.",
        "apihelp-opensearch-param-namespace": "Именски простори за пребарување.",
-       "apihelp-opensearch-param-suggest": "Не прави ништо ако <var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> е неточно.",
+       "apihelp-opensearch-param-suggest": "Не прави ништо ако <var>[[mw:Special:MyLanguage/Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> е неточно.",
        "apihelp-opensearch-param-redirects": "Како да се работи со пренасочувања:\n;return: Дај го самото пренасочување.\n;resolve: Дај ја целната страница. Може да даде помалку од $1limit ставки.\nОд историски причини, по основно е „return“ за $1format=json и „resolve“ за други формати.",
        "apihelp-opensearch-param-format": "Формат на изводот.",
        "apihelp-opensearch-example-te": "Најди страници што почнуваат со <kbd>Те</kbd>.",
        "apihelp-options-param-resetkinds": "Сисок на типови можности за повраток кога е зададена можноста <var>$1reset</var>.",
        "apihelp-options-param-change": "Список на промени во форматот name=value (на пр. skin=vector). Вредностите не треба да содржат исправени црти. Ако не зададете вредност (дури ни знак за равенство), на пр., можност|другаможност|..., ќе биде зададена вредноста на можноста по основно.",
        "apihelp-options-param-optionname": "Назив на можноста што треба да ѝ се зададе на вредноста дадена од <var>$1optionvalue</var>.",
-       "apihelp-options-param-optionvalue": "Вредноста на можноста укажана од <var>$1optionname</var>. Може да содржи исправени црти.",
+       "apihelp-options-param-optionvalue": "Вредноста на можноста укажана од <var>$1optionname</var>.",
        "apihelp-options-example-reset": "Врати ги сите поставки по основно",
        "apihelp-options-example-change": "Смени ги поставките <kbd>skin</kbd и <kbd>hideminor</kbd>.",
        "apihelp-options-example-complex": "Врати ги сите нагодувања по основно, а потоа задај ги <kbd>skin</kbd> и <kbd>nickname</kbd>.",
        "apihelp-paraminfo-summary": "Набави информации за извршнички (API) модули.",
-       "apihelp-paraminfo-param-modules": "Список на називи на модули (вредности на параметрите <var>action</var> и <var>format</var>, или пак <kbd>main</kbd>). Може да се укажат подмодули со <kbd>+</kbd>.",
+       "apihelp-paraminfo-param-modules": "Список на називи на модули (вредности на параметрите <var>action</var> и <var>format</var>, или пак <kbd>main</kbd>). Може да се укажат подмодули со <kbd>+</kbd>, или сите подмодули <kbd>+*</kbd>, или сите подмодули рекурзивно со <kbd>+**</kbd>.",
        "apihelp-paraminfo-param-helpformat": "Формат на помошните низи.",
        "apihelp-paraminfo-param-querymodules": "Список на називи на модули за барања (вредност на параметарот <var>prop</var>, <var>meta</var> или <var>list</var>). Користете го <kbd>$1modules=query+foo</kbd> наместо <kbd>$1querymodules=foo</kbd>.",
        "apihelp-paraminfo-param-mainmodule": "Добави информации и за главниот (врховен) модул. Користете го <kbd>$1modules=main</kbd> наместо тоа.",
        "apihelp-paraminfo-param-pagesetmodule": "Дај ги сите информации и за модулот на збирот страници (укажувајќи titles= и сродни).",
        "apihelp-paraminfo-param-formatmodules": "Список на називи на форматни модули (вредностза параметарот <var>format</var>). Наместо тоа, користете го <var>$1modules</var>.",
+       "apihelp-paraminfo-example-1": "Прикажи информации за <kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd> и <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
+       "apihelp-paraminfo-example-2": "Прикажи информации за сите подмодули на <kbd>[[Special:ApiHelp/query|action=query]]</kbd>.",
        "apihelp-parse-param-summary": "Опис за расчленување.",
        "apihelp-parse-param-preview": "Расчлени во прегледен режим.",
        "apihelp-parse-param-sectionpreview": "Расчлени во прегледен режим на поднасловот (го овозможува и прегледниот режим).",
        "apihelp-patrol-summary": "Испатролирај страница или преработка.",
        "apihelp-patrol-param-rcid": "Назнака на спорешните промени за патролирање.",
        "apihelp-patrol-param-revid": "Назнака на преработката за патролирање.",
+       "apihelp-patrol-param-tags": "Ознаки за примена врз ставката во дневникот на патролирања.",
        "apihelp-patrol-example-rcid": "Испатролирај скорешна промена",
        "apihelp-patrol-example-revid": "Патролирај праработка",
        "apihelp-protect-summary": "Смени го степенот на заштита на страница.",
        "apihelp-protect-param-title": "Наслов на страница што се (од)заштитува. Не може да се користи заедно со $1pageid.",
        "apihelp-protect-param-pageid": "Назнака на страница што се (од)заштитува. Не може да се користи заедно со $1title.",
        "apihelp-protect-param-reason": "Причиина за (од)заштитување",
+       "apihelp-protect-param-tags": "Ознаки за примена врз ставката во дневникот на заштита.",
        "apihelp-protect-example-protect": "Заштити страница",
        "apihelp-purge-param-forcelinkupdate": "Поднови ги табелите со врски.",
        "apihelp-purge-example-simple": "Превчитај ги <kbd>Main Page</kbd> и <kbd>API</kbd>.",
        "apihelp-query+allcategories-param-from": "Од која категорија да почне набројувањето.",
        "apihelp-query+allcategories-param-to": "На која категорија да запре набројувањето.",
        "apihelp-query+allcategories-param-dir": "Насока на подредувањето.",
+       "apihelp-query+allcategories-param-limit": "Колку категории да се дадат.",
        "apihelp-query+allcategories-param-prop": "Кои својства да се дадат:",
        "apihelp-query+alldeletedrevisions-param-from": "Почни го исписот од овој наслов.",
        "apihelp-query+alldeletedrevisions-param-to": "Запри го исписот на овој наслов.",
index cf80ac0..83e8314 100644 (file)
@@ -29,7 +29,8 @@
                        "WhitePhosphorus",
                        "科劳",
                        "SolidBlock",
-                       "神樂坂秀吉"
+                       "神樂坂秀吉",
+                       "94rain"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|文档]]\n* [[mw:Special:MyLanguage/API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>MediaWiki API是一个成熟稳定的,不断受到支持和改进的界面。尽管我们尽力避免,但偶尔也需要作出重大更新;请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:Special:MyLanguage/API:Errors_and_warnings|API:错误与警告]]。\n\n<p class=\"mw-apisandbox-link\"><strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。</p>",
        "apihelp-upload-param-comment": "上传注释。如果没有指定<var>$1text</var>,那么它也被用于新文件的初始页面文本。",
        "apihelp-upload-param-tags": "更改标签以应用于上传日志记录和文件页面修订中。",
        "apihelp-upload-param-text": "用于新文件的初始页面文本。",
-       "apihelp-upload-param-watch": "关注页面。",
+       "apihelp-upload-param-watch": "监视页面。",
        "apihelp-upload-param-watchlist": "无条件地将页面加入至当前用户的监视列表或将其移除,使用设置或不更改监视。",
        "apihelp-upload-param-ignorewarnings": "忽略任何警告。",
        "apihelp-upload-param-file": "文件内容。",
index 52cab04..136f3a2 100644 (file)
@@ -51,6 +51,8 @@
        "config-sidebar": "* [https://www.mediawiki.org Хатняя старонка MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Даведка для ўдзельнікаў]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Даведка для адміністратараў]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Адказы на частыя пытаньні]",
        "config-sidebar-readme": "Прачытай мяне",
        "config-sidebar-relnotes": "Заўвагі да выпуску",
+       "config-sidebar-license": "Капіяваньне",
+       "config-sidebar-upgrade": "Абнаўленьне",
        "config-env-good": "Асяродзьдзе было праверанае.\nВы можаце ўсталёўваць MediaWiki.",
        "config-env-bad": "Асяродзьдзе было праверанае.\nУсталяваньне MediaWiki немагчымае.",
        "config-env-php": "Усталяваны PHP $1.",
index 36e1902..ce38338 100644 (file)
@@ -67,7 +67,7 @@
        "config-sidebar": "* [https://www.mediawiki.org Pagina principale MediaWiki]\n* [https://www.mediawiki.org/Special:MyLanguage/Help:Contents Guida ai contenuti per utenti]\n* [https://www.mediawiki.org/Special:MyLanguage/Manual:Contents Guida ai contenuti per admin]\n* [https://www.mediawiki.org/Special:MyLanguage/Manual:FAQ FAQ]",
        "config-sidebar-readme": "Leggimi",
        "config-sidebar-relnotes": "Note di versione",
-       "config-sidebar-license": "copiando",
+       "config-sidebar-license": "Licenza",
        "config-sidebar-upgrade": "Aggiornamento",
        "config-env-good": "L'ambiente è stato controllato.\nÈ possibile installare MediaWiki.",
        "config-env-bad": "L'ambiente è stato controllato.\nNon è possibile installare MediaWiki.",
index e2cb99e..1db3fca 100644 (file)
        "config-restart": "Ja, starta om",
        "config-welcome": "=== Miljökontroller ===\nGrundläggande kontroller kommer nu att utföras för att se om denna miljö är lämplig för installation av MediaWiki.\nKom ihåg att ta med denna information om du söker stöd för hur du skall slutföra installationen.",
        "config-copyright": "=== Upphovsrätt och Villkor ===\n\n$1\n\nDetta program är fri programvara; du kan vidaredistribuera den och/eller modifiera det enligt villkoren i GNU General Public License som publicerats av Free Software Foundation; antingen genom version 2 av licensen, eller (på ditt initiativ) någon senare version.\n\nDetta program är distribuerat i hopp om att det kommer att vara användbart, men '''utan någon garanti'''; utan att ens ha en underförstådd garanti om '''säljbarhet''' eller '''lämplighet för ett särskilt ändamål'''.\nSe GNU General Public License för mer detaljer.\n\nDu bör ha fått <doclink href=Copying>en kopia av GNU General Public License</doclink> tillsammans med detta program; om inte, skriv till Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, eller [https://www.gnu.org/copyleft/gpl.html läs den online].",
-       "config-sidebar": "* [https://www.mediawiki.org MediaWikis webbplats]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Användarguide]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administratörguide]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Frågor och svar]\n----\n* <doclink href=Readme>Läs mig</doclink>\n* <doclink href=ReleaseNotes>Utgivningsanteckningar</doclink>\n* <doclink href=Copying>Kopiering</doclink>\n* <doclink href=UpgradeDoc>Uppgradering</doclink>",
+       "config-sidebar": "* [https://www.mediawiki.org MediaWikis webbplats]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Användarguide]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administratörsguide]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Frågor och svar]",
+       "config-sidebar-readme": "Läs mig",
+       "config-sidebar-relnotes": "Utgivningsanteckningar",
+       "config-sidebar-license": "Kopierar",
+       "config-sidebar-upgrade": "Uppgradering",
        "config-env-good": "Miljön har kontrollerats.\nDu kan installera MediaWiki.",
        "config-env-bad": "Miljön har kontrollerats.\nDu kan inte installera MediaWiki.",
        "config-env-php": "PHP $1 är installerat.",
index 321caef..bfb0e64 100644 (file)
        "config-restart": "Так, перезапустити установку",
        "config-welcome": "=== Перевірка оточення ===\nБудуть проведені базові перевірки, щоб виявити, чи можлива установка MediaWiki у даній системі.\nНе забудьте включити цю інформацію, якщо ви звернетеся по підтримку, як завершити установку.",
        "config-copyright": "=== Авторське право і умови ===\n\n$1\n\nЦя програма є вільним програмним забезпеченням; Ви можете розповсюджувати та/або змінювати її під ліцензією GNU General Public License, опублікованою Фондом вільного програмного забезпечення; версією 2 цієї ліцензії або будь-якою пізнішою на Ваш вибір.\n\nЦя програма поширюється з надією на те, що вона буде корисною, однак '''без жодних гарантій'''; навіть без неявної гарантії '''комерційної цінності''' або '''придатності для певних цілей'''.\nДив. GNU General Public License для детальної інформації.\n\nВи повинні були отримати <doclink href=Copying>копію GNU General Public License</doclink> разом із цією програмою; якщо ж ні, зверніться до Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. або [https://www.gnu.org/copyleft/gpl.html ознайомтесь з нею онлайн].",
-       "config-sidebar": "* [https://www.mediawiki.org Сайт MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Посібник користувача]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Посібник адміністратора]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Read me</doclink>\n* <doclink href=ReleaseNotes>Інформація про випуск</doclink>\n* <doclink href=Copying>Ліцензія</doclink>\n* <doclink href=UpgradeDoc>Оновлення</doclink>",
+       "config-sidebar": "* [https://www.mediawiki.org Сайт MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Посібник користувача]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Посібник адміністратора]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]",
+       "config-sidebar-readme": "Прочитай мене",
+       "config-sidebar-relnotes": "Інформація про версію",
+       "config-sidebar-license": "Копіювання",
+       "config-sidebar-upgrade": "Оновлення",
        "config-env-good": "Перевірку середовища успішно завершено.\nВи можете встановити MediaWiki.",
        "config-env-bad": "Було проведено перевірку середовища. Ви не можете встановити MediaWiki.",
        "config-env-php": "Встановлено версію PHP: $1.",
        "config-env-hhvm": "HHVM $1  встановлено.",
-       "config-unicode-using-intl": "Ð\92икоÑ\80иÑ\81Ñ\82овÑ\83ваÑ\82и [https://pecl.php.net/intl Ð¼Ñ\96жнаÑ\80одне Ñ\80озÑ\88иÑ\80еннÑ\8f PECL] для нормалізації Юнікоду.",
-       "config-unicode-pure-php-warning": "'''Увага''': [https://pecl.php.net/intl міжнародне розширення PECL] не може провести нормалізацію Юнікоду.\nЯкщо ваш сайт має високий трафік, вам варто почитати про [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations нормалізацію Юнікоду].",
+       "config-unicode-using-intl": "Ð\97а Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð³Ð¾Ñ\8e [https://php.net/manual/en/book.intl.php PHP-Ñ\80озÑ\88иÑ\80еннÑ\8f intl] для нормалізації Юнікоду.",
+       "config-unicode-pure-php-warning": "'''Увага''': [https://php.net/manual/en/book.intl.php PHP-розширення intl] не може провести нормалізацію Юнікоду.\nЯкщо ваш сайт має високий трафік, вам варто почитати про [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations нормалізацію Юнікоду].",
        "config-unicode-update-warning": "'''Увага''': Встановлена версія обгортки нормалізації Юнікоду використовує стару версію бібліотеки [http://site.icu-project.org/ проекту ICU].\nВи маєте [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations оновити версію], якщо плануєте повноцінно використовувати Юнікод.",
        "config-no-db": "Не вдалося знайти потрібний драйвер бази даних! Вам необхідно встановити драйвер бази даних для PHP. Підтримуються {{PLURAL:$2|такий тип|такі типи}} баз даних: $1.\n\nЯкщо ви скомпілювали PHP самостійно, переналаштуйте його з увімкненим клієнтом бази даних, наприклад за допомогою <code>./configure --with-mysqli</code>.\n\nЯкщо установлено PHP з пакетів Debian або Ubuntu, тоді ви також повинні встановити, наприклад, пакунок <code>php-mysql</code>.",
        "config-outdated-sqlite": "<strong>Увага:</strong> у Вас встановлена версія SQLite $2, а це нижче, ніж мінімально необхідна версія $1. SQLite буде недоступним.",
        "config-support-info": "MediaWiki підтримує такі системи баз даних:\n\n$1\n\nЯкщо Ви не бачите серед перерахованих систему баз даних, яку використовуєте, виконайте вказівки, вказані вище, щоб увімкнути підтримку.",
        "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] є основною ціллю для MediaWiki і найкраще підтримується.  MediaWiki також працює з [{{int:version-db-mysql-url}} MySQL] та [{{int:version-db-percona-url}} Percona Server], які сумісні з MariaDB.  ([https://www.php.net/manual/en/mysqli.installation.php Як зібрати PHP з підтримкою MySQL])",
        "config-dbsupport-postgres": "*  [{{int:version-db-postgres-url}} PostgreSQL] — популярна відкрита СУБД, альтернатива MySQL. ([https://www.php.net/manual/en/pgsql.installation.php як зібрати PHP з допомогою PostgreSQL]).",
-       "config-dbsupport-sqlite": "*  [{{int:version-db-sqlite-url}} SQLite] — легка система баз даних, яка дуже добре підтримується. ([http://www.php.net/manual/en/pdo.installation.php Як зібрати PHP з допомогою SQLite], що використовує PDO)",
-       "config-dbsupport-oracle": "*  [{{int:version-db-oracle-url}} Oracle] — комерційна база даних масштабу підприємства. ([http://www.php.net/manual/en/oci8.installation.php Як зібрати PHP з підтримкою OCI8])",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] — легка система баз даних, яка дуже добре підтримується. ([http://www.php.net/manual/en/pdo.installation.php Як зібрати PHP з допомогою SQLite], використовує PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] — комерційна база даних масштабу підприємства. ([http://www.php.net/manual/en/oci8.installation.php Як зібрати PHP з підтримкою OCI8])",
        "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] — це комерційна база даних для Windows масштабу підприємства. ([https://www.php.net/manual/en/sqlsrv.installation.php Як зібрати PHP з підтримкою SQLSRV])",
        "config-header-mysql": "Налаштування MariaDB/MySQL",
        "config-header-postgres": "Налаштування PostgreSQL",
        "config-license-help": "Чимало загальнодоступних вікі публікують увесь свій вміст під [https://freedomdefined.org/Definition вільною ліцензією]. Це розвиває відчуття спільної власності і заохочує довготривалу участь. У загальному випадку для приватної чи корпоративної вікі у цьому немає необхідності.\n\nЯкщо Ви хочете мати змогу використовувати текст з Вікіпедії і дати Вікіпедії змогу використовувати текст, скопійований з Вашої вікі, вам необхідно обрати <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nРаніше Вікіпедія використовувала GNU Free Documentation License.\nGFDL — допустима ліцензія, але у ній важко розібратися, а контент під GFDL важко використовувати повторно.",
        "config-email-settings": "Налаштування електронної пошти",
        "config-enable-email": "Увімкнути вихідну електронну пошту",
-       "config-enable-email-help": "Якщо Ви хочете, що електронна пошта працювала, необхідно виставити коректні [Config-dbsupport-oracle/manual/en/mail.configuration.php налаштування пошти у PHP].\nЯкщо Вам не потрібні жодні можливості електронної пошти у вікі, можете тут їх відімкнути.",
+       "config-enable-email-help": "Якщо Ви хочете, що електронна пошта працювала, необхідно виставити коректні [https://www.php.net/manual/en/mail.configuration.php налаштування пошти у PHP].\nЯкщо Вам не потрібні жодні можливості електронної пошти у вікі, можете тут їх відімкнути.",
        "config-email-user": "Увімкнути електронну пошту користувач-користувачеві",
        "config-email-user-help": "Дозволити усім користувачам надсилати один одному електронну пошту, якщо вони увімкнули цю можливість у своїх налаштуваннях.",
        "config-email-usertalk": "Увімкнути сповіщення про повідомлення на сторінці обговорення користувача",
index 7882aaa..21c661f 100644 (file)
        "actionfailed": "Дзеяньне ня выкананае",
        "deletedtext": "«$1» была выдаленая.\nЗапісы пра выдаленыя старонкі зьмяшчаюцца ў $2.",
        "dellogpage": "Журнал выдаленьняў",
-       "dellogpagetext": "Сьпіс апошніх выдаленьняў.",
+       "dellogpagetext": "Ð\9dÑ\96жÑ\8dй Ð·Ð½Ð°Ñ\85одзÑ\96Ñ\86Ñ\86а Ñ\81ьпіс апошніх выдаленьняў.",
        "deletionlog": "журнал выдаленьняў",
        "log-name-create": "Журнал стварэньня старонак",
        "log-description-create": "Ніжэй знаходзіцца сьпіс апошніх стварэньняў старонак.",
index f1bbf9b..832c1ca 100644 (file)
        "history": "История",
        "history_short": "История",
        "history_small": "история",
-       "updatedmarker": "пÑ\80оменено Ð¾Ñ\82 Ð¿Ð¾Ñ\81ледноÑ\82о Ð¼и посещение",
+       "updatedmarker": "пÑ\80оменено Ð¾Ñ\82 Ð¿Ð¾Ñ\81ледноÑ\82о Ð\92и посещение",
        "printableversion": "Версия за печат",
        "permalink": "Постоянна препратка",
        "print": "Печат",
        "deleteprotected": "Не можете да изтриете страницата, защото е защитена.",
        "deleting-backlinks-warning": "<strong>Внимание:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Други страници]] сочат към или включват като шаблон страницата, която се опитвате да изтриете.",
        "rollback": "Отмяна на промените",
+       "rollback-confirmation-confirm": "Моля потвърдете:",
+       "rollback-confirmation-yes": "Отмяна",
        "rollback-confirmation-no": "Отказ",
        "rollbacklink": "отмяна",
        "rollbacklinkcount": "отмяна на $1 {{PLURAL:$1|редакция|редакции}}",
        "blocklist-userblocks": "Скриване блокирането на потребителски сметки",
        "blocklist-tempblocks": "Скриване на временни блокирания",
        "blocklist-addressblocks": "Скриване на отделни блокирания на IP адреси",
+       "blocklist-type-opt-sitewide": "За всички уикита",
+       "blocklist-type-opt-partial": "Частично",
        "blocklist-rangeblocks": "Скриване на блокиранията по IP диапазон",
        "blocklist-timestamp": "Дата и час",
        "blocklist-target": "Цел",
        "watchlistedit-clear-titles": "Заглавия:",
        "watchlistedit-clear-submit": "Изчистване на списъка за наблюдение (Необратимо!)",
        "watchlistedit-clear-done": "Списъкът за наблюдение беше изчистен.",
+       "watchlistedit-clear-jobqueue": "Вашият списък за наблюдение се изчиства. Това може да отнеме известно време!",
        "watchlistedit-clear-removed": "{{PLURAL:$1|1 заглавие беше премахнато|$1 заглавия бяха премахнати}}:",
        "watchlistedit-too-many": "Има твърде много страници за показване.",
        "watchlisttools-clear": "Изчистване на списъка за наблюдение",
        "redirect-file": "Име на файл",
        "redirect-logid": "Номер на записа",
        "redirect-not-exists": "Стойността не е намерена",
+       "redirect-not-numeric": "Стойността не е числова",
        "fileduplicatesearch": "Търсене на повтарящи се файлове",
        "fileduplicatesearch-summary": "Търсене на повтарящи се файлове на база хеш стойности.",
        "fileduplicatesearch-filename": "Име на файл:",
        "tag-mw-contentmodelchange": "промяна на модела на съдържание",
        "tag-mw-new-redirect": "Ново пренасочване",
        "tag-mw-removed-redirect": "Премахнато пренасочване",
+       "tag-mw-changed-redirect-target": "Промяна целта на пренасочване",
+       "tag-mw-changed-redirect-target-description": "Редакции, променящи целта на пренасочване",
        "tag-mw-blank": "Изтриване на съдържанието",
        "tag-mw-replace": "Заменено",
+       "tag-mw-replace-description": "Редакции, премахващи над 90% от съдържанието на страница",
        "tag-mw-rollback": "Отмяна",
        "tag-mw-undo": "Отмяна",
        "tags-title": "Етикети",
index 72b1763..b721395 100644 (file)
        "powersearch-togglelabel": "تاوتوێ بکە:",
        "powersearch-toggleall": "ھەموویان",
        "powersearch-togglenone": "ھیچیان",
-       "powersearch-remember": "ھەڵبژاردەکانت بۆ گەڕانەکانی تر لە بیر بێت",
+       "powersearch-remember": "بەبیرھێناوەی ھەڵبژاردەکان بۆ گەڕانەکانی داھاتوو",
        "search-external": "گەڕانی دەرەکی",
        "searchdisabled": "گەڕانی {{SITENAME}} ئێستە کار ناکات.\nدەتوانی بۆ ئێستا لە گەڕانی گووگڵ کەڵک وەرگری.\nلەیادت بێت لەوانەیە پێرستەکانیان بۆ گەڕانی ناو {{SITENAME}}، کات‌بەسەرچوو بێت.",
        "preferences": "ھەڵبژاردەکان",
index 8c1b440..d5beb37 100644 (file)
@@ -99,7 +99,9 @@
        "navigation": "Navigazione",
        "and": "&#32;è",
        "actions": "Azzione",
+       "namespaces": "Spazii",
        "variants": "Variante",
+       "navigation-heading": "Navigazione",
        "errorpagetitle": "Errore",
        "returnto": "Vultà à $1.",
        "tagline": "À prupositu di {{SITENAME}}",
        "specialpage": "Pagina speciale",
        "personaltools": "Strumenti persunali",
        "talk": "Discussione",
+       "views": "Viste sfarenti",
        "toolbox": "Stuvigli",
        "mediawikipage": "Vede i missaghji",
        "templatepage": "Vede a pagina di mudellu",
        "categorypage": "Vede a pagina di categuria",
        "viewtalkpage": "Vede a discussione",
        "otherlanguages": "In altre lingue",
+       "redirectedfrom": "(Reindirizzamentu da $1)",
        "redirectpagesub": "Pagina di reindirizzamentu",
        "redirectto": "Reindirizzamentu à:",
-       "lastmodifiedat": "Ultima mudifica di sta pagina u $1 à e $2.",
+       "lastmodifiedat": "Ùltima mudìfica di sta pàgina u $1 à e $2.",
        "protectedpage": "Pagina prutetta",
        "jumpto": "Andà à:",
        "jumptonavigation": "navigazione",
        "loginlanguagelabel": "Lingua: $1",
        "pt-login": "Cunnessione",
        "pt-login-button": "Cunnessione",
+       "pt-createaccount": "Registramentu",
        "pt-userlogout": "Scunnessione",
        "retypenew": "Scrive torna a nova parulla secreta:",
        "resetpass-submit-cancel": "Cancillà",
        "currentrev": "Ultima revisione",
        "currentrev-asof": "Versione attuale di e $1",
        "revisionasof": "Versione di e $1",
-       "revision-info": "Versione di e $4 à e $5 di $2",
+       "revision-info": "Versione di e $4 à e $5 da {{GENDER:$6|$2}}$7",
        "previousrevision": "← Versione menu ricente",
        "nextrevision": "Versione più nova →",
        "currentrevisionlink": "Ultima revisione",
        "searchprofile-everything": "Tuttu",
        "searchprofile-advanced": "Avanzatu",
        "searchprofile-articles-tooltip": "Circà in $1",
+       "searchprofile-images-tooltip": "Circà schedarii",
        "searchprofile-everything-tooltip": "Circà dapertuttu (incluse e pagine di discussione)",
        "search-result-size": "$1 ({{PLURAL:$2|1 parolla|$2 parolle}})",
        "search-redirect": "(Reindirizzamentu da $1)",
        "hide": "piattà",
        "show": "mustrà",
        "minoreditletter": "m",
+       "newpageletter": "N",
        "boteditletter": "b",
        "rc-enhanced-hide": "Nasconde i dittagli",
        "recentchangeslinked": "Mudifiche assuciate",
        "file-anchor-link": "Schedariu",
        "filehist": "Cronolugia di l'imagine",
        "filehist-deleteone": "supprimà",
+       "filehist-current": "attuale",
+       "filehist-datetime": "Data/Óra",
+       "filehist-thumb": "Previsualizzazione",
+       "filehist-thumbtext": "Previsualizzazione di a versione di",
        "filehist-user": "Cuntributore",
        "filehist-dimensions": "Dimensione",
        "filehist-comment": "Cummentu",
        "watchlistfor2": "Per $1 ($2)",
        "watch": "Suvità",
        "unwatch": "Ùn suvità micca",
-       "wlshowlast": "Mustrà l'ultime $1 ore $2 ghjorni",
+       "wlshowlast": "Mustrà l'ùltime $1 ore $2 ghjorni",
        "enotif_reset": "Marcà tutte e pagine visitate",
        "created": "creatu",
        "changed": "cambiatu",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|revisione|revisione}}",
        "tooltip-pt-userpage": "{{GENDER:|A to}} pàgina di cuntributore",
        "tooltip-pt-mytalk": "{{GENDER:|A to}} pàgina di discussione",
-       "tooltip-pt-preferences": "{{GENDER:|E to}}} preferenze",
+       "tooltip-pt-preferences": "{{GENDER:|E to}} preferenze",
        "tooltip-pt-watchlist": "Lista di e pagine ch'è tù suviti",
        "tooltip-pt-mycontris": "Lista di {{GENDER:|e to}} cuntribuzioni",
        "tooltip-pt-login": "U registramentu hè suggeritu, micca ubligatoriu",
        "tooltip-pt-logout": "Esce da a sessione",
        "tooltip-ca-talk": "Vede e discussione relative à sta pagina",
-       "tooltip-ca-edit": "Pò mudificà 'ssa pagina. Per piacè improda l'ozzione di previsualisazzione prima di salvà",
+       "tooltip-ca-edit": "Mudificà 'ssa pagina",
        "tooltip-ca-addsection": "Cumincià una nova sezzione",
        "tooltip-ca-viewsource": "Sta pagina hè prutetta, ma si pò vede u so codice surghjente",
        "tooltip-ca-history": "Versione precedente di sta pagina",
        "tooltip-t-whatlinkshere": "Listinu di tutte e pagine chì sò ligate à quessa",
        "tooltip-t-recentchangeslinked": "Versione di l'ultime mudifiche à e pagine legate à quessa",
        "tooltip-t-contributions": "Listinu di e mudifiche {{GENDER:$1|di 'ssu cuntributore}}",
+       "tooltip-t-upload": "Incaricà un schedariu",
        "tooltip-t-specialpages": "Listinu di tutte e pagine spiciale",
        "tooltip-t-print": "Versione stampevule di 'ssa pagina",
        "tooltip-t-permalink": "Ligame permanente à e revisione di sta pagina",
        "tooltip-ca-nstab-user": "Vede a pagina di cuntributore",
        "tooltip-ca-nstab-special": "Questa hè una pàgina particulare chi ùn si pó micca esse mudificata",
        "tooltip-ca-nstab-project": "Vede a pagina di u prugettu",
+       "tooltip-ca-nstab-image": "Vede pàgina di schedariu",
        "tooltip-ca-nstab-template": "Vede u mudellu",
        "tooltip-ca-nstab-category": "Vede a pagina di categuria",
        "tooltip-minoredit": "Signalà com'è mudifica minore",
        "noimages": "Nulla da vede.",
        "ilsubmit": "Ricerca",
        "bydate": "per data",
+       "namespacesall": "tutti",
        "monthsall": "tutti",
        "confirm_purge_button": "D'accordu",
        "table_pager_next": "Pagina seguente",
        "logentry-move-move": "$1 {{GENDER:$2|hà spustatu}} a pagina $3 à $4",
        "logentry-newusers-create": "U participante $3 hè statu creatu da $1",
        "rightsnone": "(nessunu)",
-       "searchsuggest-search": "Ricerca",
+       "searchsuggest-search": "Circà in {{SITENAME}}",
        "expand_templates_output": "Risultatu"
 }
index e7ae281..0d88510 100644 (file)
@@ -74,7 +74,8 @@
                        "Weblars",
                        "Kranix",
                        "Psl85",
-                       "Dipsacus fullonum"
+                       "Dipsacus fullonum",
+                       "Fugithora"
                ]
        },
        "tog-underline": "Understreg link:",
        "history": "Sidehistorik",
        "history_short": "Historik",
        "history_small": "historik",
-       "updatedmarker": "opdateret siden seneste besøg",
+       "updatedmarker": "opdateret siden dit seneste besøg",
        "printableversion": "Udskriftsvenlig udgave",
        "permalink": "Permanent link",
        "print": "Udskriv",
        "virus-scanfailed": "scan fejlede (fejlkode $1)",
        "virus-unknownscanner": "ukendt antivirus:",
        "logouttext": "<strong>Du er nu logget af.</strong>\n\nBemærk, at nogle sider stadigvæk kan vises som om du var logget på, indtil du tømmer din browsers cache.",
+       "logging-out-notify": "Du bliver logget ud, vent venligst.",
+       "logout-failed": "Kan ikke logge ud nu: $1",
        "cannotlogoutnow-title": "Kan ikke logge af på nuværende tidspunkt",
        "cannotlogoutnow-text": "Det er ikke muligt at logge af når du bruger $1.",
        "welcomeuser": "Velkommen, $1!",
index ccd9218..88bf510 100644 (file)
@@ -21,7 +21,8 @@
                        "Macofe",
                        "Matma Rex",
                        "Fitoschido",
-                       "Vlad5250"
+                       "Vlad5250",
+                       "J budissin"
                ]
        },
        "tog-underline": "Wótkaze pódšmarnuś:",
        "nstab-template": "Pśedłoga",
        "nstab-help": "Pomoc",
        "nstab-category": "Kategorija",
+       "mainpage-nstab": "Głowny bok",
        "nosuchaction": "Toś tu akciju njedajo",
        "nosuchactiontext": "Akcija, kótaruž URL pódawa, jo njepłaśiwa.\nSy se snaź zapisał pśi zapódaśu URL abo sy slědował wopacnemu wótkazoju.\nTo by mógło teke programěrowańska zmólka w {{GRAMMAR:lokatiw|{{SITENAME}}}} byś.",
        "nosuchspecialpage": "Toś ten specialny bok njeeksistěrujo",
index 491c771..5b522e2 100644 (file)
        "currentevents": "אקטואליה",
        "currentevents-url": "Project:אקטואליה",
        "disclaimers": "הבהרות משפטיות",
-       "disclaimerpage": "Project:×\94×\91×\94ר×\94 ×\9eשפ×\98×\99ת",
+       "disclaimerpage": "Project:×\94×\91×\94ר×\95ת ×\9eשפ×\98×\99×\95ת",
        "edithelp": "עזרה בעריכה",
        "helppage-top-gethelp": "עזרה",
        "mainpage": "עמוד ראשי",
        "pt-createaccount": "יצירת חשבון",
        "pt-userlogout": "יציאה מהחשבון",
        "php-mail-error-unknown": "שגיאה לא ידועה בפונקציה mail()‎ של PHP.",
-       "user-mail-no-addy": "×\94ת×\91צע × ×\99ס×\99×\95×\9f ×\9cש×\9c×\99×\97ת ×\94×\95×\93×¢×\94 ×\9c×\9c×\90 ×\9bת×\95×\91ת ×\93×\95×\90×´ל.",
-       "user-mail-no-body": "× ×\99ס×\99×\95×\9f ×\9cש×\9c×\95×\97 דוא\"ל עם תוכן ריק או קצר מאוד.",
+       "user-mail-no-addy": "×\94ת×\91צע × ×\99ס×\99×\95×\9f ×\9cש×\9c×\99×\97ת ×\94×\95×\93עת ×\93×\95×\90\"×\9c ×\9c×\9c×\90 ×\9bת×\95×\91ת ×\93×\95×\90\"ל.",
+       "user-mail-no-body": "×\94ת×\91צע × ×\99ס×\99×\95×\9f ×\9cש×\9c×\99×\97ת ×\94×\95×\93עת דוא\"ל עם תוכן ריק או קצר מאוד.",
        "changepassword": "שינוי סיסמה",
        "resetpass_announce": "כדי לסיים את הכניסה לחשבון, יש להגדיר סיסמה חדשה.",
        "resetpass_text": "<!-- יש להוסיף טקסט כאן -->",
        "autoblockedtext": "כתובת ה־IP שלך נחסמה באופן אוטומטי כיוון שמשתמש אחר, שנחסם על־ידי $1, השתמש בה.\nהסיבה שניתנה לחסימה היא:\n\n:<em>$2</em>\n\n* תחילת החסימה: $8\n* פקיעת החסימה: $6\n* החסימה שבוצעה: $7\n\nבאפשרותך ליצור קשר עם $1 או עם כל אחד מ[[{{MediaWiki:Grouppage-sysop}}|מפעילי המערכת]] האחרים כדי לדון בחסימה.\n\nכמו־כן, באפשרותך להשתמש בתכונת \"{{int:emailuser}}\", אלא אם לא ציינת כתובת דוא\"ל תקפה ב[[Special:Preferences|העדפות המשתמש שלך]] או אם נחסמת משליחת דוא\"ל.\n\nכתובת ה־IP הנוכחית שלך היא $3, ומספר החסימה שלך הוא #$5.\nיש לציין את כל הפרטים הללו בכל פנייה לבירור החסימה.",
        "systemblockedtext": "שם המשתמש או כתובת ה־IP שלך נחסמו באופן אוטומטי על־ידי תוכנת מדיה־ויקי.\nהסיבה שניתנה לחסימה היא:\n\n:<em>$2</em>\n\n* תחילת החסימה: $8\n* פקיעת החסימה: $6\n* החסימה שבוצעה: $7\n\nכתובת ה־IP הנוכחית שלך היא $3.\nיש לציין את כל הפרטים הללו בכל פנייה לבירור החסימה.",
        "blockednoreason": "לא ניתנה סיבה",
-       "blockedtext-composite": "<strong>ש×\9d ×\94×\9eשת×\9eש ×\90×\95 ×\9bת×\95×\91ת ×\94Ö¾IP ×©×\9c×\9b×\9d × ×\97ס×\9e×\95 ×\9eער×\99×\9b×\94.</strong>\n\n×\94ס×\99×\91×\94 ×©× ×\99תנ×\94 ×\94×\99×\90:\n\n:<em>$2</em>.\n\n* ×ª×\97×\99×\9cת ×\94×\97ס×\99×\9e×\94: $8\n* ×¤×§×\99עת ×\94×\97ס×\99×\9e×\94 ×\94×\90ר×\95×\9b×\94 ×\91×\99×\95תר: $6\n\n×\9bת×\95×\91ת ×\94Ö¾IP ×\94× ×\95×\9b×\97×\99ת ×©×\9c×\9a ×\94×\99×\90 $3.\n×\99ש ×\9cספק ×\90ת ×\9b×\9c ×\94×\9e×\99×\93×¢ ×\94× \"×\9c ×¢×\91×\95ר ×\9b×\9c ×\94ש×\90×\99×\9cת×\95ת ×©×\90ת×\9d ×\9e×\91צע×\99×\9d.",
-       "blockedtext-composite-reason": "×\99שנ×\9f ×\9eספר ×\97ס×\99×\9e×\95ת ×¢×\9c ×\94×\97ש×\91×\95×\9f ×©×\9c×\9a ×\95\90×\95 ×\9bת×\95×\91ת ×\94Ö¾IP ×©×\9c×\9a",
+       "blockedtext-composite": "<strong>ש×\9d ×\94×\9eשת×\9eש ×\90×\95 ×\9bת×\95×\91ת ×\94Ö¾IP ×©×\9c×\9a × ×\97ס×\9e×\95.</strong>\n\n×\94ס×\99×\91×\94 ×©× ×\99תנ×\94 ×\9c×\9b×\9a ×\94×\99×\90:\n\n:<em>$2</em>.\n\n* ×ª×\97×\99×\9cת ×\94×\97ס×\99×\9e×\94: $8\n* ×¤×§×\99עת ×\94×\97ס×\99×\9e×\94 ×\94×\90ר×\95×\9b×\94 ×\91×\99×\95תר: $6\n\n×\9bת×\95×\91ת ×\94Ö¾IP ×\94× ×\95×\9b×\97×\99ת ×©×\9c×\9a ×\94×\99×\90 $3.\n×\99ש ×\9cצ×\99×\99×\9f ×\90ת ×\9b×\9c ×\94פר×\98×\99×\9d ×\94×\9c×\9c×\95 ×\91×\9b×\9c ×¤× ×\99×\99×\94 ×\9c×\91×\99ר×\95ר ×\94×\97ס×\99×\9e×\94.",
+       "blockedtext-composite-reason": "×\94×\95פע×\9c×\95 ×\9eספר ×\97ס×\99×\9e×\95ת ×¢×\9c ×\97ש×\91×\95×\9f ×\94×\9eשת×\9eש ×©×\9c×\9a ×\90×\95 ×¢×\9c ×\9bת×\95×\91ת ×\94Ö¾IP ×©×\9c×\9a (×\90×\95 ×¢×\9c ×©× ×\99×\94×\9d)",
        "whitelistedittext": "נדרשת $1 כדי לערוך דפים.",
        "confirmedittext": "יש לאמת את כתובת הדוא\"ל לפני עריכת דפים.\nנא להגדיר ולאמת את כתובת הדוא\"ל שלך באמצעות [[Special:Preferences|העדפות המשתמש]] שלך.",
        "nosuchsectiontitle": "הפסקה לא נמצאה",
        "gender-male": "הוא עורך דפים בוויקי",
        "gender-female": "היא עורכת דפים בוויקי",
        "prefs-help-gender": "לא חובה למלא העדפה זו.\nהמערכת משתמשת במידע הזה כדי לפנות אליך/אלייך ולציין את שם המשתמש שלך במין הדקדוקי הנכון.\nהמידע יהיה ציבורי.",
-       "email": "דוא״ל",
+       "email": "דוא\"ל",
        "prefs-help-realname": "לא חובה למלא את השם האמיתי.\nאם סופק, הוא עשוי לשמש כדי לייחס לך את עבודתך.",
        "prefs-help-email": "כתובת דואר אלקטרוני היא אופציונלית, אבל היא חיונית לאיפוס הסיסמה במקרה ש{{GENDER:|תשכח|תשכחי}} אותה.",
        "prefs-help-email-others": "באפשרותך גם לאפשר למשתמשים ליצור איתך קשר באמצעות דוא\"ל דרך קישור בדף המשתמש או בדף השיחה שלך.\nכתובת הדוא\"ל שלך לא תיחשף כשמשתמשים יצרו איתך קשר.",
        "mw-widgets-abandonedit-discard": "ביטול העריכות",
        "mw-widgets-abandonedit-keep": "המשך עריכה",
        "mw-widgets-abandonedit-title": "בטוח?",
-       "mw-widgets-copytextlayout-copy": "העתק",
+       "mw-widgets-copytextlayout-copy": "העתקה",
        "mw-widgets-copytextlayout-copy-fail": "ההעתקה ללוח נכשלה.",
        "mw-widgets-copytextlayout-copy-success": "הועתק ללוח.",
        "mw-widgets-dateinput-no-date": "לא נבחר תאריך",
        "edit-error-short": "שגיאה: $1",
        "edit-error-long": "שגיאות:\n\n$1",
        "specialmute": "השתקה",
-       "specialmute-success": "העדפות ההשתקה שלך עודכנו. ר' את כל המשתמשים המושתקים ב[[Special:Preferences|העדפות שלך]].",
+       "specialmute-success": "העדפות ההשתקה שלך עודכנו. רשימת כל המשתמשים המושתקים זמינה ב[[Special:Preferences|העדפות שלך]].",
        "specialmute-submit": "אישור",
-       "specialmute-label-mute-email": "×\9c×\94שת×\99ק דואר אלקטרוני מהמשתמש הזה",
-       "specialmute-header": "× ×\90 ×\9c×\91×\97×\95ר ×\90ת העדפות ההשתקה שלך עבור {{BIDI:[[User:$1]]}}.",
+       "specialmute-label-mute-email": "×\94שתקת ×\94×\95×\93×¢×\95ת דואר אלקטרוני מהמשתמש הזה",
+       "specialmute-header": "×\91×\97×\99ר×\95ת העדפות ההשתקה שלך עבור {{BIDI:[[User:$1]]}}.",
        "specialmute-error-invalid-user": "שם המשתמש המבוקש לא נמצא.",
-       "specialmute-error-email-blacklist-disabled": "השתקת משתמשים משליחת דואר אלקטרוני אליך אינה מופעלת.",
+       "specialmute-error-email-blacklist-disabled": "×\94×\90פשר×\95ת ×\9c×\94שתקת ×\9eשת×\9eש×\99×\9d ×\9eש×\9c×\99×\97ת ×\93×\95×\90ר ×\90×\9cק×\98ר×\95× ×\99 ×\90×\9c×\99×\9a ×\90×\99× ×\94 ×\9e×\95פע×\9cת.",
        "specialmute-error-email-preferences": "יש לאמת את כתובת הדואר האלקטרוני שלך לפני שתהיה לך אפשרות להשתיק משתמש. אפשר לעשות זאת מהדף [[Special:Preferences]].",
-       "specialmute-email-footer": "×\9b×\93×\99 ×\9c× ×\94×\9c ×\90ת ×\94×\94×¢×\93פ×\95ת ×¢×\91×\95ר {{BIDI:$2}} × ×\90 לבקר בדף <$1>.",
-       "specialmute-login-required": "× ×\90 ×\9c×\94×\99×\9bנס ×\9c×\97ש×\91×\95×\9f ×\9b×\93×\99 ×\9cש×\91ות את העדפות ההשתקה שלך.",
+       "specialmute-email-footer": "×\9b×\93×\99 ×\9c× ×\94×\9c ×\90ת ×\94×¢×\93פ×\95ת ×§×\91×\9cת ×\94×\93×\95×\90ר ×\94×\90×\9cק×\98ר×\95× ×\99 ×©× ×©×\9c×\97 ×¢×\9cÖ¾×\99×\93×\99 {{BIDI:$2}}, ×\91×\90פשר×\95ת×\9a לבקר בדף <$1>.",
+       "specialmute-login-required": "× ×\93רשת ×\9b× ×\99ס×\94 ×\9c×\97ש×\91×\95×\9f ×\9b×\93×\99 ×\9cשנות את העדפות ההשתקה שלך.",
        "revid": "גרסה $1",
        "pageid": "מזהה דף $1",
        "interfaceadmin-info": "$1\n\nההרשאות לעריכת קובצי CSS/JS/JSON של האתר כולו הופרדו לאחרונה מההרשאה <code>editinterface</code>. אם לא ברור לך מדוע קיבלת את הודעת השגיאה הזאת, ר' [[mw:MediaWiki_1.32/interface-admin]].",
index 7879fce..7764a21 100644 (file)
        "logentry-partialblock-block-ns": "{{PLURAL:$1|名前空間}} $2",
        "logentry-partialblock-block": "$1 が {{GENDER:$4|$3}} に対して $7 からの編集を $5 {{GENDER:$2||ブロックしました}} $6",
        "logentry-partialblock-reblock": "$1 が {{GENDER:$4|$3}} に対する $7 のブロックの期限を $5 に{{GENDER:$2|変更しました}} $6",
+       "logentry-non-editing-block-block": "$1 が {{GENDER:$4|$3}} に対して編集以外の処理を $5 $6 で{{GENDER:$2||ブロックしました}}",
+       "logentry-non-editing-block-reblock": "$1 が {{GENDER:$4|$3}} に対する特定の編集以外の処理のブロックの期限を $5 $6 に{{GENDER:$2|変更しました}}",
        "logentry-suppress-block": "$1 が {{GENDER:$4|$3}} を$5で{{GENDER:$2|ブロックしました}} $6",
        "logentry-suppress-reblock": "$1 が {{GENDER:$4|$3}} のブロックの期限を$5に{{GENDER:$2|変更しました}} $6",
        "logentry-import-upload": "$1 がファイルをアップロードして $3 を{{GENDER:$2|インポートしました}}",
        "edit-error-short": "エラー: $1",
        "edit-error-long": "エラー:\n\n\n\n$1",
        "specialmute": "ミュート",
+       "specialmute-success": "ミュートの個人設定が更新されました。[[Special:Preferences|ご自分の個人設定ページ]]でミューとした利用者の一覧を確認できます。",
+       "specialmute-submit": "確定",
        "specialmute-label-mute-email": "この利用者からのウィキメールをミュートする",
+       "specialmute-header": "{{BIDI:[[User:$1]]}}さんに対するミュートを個人設定で選択してください。",
        "specialmute-error-invalid-user": "あなたが要求した利用者名は見つかりませんでした。",
+       "specialmute-error-email-blacklist-disabled": "利用者からメールを受け取らないようにするミュートは設定されていません。",
+       "specialmute-error-email-preferences": "発信者をミューとする準備として、ご自分のeメールアドレスの認証が必要です。手続きは[[Special:Preferences|個人設定]]のページで行います。",
+       "specialmute-email-footer": "{{BIDI:$2}}のeメール発信者の個人設定を変更するには<$1>を開いてください。",
+       "specialmute-login-required": "ミュートの個人設定を変更するにはログインしてください。",
        "revid": "版 $1",
        "pageid": "ページID $1",
        "interfaceadmin-info": "$1\n\nサイト全体のCSS/JavaScriptの編集権限は、最近<code>editinterface</code> 権限から分離されました。なぜこのエラーが表示されたのかわからない場合は、[[mw:MediaWiki_1.32/interface-admin]]をご覧ください。",
index da07dd6..3a4ba28 100644 (file)
@@ -76,7 +76,8 @@
                        "Delim",
                        "Comjun04",
                        "Son77391",
-                       "Jango"
+                       "Jango",
+                       "D6283"
                ]
        },
        "tog-underline": "링크에 밑줄 긋기:",
        "logentry-block-block": "$1님이 {{GENDER:$4|$3}}님을 $5 {{GENDER:$2|차단했습니다}} $6",
        "logentry-block-unblock": "$1님이 {{GENDER:$4|$3}}님의 {{GENDER:$2|차단을 해제했습니다}}",
        "logentry-block-reblock": "$1 님이 {{GENDER:$4|$3}} 님의 차단 기간을 $5(으)로 {{GENDER:$2|바꾸었습니다}} $6",
-       "logentry-partialblock-block": "$1님이 {{GENDER:$4|$3}}님을 $7 {{PLURAL:$8|문서를|문서들을}} 편집하지 못하도록 $5 {{GENDER:$2|차단}}했습니다. $6",
+       "logentry-partialblock-block": "$1님이 {{GENDER:$4|$3}}님을 $7 편집하지 못하도록 $5 {{GENDER:$2|차단}}했습니다. $6",
        "logentry-suppress-block": "$1님이 {{GENDER:$4|$3}} 사용자를 $5 {{GENDER:$2|차단했습니다}} $6",
        "logentry-suppress-reblock": "$1 님이 {{GENDER:$4|$3}} 님의 차단 기간을 $5(으)로 {{GENDER:$2|바꾸었습니다}} $6",
        "logentry-import-upload": "$1님이 $3 문서를 파일 올리기로 {{GENDER:$2|가져왔습니다}}",
        "edit-error-short": "오류: $1",
        "edit-error-long": "오류:\n\n$1",
        "specialmute": "알림 미표시",
-       "specialmute-success": "ì\95\8c림 ë¯¸í\91\9cì\8b\9c í\99\98ê²½ ì\84¤ì \95ì\9d´ ì\84±ê³µì \81ì\9c¼ë¡\9c ì\97\85ë\8d°ì\9d´í\8a¸ë\90\98ì\97\88ì\8aµë\8b\88ë\8b¤. [[Special:Preferences]]에서 알림이 표시되지 않는 모든 사용자를 확인하십시오.",
+       "specialmute-success": "ì\95\8c림 ë¯¸í\91\9cì\8b\9c í\99\98ê²½ ì\84¤ì \95ì\9d´ ì\97\85ë\8d°ì\9d´í\8a¸ë\90\98ì\97\88ì\8aµë\8b\88ë\8b¤. [[Special:Preferences|í\99\98ê²½ ì\84¤ì \95]]에서 알림이 표시되지 않는 모든 사용자를 확인하십시오.",
        "specialmute-submit": "확인",
        "specialmute-label-mute-email": "이 사용자의 이메일 알림을 표시하지 않습니다",
        "specialmute-header": "{{BIDI:[[User:$1]]}}의 알림 미표시 환경 설정을 선택해 주십시오.",
index de47550..c29f5d6 100644 (file)
        "versionrequired": "Dibutuahan MediaWiki versi $1",
        "versionrequiredtext": "MediaWiki versi $1 dibutuahan untuak manggunoan laman ko. Caliak [[Special:Version|versi laman]]",
        "ok": "OK",
-       "pagetitle": "$1 - {{SITENAME}} bahaso Minang",
+       "pagetitle": "$1 - {{SITENAME}} Minangkabau",
        "pagetitle-view-mainpage": "{{SITENAME}} bahaso Minang",
        "backlinksubtitle": "← $1",
        "retrievedfrom": "Didapek dari \"$1\"",
index 75726e2..0ac7b3d 100644 (file)
        "minoredit": "Chisto è nu cagnamiénto piccerillo",
        "watchthis": "Tiene d'uocchio sta paggena",
        "savearticle": "Sarva 'a paggena",
-       "savechanges": "Sarva 'e cagnamiénte",
+       "savechanges": "Sarva",
        "publishpage": "Pubbreca paggena",
        "publishchanges": "Pubbreca 'e cagnamiente",
        "savearticle-start": "Sarva 'a paggena...",
index b658cd0..a011006 100644 (file)
        "search-filter-title-prefix": "ߞߐߜߍ ߡߍ߲ ߠߎ߬ ߞߎ߲߬ߕߐ߮ ߦߋ߫ ߘߊߡߌ߬ߣߊ߬ ߟߊ߫  \"$1\" ߡߊ߬ ߏ߬ ߟߎ߫ ߟߋ߬ ߘߐߙߐ߲߫ ߢߌߣߌ߲ ߦߴߌ ߘߐ߫.",
        "search-filter-title-prefix-reset": "ߞߐߜߍ ߓߍ߯ ߢߌߣߌ߲߫",
        "searchresults-title": "ߣߌ߲߬ \"$1\" ߢߌߣߌ߲ߠߌ߲ ߞߐߝߟߌ",
+       "titlematches": "ߞߐߜߍ ߞߎ߲߬ߕߐ߮ ߓߍ߲߬ߢߐ߲߰ߡߊ߬ߣߍ߲߫",
        "prevn": "ߕߊ߬ߡߌ߲߬ߣߍ߲ ߠߎ߬ {{PLURAL:$1|$1}}",
        "nextn": "ߟߊߕߎ߲߰ߠߊ {{PLURAL:$1|$1}}",
        "prev-page": "ߞߐߜߍ ߢߍߕߊ",
        "right-editmyuserjs": "ߌ ߖߘߍ߬ߞߊ߬ߣߌ߲߬ JavaScript ߞߐߕߐ߮ ߟߎ߬ ߡߊߦߟߍ߬ߡߊ߲߫",
        "right-viewmywatchlist": "ߌ ߖߘߍ߬ߞߊ߬ߣߌ߲߬ ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߦߋ߫",
        "right-editmyoptions": "ߌ ߖߘߍ߬ߞߊ߬ߣߌ߲߬ ߟߊߝߌߛߦߊߟߌ ߡߊߦߟߍ߬ߡߊ߲߫",
+       "right-import": "ߞߐߜߍ ߟߎ߬ ߟߊߛߣߍ߫ ߞߊ߬ ߓߐ߫ ߥߞߌ ߕߐ߭ ߟߎ߬ ߘߐ߫",
+       "right-importupload": "ߞߐߜߍ ߟߎ߬ ߟߊߛߣߍ߫ ߞߊ߬ ߓߐ߫ ߞߐߕߐ߯ ߟߊߦߟߍ߬ߣߍ߲ ߠߎ߬ ߘߐ߫",
+       "right-patrol": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߘߏ ߟߎ߬ ߟߊ߫ ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߣߍ߲ ߠߎ߬ ߣߐ߬ߣߐ߬.",
+       "right-autopatrol": "ߒ ߖߍ߬ߘߍ ߟߊ߫ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߣߍ߲ ߠߎ߬ ߞߍ߫ ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߣߍ߲ ߘߌ߫ ߞߍ߲ߒߖߘߍߦߋ߫ ߓߟߏߡߊ߬",
+       "right-patrolmarks": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߞߎߘߊ ߟߎ߬ ߦߋ߫ ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߠߌ߲߫ ߣߐ߬ߣߐ߬ߣߍ߲ ߘߌ߫",
        "right-unwatchedpages": "ߞߐߜߍ߫ ߜߋ߬ߟߎ߲߬ߓߊߟߌ ߟߎ߬ ߛߙߍߘߍ ߦߋ߫",
        "right-mergehistory": "ߞߐߜߍ ߟߊ߫ ߘߐ߬ߝߐ ߟߎ߬ ߞߍߢߐ߲߮ߞߊ߲߬",
        "right-userrights": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߤߊߞߍ ߓߍ߯ ߡߊߦߟߍ߬ߡߊ߲߫",
        "rcfilters-filterlist-feedbacklink": "ߌ ߤߊߞߟߌߣߊ߲ ߝߐ߫ ߊ߲ ߧߋ߫ ߞߊ߬ ߓߍ߲߬ ߛߍ߲ߛߍ߲ߟߊ߲ ߖߐ߯ߙߊ߲ ߠߊ߫ ߞߏ ߡߊ߬.",
        "rcfilters-highlightbutton-title": "ߞߐߝߟߌ߫ ߡߊߦߋߙߋ߲ߣߍ߲ ߠߎ߬",
        "rcfilters-highlightmenu-title": "ߞߐ߬ߟߐ ߘߏ߫ ߓߊߓߌ߬ߟߊ߬",
+       "rcfilters-filterlist-noresults": "ߛߍ߲ߛߍ߲ߟߊ߲߫ ߡߊ߫ ߛߐ߬ߘߐ߲߬",
        "rcfilters-filter-editsbyself-label": "ߡߍ߲ ߠߎ߬ ߡߊߦߟߍ߬ߡߊ߲߬ߣߍ߲߬ ߌ ߓߟߏ߫",
        "rcfilters-filter-editsbyself-description": "ߌ ߖߘߍ߬ߞߊ߬ߣߌ߲߬ ߓߟߏߓߌߟߊߢߐ߲߯ߞߊ߲.",
        "rcfilters-filter-editsbyother-label": "ߘߏ ߟߎ߬ ߟߊ߫ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߠߎ߬",
        "filedelete-reason-dropdown": "* ߖߏ߰ߛߌ߬ߟߌ ߟߎ߬ ߝߊ߲߬ߓߊ ߞߎ߲߭\n** ߓߊߦߟߍߡߊ߲ ߤߊߞߍ ߕߌߢߍߟߌ\n** ߞߐߕߐ߯ ߓߊߟߌߣߍ߲ ߠߎ߬",
        "filedelete-edit-reasonlist": "ߖߏ߰ߛߌ߬ߟߌ ߞߎ߲߭ ߡߊߦߟߍ߬ߡߊ߲߫",
        "filedelete-maintenance-title": "ߞߐߕߐ߮ ߕߍ߫ ߛߐ߲߬ ߖߏ߰ߛߌ߬ ߟߊ߫",
+       "mimetype": "MIME ߛߎ߮ߦߊ:",
+       "download": "ߟߊ߬ߖߌ߰ߒ߬ߞߎ߲߬ߠߌ߲",
+       "unwatchedpages": "ߞߐߜߍ߫ ߜߋ߬ߟߎ߲߬ߓߊߟߌ ߟߎ߬",
+       "listredirects": "ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲ ߛߙߍߘߍ ߟߎ߬",
+       "listduplicatedfiles": "ߞߐߕߐ߯ ߓߊߟߌߣߍ߲ ߠߎ߬ ߛߙߍߘߍ",
+       "listduplicatedfiles-entry": "[[:File:$1|$1]] ߓߘߊ߫ [[$3|{{PLURAL:$2|ߓߊߟߌ߫|ߟߎ߬ ߓߊߟߌߣߍ߲߫}}]]",
+       "unusedtemplates": "ߞߙߊߞߏ߫ ߟߊߓߊ߯ߙߊߓߊߟߌ ߟߎ߬",
        "unusedtemplateswlh": "ߛߘߌ߬ߜߋ߲ ߜߘߍ ߟߎ߬",
        "randompage": "ߞߎ߲߬ߝߍ߬ ߞߐߜߍ",
+       "randompage-nopages": "ߞߐߕߐ߯ ߛߌ߫ ߕߍ߫ ߢߌ߲߬ ߠߎ߬ ߘߐ߫ \n{{PLURAL:$2|ߕߐ߯ߛߓߍ ߞߣߍ|ߕߐ߯ߛߓߍ߫ ߞߣߍ ߟߎ߬}}: $1.",
        "randomincategory": "ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߞߐߜߍ ߦߌߟߡߊ ߘߐ߫",
+       "randomincategory-invalidcategory": "$1 ߕߍ߫ ߦߌߟߡߊ߫ ߕߐ߯ ߓߍ߲߬ߣߍ߲߬ ߘߌ߫.",
        "randomincategory-nopages": "ߞߐߜߍ߫ ߛߌ߫ ߕߍ߫  [[:Category:$1|$1]] ߘߌ߫ ߦߌߟߡߊ",
        "randomincategory-category": "ߦߌߟߡߊ",
        "randomincategory-legend": "ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߞߐߜߍ ߦߌߟߡߊ ߘߐ߫",
        "statistics-pages": "ߞߐߜߍ ߟߎ߬",
        "statistics-pages-desc": "ߞߐߜߍ ߡߍ߲ ߓߍ߯ ߦߋ߫ ߥߞߌ ߞߊ߲߬߸ ߦߏ߫ ߞߎߡߊߢߐ߲߯ߦߊ߫ ߞߐߜߍ߸ ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲߸ ߊ߬ ߣߌ߫.",
        "statistics-files": "ߞߐߕߐ߮ ߟߊߦߟߍ߬ߣߍ߲ ߠߎ߬",
+       "statistics-edits-average": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߡߎ߰ߡߍ ߡߍ߲ ߞߍߣߍ߲߫ ߞߐߜߍ ߡߊ߬",
+       "statistics-users": "ߟߊߓߊ߯ߙߊߓߊ߯ ߛߙߍߘߍߦߊߣߍ߲ ߠߎ߬",
+       "statistics-users-active-desc": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߡߍ߲ ߠߎ߬ ߝߊߘߌ߲ߧߊ߫ ߘߊ߫ ߞߏ߫ ߘߏ߫ ߞߍ {{PLURAL:$1|ߕߟߋ߬|$1 ߕߋ߬ߟߋ}} ߟߎ߬ ߞߘߐ߫.",
        "pageswithprop-submit": "ߕߊ߯",
        "double-redirect-fixer": "ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲ ߘߐߓߍ߲߬ߟߊ߲",
        "brokenredirects-edit": "ߊ߬ ߡߊߦߟߍ߬ߡߊ߲߬",
        "apisandbox-dynamic-parameters-add-label": "ߟߊ߬ߓߍ߲߬ߢߐ߲߰ߡߊ ߟߊߘߏ߲߬",
        "apisandbox-dynamic-parameters-add-placeholder": "ߟߊ߬ߓߍ߲߬ߢߐ߲߰ߡߊ ߕߐ߮",
        "apisandbox-dynamic-error-exists": "ߟߊ߬ߓߍ߲߬ߢߐ߲߰ߡߊ ߕߐ߮  \"$1\" ߦߋ߫ ߦߋ߲߬ ߞߘߐ߬ߡߊ߲߬.",
+       "apisandbox-fetch-token": "ߖߐߟߐ߲ߞߐ ߞߍߒߖߘߍߦߋ߫ ߟߝߊߟߌ",
        "apisandbox-add-multi": "ߟߊ߬ߘߏ߲߬ߠߌ߲",
        "apisandbox-results": "ߞߐߖߋߓߌ ߟߎ߬",
        "apisandbox-sending-request": "API ߡߊ߬ߢߌ߬ߣߌ߲߬ߞߊ߬ߟߌ ߗߋߟߌ ߦߴߌ ߘߐ߫...",
        "allpagessubmit": "ߥߊ߫",
        "allpages-hide-redirects": "ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲ ߢߡߊߘߏ߲߰",
        "categories": "ߦߌߟߡߊ ߟߎ߬",
+       "categoriesfrom": "ߦߌߟߡߊ ߟߎ߬ ߦߌ߬ߘߊ߬ߟߌ ߟߊߝߟߐ߫ ߣߌ߲߬ ߡߊ߬:",
+       "deletedcontributions": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߟߊ߫ ߓߟߏߡߊߜߍ߲ ߠߎ߬ ߓߘߊ߫ ߖߏ߬ߛߌ߬",
+       "deletedcontributions-title": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߟߊ߫ ߓߟߏߡߊߜߍ߲ ߓߘߊ߫ ߓߊ߲߫ ߖߏ߬ߛߌ߬ ߟߊ߫",
+       "sp-deletedcontributions-contribs": "ߓߟߏߓߌߟߊߢߐ߲߯ߞߊ߲ ߠߎ߬",
        "linksearch": "ߞߐߞߊ߲ߠߊ ߛߘߌ߬ߜߋ߲ ߢߌߣߌ߲ߠߌ߲",
+       "linksearch-ns": "ߕߐ߯ߛߓߍ ߞߣߍ:",
+       "linksearch-ok": "ߢߌߣߌ߲ߠߌ߲",
+       "linksearch-line": "$1 ߦߋ߫ ߛߘߌ߬ߜߋ߲ ߠߋ߬ ߘߌ߫ ߞߊ߬ ߓߐ߫ $2",
+       "listusersfrom": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߟߎ߬ ߦߌ߬ߘߊ߬ߟߌ ߟߊߝߟߐ߫ ߣߌ߲߬ ߡߊ߬:",
+       "listusers-submit": "ߦߌ߬ߘߊ߬ߟߌ",
+       "listusers-noresult": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߕߴߦߋ߲߬",
        "activeusers-noresult": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߕߴߦߋ߲߬",
        "listgrouprights-members": "(ߛߌ߲߬ߝߏ߲ ߠߎ߫ ߛߙߍߘߍ)",
        "emailuser": "ߗߋߛߓߍ ߗߋ߫ ߣߌ߲߬ ߕߌ߭ ߡߊ߬",
index a7f0c09..4400475 100644 (file)
@@ -73,6 +73,7 @@ wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" );
 wfRequireOnceInGlobalScope( "$IP/tests/common/TestsAutoLoader.php" );
 wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
 wfRequireOnceInGlobalScope( "$IP/includes/DefaultSettings.php" );
+wfRequireOnceInGlobalScope( "$IP/includes/GlobalFunctions.php" );
 
 // Load extensions/skins present in filesystem so that classes can be discovered.
 $directoryToJsonMap = [
diff --git a/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php b/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php
deleted file mode 100644 (file)
index bb71610..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfAppendQuery
- */
-class WfAppendQueryTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideAppendQuery
-        */
-       public function testAppendQuery( $url, $query, $expected, $message = null ) {
-               $this->assertEquals( $expected, wfAppendQuery( $url, $query ), $message );
-       }
-
-       public static function provideAppendQuery() {
-               return [
-                       [
-                               'http://www.example.org/index.php',
-                               '',
-                               'http://www.example.org/index.php',
-                               'No query'
-                       ],
-                       [
-                               'http://www.example.org/index.php',
-                               [ 'foo' => 'bar' ],
-                               'http://www.example.org/index.php?foo=bar',
-                               'Set query array'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foz=baz',
-                               'foo=bar',
-                               'http://www.example.org/index.php?foz=baz&foo=bar',
-                               'Set query string'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               '',
-                               'http://www.example.org/index.php?foo=bar',
-                               'Empty string with query'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               [ 'baz' => 'quux' ],
-                               'http://www.example.org/index.php?foo=bar&baz=quux',
-                               'Add query array'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               'baz=quux',
-                               'http://www.example.org/index.php?foo=bar&baz=quux',
-                               'Add query string'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               [ 'baz' => 'quux', 'foo' => 'baz' ],
-                               'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
-                               'Modify query array'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               'baz=quux&foo=baz',
-                               'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
-                               'Modify query string'
-                       ],
-                       [
-                               'http://www.example.org/index.php#baz',
-                               'foo=bar',
-                               'http://www.example.org/index.php?foo=bar#baz',
-                               'URL with fragment'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar#baz',
-                               'quux=blah',
-                               'http://www.example.org/index.php?foo=bar&quux=blah#baz',
-                               'URL with query string and fragment'
-                       ]
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php b/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php
deleted file mode 100644 (file)
index 65b56ef..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfArrayPlus2d
- */
-class WfArrayPlus2dTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideArrays
-        */
-       public function testWfArrayPlus2d( $baseArray, $newValues, $expected, $testName ) {
-               $this->assertEquals(
-                       $expected,
-                       wfArrayPlus2d( $baseArray, $newValues ),
-                       $testName
-               );
-       }
-
-       /**
-        * Provider for testing wfArrayPlus2d
-        *
-        * @return array
-        */
-       public static function provideArrays() {
-               return [
-                       // target array, new values array, expected result
-                       [
-                               [ 0 => '1dArray' ],
-                               [ 1 => '1dArray' ],
-                               [ 0 => '1dArray', 1 => '1dArray' ],
-                               "Test simple union of two arrays with different keys",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => '2dArray' ],
-                               ],
-                               [
-                                       0 => [ 1 => '2dArray' ],
-                               ],
-                               [
-                                       0 => [ 0 => '2dArray', 1 => '2dArray' ],
-                               ],
-                               "Test union of 2d arrays with different keys in the value array",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => '2dArray' ],
-                               ],
-                               [
-                                       0 => [ 0 => '1dArray' ],
-                               ],
-                               [
-                                       0 => [ 0 => '2dArray' ],
-                               ],
-                               "Test union of 2d arrays with same keys in the value array",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 1 => '2dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               "Test union of 3d array with different keys",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 1 => [ 0 => '2dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ], 1 => [ 0 => '2dArray' ] ],
-                               ],
-                               "Test union of 3d array with different keys in the value array",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 0 => '2dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               "Test union of 3d array with same keys in the value array",
-                       ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
deleted file mode 100644 (file)
index 7ddad36..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfAssembleUrl
- */
-class WfAssembleUrlTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideURLParts
-        */
-       public function testWfAssembleUrl( $parts, $output ) {
-               $partsDump = print_r( $parts, true );
-               $this->assertEquals(
-                       $output,
-                       wfAssembleUrl( $parts ),
-                       "Testing $partsDump assembles to $output"
-               );
-       }
-
-       /**
-        * Provider of URL parts for testing wfAssembleUrl()
-        *
-        * @return array
-        */
-       public static function provideURLParts() {
-               $schemes = [
-                       '' => [],
-                       '//' => [
-                               'delimiter' => '//',
-                       ],
-                       'http://' => [
-                               'scheme' => 'http',
-                               'delimiter' => '://',
-                       ],
-               ];
-
-               $hosts = [
-                       '' => [],
-                       'example.com' => [
-                               'host' => 'example.com',
-                       ],
-                       'example.com:123' => [
-                               'host' => 'example.com',
-                               'port' => 123,
-                       ],
-                       'id@example.com' => [
-                               'user' => 'id',
-                               'host' => 'example.com',
-                       ],
-                       'id@example.com:123' => [
-                               'user' => 'id',
-                               'host' => 'example.com',
-                               'port' => 123,
-                       ],
-                       'id:key@example.com' => [
-                               'user' => 'id',
-                               'pass' => 'key',
-                               'host' => 'example.com',
-                       ],
-                       'id:key@example.com:123' => [
-                               'user' => 'id',
-                               'pass' => 'key',
-                               'host' => 'example.com',
-                               'port' => 123,
-                       ],
-               ];
-
-               $cases = [];
-               foreach ( $schemes as $scheme => $schemeParts ) {
-                       foreach ( $hosts as $host => $hostParts ) {
-                               foreach ( [ '', '/path' ] as $path ) {
-                                       foreach ( [ '', 'query' ] as $query ) {
-                                               foreach ( [ '', 'fragment' ] as $fragment ) {
-                                                       $parts = array_merge(
-                                                               $schemeParts,
-                                                               $hostParts
-                                                       );
-                                                       $url = $scheme .
-                                                               $host .
-                                                               $path;
-
-                                                       if ( $path ) {
-                                                               $parts['path'] = $path;
-                                                       }
-                                                       if ( $query ) {
-                                                               $parts['query'] = $query;
-                                                               $url .= '?' . $query;
-                                                       }
-                                                       if ( $fragment ) {
-                                                               $parts['fragment'] = $fragment;
-                                                               $url .= '#' . $fragment;
-                                                       }
-
-                                                       $cases[] = [
-                                                               $parts,
-                                                               $url,
-                                                       ];
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               $complexURL = 'http://id:key@example.org:321' .
-                       '/over/there?name=ferret&foo=bar#nose';
-               $cases[] = [
-                       wfParseUrl( $complexURL ),
-                       $complexURL,
-               ];
-
-               return $cases;
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
deleted file mode 100644 (file)
index 78e09e6..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfBaseName
- */
-class WfBaseNameTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider providePaths
-        */
-       public function testBaseName( $fullpath, $basename ) {
-               $this->assertEquals( $basename, wfBaseName( $fullpath ),
-                       "wfBaseName('$fullpath') => '$basename'" );
-       }
-
-       public static function providePaths() {
-               return [
-                       [ '', '' ],
-                       [ '/', '' ],
-                       [ '\\', '' ],
-                       [ '//', '' ],
-                       [ '\\\\', '' ],
-                       [ 'a', 'a' ],
-                       [ 'aaaa', 'aaaa' ],
-                       [ '/a', 'a' ],
-                       [ '\\a', 'a' ],
-                       [ '/aaaa', 'aaaa' ],
-                       [ '\\aaaa', 'aaaa' ],
-                       [ '/aaaa/', 'aaaa' ],
-                       [ '\\aaaa\\', 'aaaa' ],
-                       [ '\\aaaa\\', 'aaaa' ],
-                       [
-                               '/mnt/upload3/wikipedia/en/thumb/8/8b/'
-                                       . 'Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg',
-                               '93px-Zork_Grand_Inquisitor_box_cover.jpg'
-                       ],
-                       [ 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ],
-                       [ 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php b/tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php
deleted file mode 100644 (file)
index 7402054..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfEscapeShellArg
- */
-class WfEscapeShellArgTest extends MediaWikiTestCase {
-       public function testSingleInput() {
-               if ( wfIsWindows() ) {
-                       $expected = '"blah"';
-               } else {
-                       $expected = "'blah'";
-               }
-
-               $actual = wfEscapeShellArg( 'blah' );
-
-               $this->assertEquals( $expected, $actual );
-       }
-
-       public function testMultipleArgs() {
-               if ( wfIsWindows() ) {
-                       $expected = '"foo" "bar" "baz"';
-               } else {
-                       $expected = "'foo' 'bar' 'baz'";
-               }
-
-               $actual = wfEscapeShellArg( 'foo', 'bar', 'baz' );
-
-               $this->assertEquals( $expected, $actual );
-       }
-
-       public function testMultipleArgsAsArray() {
-               if ( wfIsWindows() ) {
-                       $expected = '"foo" "bar" "baz"';
-               } else {
-                       $expected = "'foo' 'bar' 'baz'";
-               }
-
-               $actual = wfEscapeShellArg( [ 'foo', 'bar', 'baz' ] );
-
-               $this->assertEquals( $expected, $actual );
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php
deleted file mode 100644 (file)
index 8a7bfa5..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfGetCaller
- */
-class WfGetCallerTest extends MediaWikiTestCase {
-       public function testZero() {
-               $this->assertEquals( 'WfGetCallerTest->testZero', wfGetCaller( 1 ) );
-       }
-
-       function callerOne() {
-               return wfGetCaller();
-       }
-
-       public function testOne() {
-               $this->assertEquals( 'WfGetCallerTest->testOne', self::callerOne() );
-       }
-
-       static function intermediateFunction( $level = 2, $n = 0 ) {
-               if ( $n > 0 ) {
-                       return self::intermediateFunction( $level, $n - 1 );
-               }
-
-               return wfGetCaller( $level );
-       }
-
-       public function testTwo() {
-               $this->assertEquals( 'WfGetCallerTest->testTwo', self::intermediateFunction() );
-       }
-
-       public function testN() {
-               $this->assertEquals( 'WfGetCallerTest->testN', self::intermediateFunction( 2, 0 ) );
-               $this->assertEquals(
-                       'WfGetCallerTest::intermediateFunction',
-                       self::intermediateFunction( 1, 0 )
-               );
-
-               for ( $i = 0; $i < 10; $i++ ) {
-                       $this->assertEquals(
-                               'WfGetCallerTest::intermediateFunction',
-                               self::intermediateFunction( $i + 1, $i )
-                       );
-               }
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
deleted file mode 100644 (file)
index eae5588..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfRemoveDotSegments
- */
-class WfRemoveDotSegmentsTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider providePaths
-        */
-       public function testWfRemoveDotSegments( $inputPath, $outputPath ) {
-               $this->assertEquals(
-                       $outputPath,
-                       wfRemoveDotSegments( $inputPath ),
-                       "Testing $inputPath expands to $outputPath"
-               );
-       }
-
-       /**
-        * Provider of URL paths for testing wfRemoveDotSegments()
-        *
-        * @return array
-        */
-       public static function providePaths() {
-               return [
-                       [ '/a/b/c/./../../g', '/a/g' ],
-                       [ 'mid/content=5/../6', 'mid/6' ],
-                       [ '/a//../b', '/a/b' ],
-                       [ '/.../a', '/.../a' ],
-                       [ '.../a', '.../a' ],
-                       [ '', '' ],
-                       [ '/', '/' ],
-                       [ '//', '//' ],
-                       [ '.', '' ],
-                       [ '..', '' ],
-                       [ '...', '...' ],
-                       [ '/.', '/' ],
-                       [ '/..', '/' ],
-                       [ './', '' ],
-                       [ '../', '' ],
-                       [ './a', 'a' ],
-                       [ '../a', 'a' ],
-                       [ '../../a', 'a' ],
-                       [ '.././a', 'a' ],
-                       [ './../a', 'a' ],
-                       [ '././a', 'a' ],
-                       [ '../../', '' ],
-                       [ '.././', '' ],
-                       [ './../', '' ],
-                       [ '././', '' ],
-                       [ '../..', '' ],
-                       [ '../.', '' ],
-                       [ './..', '' ],
-                       [ './.', '' ],
-                       [ '/../../a', '/a' ],
-                       [ '/.././a', '/a' ],
-                       [ '/./../a', '/a' ],
-                       [ '/././a', '/a' ],
-                       [ '/../../', '/' ],
-                       [ '/.././', '/' ],
-                       [ '/./../', '/' ],
-                       [ '/././', '/' ],
-                       [ '/../..', '/' ],
-                       [ '/../.', '/' ],
-                       [ '/./..', '/' ],
-                       [ '/./.', '/' ],
-                       [ 'b/../../a', '/a' ],
-                       [ 'b/.././a', '/a' ],
-                       [ 'b/./../a', '/a' ],
-                       [ 'b/././a', 'b/a' ],
-                       [ 'b/../../', '/' ],
-                       [ 'b/.././', '/' ],
-                       [ 'b/./../', '/' ],
-                       [ 'b/././', 'b/' ],
-                       [ 'b/../..', '/' ],
-                       [ 'b/../.', '/' ],
-                       [ 'b/./..', '/' ],
-                       [ 'b/./.', 'b/' ],
-                       [ '/b/../../a', '/a' ],
-                       [ '/b/.././a', '/a' ],
-                       [ '/b/./../a', '/a' ],
-                       [ '/b/././a', '/b/a' ],
-                       [ '/b/../../', '/' ],
-                       [ '/b/.././', '/' ],
-                       [ '/b/./../', '/' ],
-                       [ '/b/././', '/b/' ],
-                       [ '/b/../..', '/' ],
-                       [ '/b/../.', '/' ],
-                       [ '/b/./..', '/' ],
-                       [ '/b/./.', '/b/' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
deleted file mode 100644 (file)
index 40b2e63..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfShorthandToInteger
- */
-class WfShorthandToIntegerTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideABunchOfShorthands
-        */
-       public function testWfShorthandToInteger( $input, $output, $description ) {
-               $this->assertEquals(
-                       wfShorthandToInteger( $input ),
-                       $output,
-                       $description
-               );
-       }
-
-       public static function provideABunchOfShorthands() {
-               return [
-                       [ '', -1, 'Empty string' ],
-                       [ '     ', -1, 'String of spaces' ],
-                       [ '1G', 1024 * 1024 * 1024, 'One gig uppercased' ],
-                       [ '1g', 1024 * 1024 * 1024, 'One gig lowercased' ],
-                       [ '1M', 1024 * 1024, 'One meg uppercased' ],
-                       [ '1m', 1024 * 1024, 'One meg lowercased' ],
-                       [ '1K', 1024, 'One kb uppercased' ],
-                       [ '1k', 1024, 'One kb lowercased' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php b/tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php
deleted file mode 100644 (file)
index 7f56b60..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfStringToBool
- */
-class WfStringToBoolTest extends MediaWikiTestCase {
-
-       public function getTestCases() {
-               return [
-                       [ 'true', true ],
-                       [ 'on', true ],
-                       [ 'yes', true ],
-                       [ 'TRUE', true ],
-                       [ 'YeS', true ],
-                       [ 'On', true ],
-                       [ '1', true ],
-                       [ '+1', true ],
-                       [ '01', true ],
-                       [ '-001', true ],
-                       [ '  1', true ],
-                       [ '-1  ', true ],
-                       [ '', false ],
-                       [ '0', false ],
-                       [ 'false', false ],
-                       [ 'NO', false ],
-                       [ 'NOT', false ],
-                       [ 'never', false ],
-                       [ '!&', false ],
-                       [ '-0', false ],
-                       [ '+0', false ],
-                       [ 'forget about it', false ],
-                       [ ' on', false ],
-                       [ 'true ', false ],
-               ];
-       }
-
-       /**
-        * @dataProvider getTestCases
-        * @param string $str
-        * @param bool $bool
-        */
-       public function testStr2Bool( $str, $bool ) {
-               if ( $bool ) {
-                       $this->assertTrue( wfStringToBool( $str ) );
-               } else {
-                       $this->assertFalse( wfStringToBool( $str ) );
-               }
-       }
-
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
deleted file mode 100644 (file)
index a70f136..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfTimestamp
- */
-class WfTimestampTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideNormalTimestamps
-        */
-       public function testNormalTimestamps( $input, $format, $output, $desc ) {
-               $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
-       }
-
-       public static function provideNormalTimestamps() {
-               $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
-
-               return [
-                       // TS_UNIX
-                       [ $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ],
-                       [ -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ],
-                       [ $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ],
-                       [ $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ],
-                       [ $t + 0.01, TS_MW, '20010115123456', 'TS_UNIX float to TS_MW' ],
-
-                       [ $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ],
-
-                       // TS_MW
-                       [ '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ],
-                       [ '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ],
-                       [ '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ],
-                       [ '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ],
-
-                       // TS_DB
-                       [ '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ],
-                       [ '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ],
-                       [ '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ],
-                       [
-                               '2001-01-15 12:34:56',
-                               TS_ISO_8601_BASIC,
-                               '20010115T123456Z',
-                               'TS_DB to TS_ISO_8601_BASIC'
-                       ],
-
-                       # rfc2822 section 3.3
-                       [ '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ],
-                       [ 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
-                       [
-                               ' Mon, 15 Jan 2001 12:34:56 GMT',
-                               TS_MW,
-                               '20010115123456',
-                               'TS_RFC2822 with leading space to TS_MW'
-                       ],
-                       [
-                               '15 Jan 2001 12:34:56 GMT',
-                               TS_MW,
-                               '20010115123456',
-                               'TS_RFC2822 without optional day-of-week to TS_MW'
-                       ],
-
-                       # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
-                       # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
-                       [ 'Mon, 15         Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
-
-                       # WSP = SP / HTAB ; rfc2234
-                       [
-                               "Mon, 15 Jan\x092001 12:34:56 GMT",
-                               TS_MW,
-                               '20010115123456',
-                               'TS_RFC2822 with HTAB to TS_MW'
-                       ],
-                       [
-                               "Mon, 15 Jan\x09 \x09  2001 12:34:56 GMT",
-                               TS_MW,
-                               '20010115123456',
-                               'TS_RFC2822 with HTAB and SP to TS_MW'
-                       ],
-                       [
-                               'Sun, 6 Nov 94 08:49:37 GMT',
-                               TS_MW,
-                               '19941106084937',
-                               'TS_RFC2822 with obsolete year to TS_MW'
-                       ],
-               ];
-       }
-
-       /**
-        * This test checks wfTimestamp() with values outside.
-        * It needs PHP 64 bits or PHP > 5.1.
-        * See r74778 and T27451
-        * @dataProvider provideOldTimestamps
-        */
-       public function testOldTimestamps( $input, $outputType, $output, $message ) {
-               $timestamp = wfTimestamp( $outputType, $input );
-               if ( substr( $output, 0, 1 ) === '/' ) {
-                       // T66946: Day of the week calculations for very old
-                       // timestamps varies from system to system.
-                       $this->assertRegExp( $output, $timestamp, $message );
-               } else {
-                       $this->assertEquals( $output, $timestamp, $message );
-               }
-       }
-
-       public static function provideOldTimestamps() {
-               return [
-                       [
-                               '19011213204554',
-                               TS_RFC2822,
-                               'Fri, 13 Dec 1901 20:45:54 GMT',
-                               'Earliest time according to PHP documentation'
-                       ],
-                       [ '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ],
-                       [ '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ],
-                       [ '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ],
-                       [ '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ],
-                       [
-                               '19011213204551',
-                               TS_RFC2822,
-                               'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1'
-                       ],
-                       [ '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ],
-                       [ '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ],
-                       [ '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ],
-                       [ '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ],
-                       [ '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ],
-                       [ '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ],
-                       [
-                               '0117-08-09 12:34:56',
-                               TS_RFC2822,
-                               '/, 09 Aug 0117 12:34:56 GMT$/',
-                               'Death of Roman Emperor [[Trajan]]'
-                       ],
-
-                       /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
-                       [ '-58979923200', TS_RFC2822, '/, 01 Jan 0101 00:00:00 GMT$/', '1/1/101' ],
-                       [ '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ],
-
-                       /* It is not clear if we should generate a year 0 or not
-                        * We are completely off RFC2822 requirement of year being
-                        * 1900 or later.
-                        */
-                       [
-                               '-62142076800',
-                               TS_RFC2822,
-                               'Wed, 18 Oct 0000 00:00:00 GMT',
-                               'ISO 8601:2004 [[year 0]], also called [[1 BC]]'
-                       ],
-               ];
-       }
-
-       /**
-        * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
-        * @dataProvider provideHttpDates
-        */
-       public function testHttpDate( $input, $output, $desc ) {
-               $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
-       }
-
-       public static function provideHttpDates() {
-               return [
-                       [ 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ],
-                       [ 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ],
-                       [ 'Sun Nov  6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ],
-                       // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
-                       [
-                               'Mon, 22 Nov 2010 14:12:42 GMT; length=52626',
-                               '20101122141242',
-                               'Netscape extension to HTTP/1.0'
-                       ],
-               ];
-       }
-
-       /**
-        * There are a number of assumptions in our codebase where wfTimestamp()
-        * should give the current date but it is not given a 0 there. See r71751 CR
-        */
-       public function testTimestampParameter() {
-               $now = wfTimestamp( TS_UNIX );
-               // We check that wfTimestamp doesn't return false (error) and use a LessThan assert
-               // for the cases where the test is run in a second boundary.
-
-               $zero = wfTimestamp( TS_UNIX, 0 );
-               $this->assertNotEquals( false, $zero );
-               $this->assertLessThan( 5, $zero - $now );
-
-               $empty = wfTimestamp( TS_UNIX, '' );
-               $this->assertNotEquals( false, $empty );
-               $this->assertLessThan( 5, $empty - $now );
-
-               $null = wfTimestamp( TS_UNIX, null );
-               $this->assertNotEquals( false, $null );
-               $this->assertLessThan( 5, $null - $now );
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php
deleted file mode 100644 (file)
index f9735c1..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-
-/**
- * The function only need a string parameter and might react to IIS7.0
- *
- * @group GlobalFunctions
- * @covers ::wfUrlencode
- */
-class WfUrlencodeTest extends MediaWikiTestCase {
-       # ### TESTS ##############################################################
-
-       /**
-        * @dataProvider provideURLS
-        */
-       public function testEncodingUrlWith( $input, $expected ) {
-               $this->verifyEncodingFor( 'Apache', $input, $expected );
-       }
-
-       /**
-        * @dataProvider provideURLS
-        */
-       public function testEncodingUrlWithMicrosoftIis7( $input, $expected ) {
-               $this->verifyEncodingFor( 'Microsoft-IIS/7', $input, $expected );
-       }
-
-       # ### HELPERS #############################################################
-
-       /**
-        * Internal helper that actually run the test.
-        * Called by the public methods testEncodingUrlWith...()
-        */
-       private function verifyEncodingFor( $server, $input, $expectations ) {
-               $expected = $this->extractExpect( $server, $expectations );
-
-               // save up global
-               $old = $_SERVER['SERVER_SOFTWARE'] ?? null;
-               $_SERVER['SERVER_SOFTWARE'] = $server;
-               wfUrlencode( null );
-
-               // do the requested test
-               $this->assertEquals(
-                       $expected,
-                       wfUrlencode( $input ),
-                       "Encoding '$input' for server '$server' should be '$expected'"
-               );
-
-               // restore global
-               if ( $old === null ) {
-                       unset( $_SERVER['SERVER_SOFTWARE'] );
-               } else {
-                       $_SERVER['SERVER_SOFTWARE'] = $old;
-               }
-               wfUrlencode( null );
-       }
-
-       /**
-        * Interprets the provider array. Return expected value depending
-        * the HTTP server name.
-        */
-       private function extractExpect( $server, $expectations ) {
-               if ( is_string( $expectations ) ) {
-                       return $expectations;
-               } elseif ( is_array( $expectations ) ) {
-                       if ( !array_key_exists( $server, $expectations ) ) {
-                               throw new MWException( __METHOD__ . " expectation does not have any "
-                                       . "value for server name $server. Check the provider array.\n" );
-                       } else {
-                               return $expectations[$server];
-                       }
-               } else {
-                       throw new MWException( __METHOD__ . " given invalid expectation for "
-                               . "'$server'. Should be a string or an array [ <http server name> => <string> ].\n" );
-               }
-       }
-
-       # ### PROVIDERS ###########################################################
-
-       /**
-        * Format is either:
-        *   [ 'input', 'expected' ];
-        * Or:
-        *   [ 'input',
-        *       [ 'Apache', 'expected' ],
-        *       [ 'Microsoft-IIS/7', 'expected' ],
-        *   ],
-        * If you want to add other HTTP server name, you will have to add a new
-        * testing method much like the testEncodingUrlWith() method above.
-        */
-       public static function provideURLS() {
-               return [
-                       # ## RFC 1738 chars
-                       // + is not safe
-                       [ '+', '%2B' ],
-                       // & and = not safe in queries
-                       [ '&', '%26' ],
-                       [ '=', '%3D' ],
-
-                       [ ':', [
-                               'Apache' => ':',
-                               'Microsoft-IIS/7' => '%3A',
-                       ] ],
-
-                       // remaining chars do not need encoding
-                       [
-                               ';@$-_.!*',
-                               ';@$-_.!*',
-                       ],
-
-                       # ## Other tests
-                       // slash remain unchanged. %2F seems to break things
-                       [ '/', '/' ],
-                       // T105265
-                       [ '~', '~' ],
-
-                       // Other 'funnies' chars
-                       [ '[]', '%5B%5D' ],
-                       [ '<>', '%3C%3E' ],
-
-                       // Apostrophe is encoded
-                       [ '\'', '%27' ],
-               ];
-       }
-}
index 448eec8..00b8d18 100644 (file)
@@ -2537,35 +2537,42 @@ class OutputPageTest extends MediaWikiTestCase {
                $rl = $out->getResourceLoader();
                $rl->setMessageBlobStore( $this->createMock( MessageBlobStore::class ) );
                $rl->register( [
-                       'test.foo' => new ResourceLoaderTestModule( [
+                       'test.foo' => [
+                               'class' => ResourceLoaderTestModule::class,
                                'script' => 'mw.test.foo( { a: true } );',
                                'styles' => '.mw-test-foo { content: "style"; }',
-                       ] ),
-                       'test.bar' => new ResourceLoaderTestModule( [
+                       ],
+                       'test.bar' => [
+                               'class' => ResourceLoaderTestModule::class,
                                'script' => 'mw.test.bar( { a: true } );',
                                'styles' => '.mw-test-bar { content: "style"; }',
-                       ] ),
-                       'test.baz' => new ResourceLoaderTestModule( [
+                       ],
+                       'test.baz' => [
+                               'class' => ResourceLoaderTestModule::class,
                                'script' => 'mw.test.baz( { a: true } );',
                                'styles' => '.mw-test-baz { content: "style"; }',
-                       ] ),
-                       'test.quux' => new ResourceLoaderTestModule( [
+                       ],
+                       'test.quux' => [
+                               'class' => ResourceLoaderTestModule::class,
                                'script' => 'mw.test.baz( { token: 123 } );',
                                'styles' => '/* pref-animate=off */ .mw-icon { transition: none; }',
                                'group' => 'private',
-                       ] ),
-                       'test.noscript' => new ResourceLoaderTestModule( [
+                       ],
+                       'test.noscript' => [
+                               'class' => ResourceLoaderTestModule::class,
                                'styles' => '.stuff { color: red; }',
                                'group' => 'noscript',
-                       ] ),
-                       'test.group.foo' => new ResourceLoaderTestModule( [
+                       ],
+                       'test.group.foo' => [
+                               'class' => ResourceLoaderTestModule::class,
                                'script' => 'mw.doStuff( "foo" );',
                                'group' => 'foo',
-                       ] ),
-                       'test.group.bar' => new ResourceLoaderTestModule( [
+                       ],
+                       'test.group.bar' => [
+                               'class' => ResourceLoaderTestModule::class,
                                'script' => 'mw.doStuff( "bar" );',
                                'group' => 'bar',
-                       ] ),
+                       ],
                ] );
                $links = $method->invokeArgs( $out, $args );
                $actualHtml = strval( $links );
@@ -2648,17 +2655,16 @@ class OutputPageTest extends MediaWikiTestCase {
                        ->setConstructorArgs( [ $ctx ] )
                        ->setMethods( [ 'buildCssLinksArray' ] )
                        ->getMock();
-               $op->expects( $this->any() )
-                       ->method( 'buildCssLinksArray' )
+               $op->method( 'buildCssLinksArray' )
                        ->willReturn( [] );
                $rl = $op->getResourceLoader();
                $rl->setMessageBlobStore( $this->createMock( MessageBlobStore::class ) );
 
                // Register custom modules
                $rl->register( [
-                       'example.site.a' => new ResourceLoaderTestModule( [ 'group' => 'site' ] ),
-                       'example.site.b' => new ResourceLoaderTestModule( [ 'group' => 'site' ] ),
-                       'example.user' => new ResourceLoaderTestModule( [ 'group' => 'user' ] ),
+                       'example.site.a' => [ 'class' => ResourceLoaderTestModule::class, 'group' => 'site' ],
+                       'example.site.b' => [ 'class' => ResourceLoaderTestModule::class, 'group' => 'site' ],
+                       'example.user' => [ 'class' => ResourceLoaderTestModule::class, 'group' => 'user' ],
                ] );
 
                $op = TestingAccessWrapper::newFromObject( $op );
diff --git a/tests/phpunit/includes/PathRouterTest.php b/tests/phpunit/includes/PathRouterTest.php
deleted file mode 100644 (file)
index d891675..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-<?php
-
-/**
- * Tests for the PathRouter parsing.
- *
- * @covers PathRouter
- */
-class PathRouterTest extends MediaWikiTestCase {
-
-       /**
-        * @var PathRouter
-        */
-       protected $basicRouter;
-
-       protected function setUp() {
-               parent::setUp();
-               $router = new PathRouter;
-               $router->add( "/wiki/$1" );
-               $this->basicRouter = $router;
-       }
-
-       public static function provideParse() {
-               $tests = [
-                       // Basic path parsing
-                       'Basic path parsing' => [
-                               "/wiki/$1",
-                               "/wiki/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       //
-                       'Loose path auto-$1: /$1' => [
-                               "/",
-                               "/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       'Loose path auto-$1: /wiki' => [
-                               "/wiki",
-                               "/wiki/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       'Loose path auto-$1: /wiki/' => [
-                               "/wiki/",
-                               "/wiki/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       // Ensure that path is based on specificity, not order
-                       'Order, /$1 added first' => [
-                               [ "/$1", "/a/$1", "/b/$1" ],
-                               "/a/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       'Order, /$1 added last' => [
-                               [ "/b/$1", "/a/$1", "/$1" ],
-                               "/a/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       // Handling of key based arrays with a url parameter
-                       'Key based array' => [
-                               [ [
-                                       'path' => [ 'edit' => "/edit/$1" ],
-                                       'params' => [ 'action' => '$key' ],
-                               ] ],
-                               "/edit/Foo",
-                               [ 'title' => "Foo", 'action' => 'edit' ]
-                       ],
-                       // Additional parameter
-                       'Basic $2' => [
-                               [ [
-                                       'path' => '/$2/$1',
-                                       'params' => [ 'test' => '$2' ]
-                               ] ],
-                               "/asdf/Foo",
-                               [ 'title' => "Foo", 'test' => 'asdf' ]
-                       ],
-               ];
-               // Shared patterns for restricted value parameter tests
-               $restrictedPatterns = [
-                       [
-                               'path' => '/$2/$1',
-                               'params' => [ 'test' => '$2' ],
-                               'options' => [ '$2' => [ 'a', 'b' ] ]
-                       ],
-                       [
-                               'path' => '/$2/$1',
-                               'params' => [ 'test2' => '$2' ],
-                               'options' => [ '$2' => 'c' ]
-                       ],
-                       '/$1'
-               ];
-               $tests += [
-                       // Restricted value parameter tests
-                       'Restricted 1' => [
-                               $restrictedPatterns,
-                               "/asdf/Foo",
-                               [ 'title' => "asdf/Foo" ]
-                       ],
-                       'Restricted 2' => [
-                               $restrictedPatterns,
-                               "/a/Foo",
-                               [ 'title' => "Foo", 'test' => 'a' ]
-                       ],
-                       'Restricted 3' => [
-                               $restrictedPatterns,
-                               "/c/Foo",
-                               [ 'title' => "Foo", 'test2' => 'c' ]
-                       ],
-
-                       // Callback test
-                       'Callback' => [
-                               [ [
-                                       'path' => "/$1",
-                                       'params' => [ 'a' => 'b', 'data:foo' => 'bar' ],
-                                       'options' => [ 'callback' => [ __CLASS__, 'callbackForTest' ] ]
-                               ] ],
-                               '/Foo',
-                               [
-                                       'title' => "Foo",
-                                       'x' => 'Foo',
-                                       'a' => 'b',
-                                       'foo' => 'bar'
-                               ]
-                       ],
-
-                       // Test to ensure that matches are not made if a parameter expects nonexistent input
-                       'Fail' => [
-                               [ [
-                                       'path' => "/wiki/$1",
-                                       'params' => [ 'title' => "$1$2" ],
-                               ] ],
-                               "/wiki/A",
-                               []
-                       ],
-
-                       // Make sure the router handles titles like Special:Recentchanges correctly
-                       'Special title' => [
-                               "/wiki/$1",
-                               "/wiki/Special:Recentchanges",
-                               [ 'title' => "Special:Recentchanges" ]
-                       ],
-
-                       // Make sure the router decodes urlencoding properly
-                       'URL encoding' => [
-                               "/wiki/$1",
-                               "/wiki/Title_With%20Space",
-                               [ 'title' => "Title_With Space" ]
-                       ],
-
-                       // Double slash and dot expansion
-                       'Double slash in prefix' => [
-                               '/wiki/$1',
-                               '//wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-                       'Double slash at start of $1' => [
-                               '/wiki/$1',
-                               '/wiki//Foo',
-                               [ 'title' => '/Foo' ]
-                       ],
-                       'Double slash in middle of $1' => [
-                               '/wiki/$1',
-                               '/wiki/.hack//SIGN',
-                               [ 'title' => '.hack//SIGN' ]
-                       ],
-                       'Dots removed 1' => [
-                               '/wiki/$1',
-                               '/x/../wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-                       'Dots removed 2' => [
-                               '/wiki/$1',
-                               '/./wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-                       'Dots retained 1' => [
-                               '/wiki/$1',
-                               '/wiki/../wiki/Foo',
-                               [ 'title' => '../wiki/Foo' ]
-                       ],
-                       'Dots retained 2' => [
-                               '/wiki/$1',
-                               '/wiki/./Foo',
-                               [ 'title' => './Foo' ]
-                       ],
-                       'Triple slash' => [
-                               '/wiki/$1',
-                               '///wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-                       // '..' only traverses one slash, see e.g. RFC 3986
-                       'Dots traversing double slash 1' => [
-                               '/wiki/$1',
-                               '/a//b/../../wiki/Foo',
-                               []
-                       ],
-                       'Dots traversing double slash 2' => [
-                               '/wiki/$1',
-                               '/a//b/../../../wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-               ];
-
-               // Make sure the router doesn't break on special characters like $ used in regexp replacements
-               foreach ( [ "$", "$1", "\\", "\\$1" ] as $char ) {
-                       $tests["Regexp character $char"] = [
-                               "/wiki/$1",
-                               "/wiki/$char",
-                               [ 'title' => "$char" ]
-                       ];
-               }
-
-               $tests += [
-                       // Make sure the router handles characters like +&() properly
-                       "Special characters" => [
-                               "/wiki/$1",
-                               "/wiki/Plus+And&Dollar\\Stuff();[]{}*",
-                               [ 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ],
-                       ],
-
-                       // Make sure the router handles unicode characters correctly
-                       "Unicode 1" => [
-                               "/wiki/$1",
-                               "/wiki/Spécial:Modifications_récentes" ,
-                               [ 'title' => "Spécial:Modifications_récentes" ],
-                       ],
-
-                       "Unicode 2" => [
-                               "/wiki/$1",
-                               "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes",
-                               [ 'title' => "Spécial:Modifications_récentes" ],
-                       ]
-               ];
-
-               // Ensure the router doesn't choke on long paths.
-               $lorem = "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_" .
-                       "tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_" .
-                        "nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._" .
-                        "Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_" .
-                        "eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_" .
-                        "in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum.";
-
-               $tests += [
-                       "Long path" => [
-                               "/wiki/$1",
-                               "/wiki/$lorem",
-                               [ 'title' => $lorem ]
-                       ],
-
-                       // Ensure that the php passed site of parameter values are not urldecoded
-                       "Pattern urlencoding" => [
-                               [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => '%20:$1' ] ] ],
-                               "/wiki/Foo",
-                               [ 'title' => '%20:Foo' ]
-                       ],
-
-                       // Ensure that raw parameter values do not have any variable replacements or urldecoding
-                       "Raw param value" => [
-                               [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => [ 'value' => 'bar%20$1' ] ] ] ],
-                               "/wiki/Foo",
-                               [ 'title' => 'bar%20$1' ]
-                       ]
-               ];
-
-               return $tests;
-       }
-
-       /**
-        * Test path parsing
-        * @dataProvider provideParse
-        */
-       public function testParse( $patterns, $path, $expected ) {
-               $patterns = (array)$patterns;
-
-               $router = new PathRouter;
-               foreach ( $patterns as $pattern ) {
-                       if ( is_array( $pattern ) ) {
-                               $router->add( $pattern['path'], $pattern['params'] ?? [],
-                                       $pattern['options'] ?? [] );
-                       } else {
-                               $router->add( $pattern );
-                       }
-               }
-               $matches = $router->parse( $path );
-               $this->assertEquals( $matches, $expected );
-       }
-
-       public static function callbackForTest( &$matches, $data ) {
-               $matches['x'] = $data['$1'];
-               $matches['foo'] = $data['foo'];
-       }
-
-       public static function provideWeight() {
-               return [
-                       [ '/Foo', [ 'title' => 'Foo' ] ],
-                       [ '/Bar', [ 'ping' => 'pong' ] ],
-                       [ '/Baz', [ 'marco' => 'polo' ] ],
-                       [ '/asdf-foo', [ 'title' => 'qwerty-foo' ] ],
-                       [ '/qwerty-bar', [ 'title' => 'asdf-bar' ] ],
-                       [ '/a/Foo', [ 'title' => 'Foo' ] ],
-                       [ '/asdf/Foo', [ 'title' => 'Foo' ] ],
-                       [ '/qwerty/Foo', [ 'title' => 'Foo', 'qwerty' => 'qwerty' ] ],
-                       [ '/baz/Foo', [ 'title' => 'Foo', 'unrestricted' => 'baz' ] ],
-                       [ '/y/Foo', [ 'title' => 'Foo', 'restricted-to-y' => 'y' ] ],
-               ];
-       }
-
-       /**
-        * Test to ensure weight of paths is handled correctly
-        * @dataProvider provideWeight
-        */
-       public function testWeight( $path, $expected ) {
-               $router = new PathRouter;
-               $router->addStrict( "/Bar", [ 'ping' => 'pong' ] );
-               $router->add( "/asdf-$1", [ 'title' => 'qwerty-$1' ] );
-               $router->add( "/$1" );
-               $router->add( "/qwerty-$1", [ 'title' => 'asdf-$1' ] );
-               $router->addStrict( "/Baz", [ 'marco' => 'polo' ] );
-               $router->add( "/a/$1" );
-               $router->add( "/asdf/$1" );
-               $router->add( "/$2/$1", [ 'unrestricted' => '$2' ] );
-               $router->add( [ 'qwerty' => "/qwerty/$1" ], [ 'qwerty' => '$key' ] );
-               $router->add( "/$2/$1", [ 'restricted-to-y' => '$2' ], [ '$2' => 'y' ] );
-
-               $this->assertEquals( $router->parse( $path ), $expected );
-       }
-}
diff --git a/tests/phpunit/includes/Rest/ResponseFactoryTest.php b/tests/phpunit/includes/Rest/ResponseFactoryTest.php
deleted file mode 100644 (file)
index ae71272..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use ArrayIterator;
-use MediaWiki\Rest\HttpException;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWikiTestCase;
-
-/** @covers \MediaWiki\Rest\ResponseFactory */
-class ResponseFactoryTest extends MediaWikiTestCase {
-       public static function provideEncodeJson() {
-               return [
-                       [ (object)[], '{}' ],
-                       [ '/', '"/"' ],
-                       [ '£', '"£"' ],
-                       [ [], '[]' ],
-               ];
-       }
-
-       /** @dataProvider provideEncodeJson */
-       public function testEncodeJson( $input, $expected ) {
-               $rf = new ResponseFactory;
-               $this->assertSame( $expected, $rf->encodeJson( $input ) );
-       }
-
-       public function testCreateJson() {
-               $rf = new ResponseFactory;
-               $response = $rf->createJson( [] );
-               $response->getBody()->rewind();
-               $this->assertSame( 'application/json', $response->getHeaderLine( 'Content-Type' ) );
-               $this->assertSame( '[]', $response->getBody()->getContents() );
-               // Make sure getSize() is functional, since testCreateNoContent() depends on it
-               $this->assertSame( 2, $response->getBody()->getSize() );
-       }
-
-       public function testCreateNoContent() {
-               $rf = new ResponseFactory;
-               $response = $rf->createNoContent();
-               $this->assertSame( [], $response->getHeader( 'Content-Type' ) );
-               $this->assertSame( 0, $response->getBody()->getSize() );
-               $this->assertSame( 204, $response->getStatusCode() );
-       }
-
-       public function testCreatePermanentRedirect() {
-               $rf = new ResponseFactory;
-               $response = $rf->createPermanentRedirect( 'http://www.example.com/' );
-               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
-               $this->assertSame( 301, $response->getStatusCode() );
-       }
-
-       public function testCreateLegacyTemporaryRedirect() {
-               $rf = new ResponseFactory;
-               $response = $rf->createLegacyTemporaryRedirect( 'http://www.example.com/' );
-               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
-               $this->assertSame( 302, $response->getStatusCode() );
-       }
-
-       public function testCreateTemporaryRedirect() {
-               $rf = new ResponseFactory;
-               $response = $rf->createTemporaryRedirect( 'http://www.example.com/' );
-               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
-               $this->assertSame( 307, $response->getStatusCode() );
-       }
-
-       public function testCreateSeeOther() {
-               $rf = new ResponseFactory;
-               $response = $rf->createSeeOther( 'http://www.example.com/' );
-               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
-               $this->assertSame( 303, $response->getStatusCode() );
-       }
-
-       public function testCreateNotModified() {
-               $rf = new ResponseFactory;
-               $response = $rf->createNotModified();
-               $this->assertSame( 0, $response->getBody()->getSize() );
-               $this->assertSame( 304, $response->getStatusCode() );
-       }
-
-       /** @expectedException \InvalidArgumentException */
-       public function testCreateHttpErrorInvalid() {
-               $rf = new ResponseFactory;
-               $rf->createHttpError( 200 );
-       }
-
-       public function testCreateHttpError() {
-               $rf = new ResponseFactory;
-               $response = $rf->createHttpError( 415, [ 'message' => '...' ] );
-               $this->assertSame( 415, $response->getStatusCode() );
-               $body = $response->getBody();
-               $body->rewind();
-               $data = json_decode( $body->getContents(), true );
-               $this->assertSame( 415, $data['httpCode'] );
-               $this->assertSame( '...', $data['message'] );
-       }
-
-       public function testCreateFromExceptionUnlogged() {
-               $rf = new ResponseFactory;
-               $response = $rf->createFromException( new HttpException( 'hello', 415 ) );
-               $this->assertSame( 415, $response->getStatusCode() );
-               $body = $response->getBody();
-               $body->rewind();
-               $data = json_decode( $body->getContents(), true );
-               $this->assertSame( 415, $data['httpCode'] );
-               $this->assertSame( 'hello', $data['message'] );
-       }
-
-       public function testCreateFromExceptionLogged() {
-               $rf = new ResponseFactory;
-               $response = $rf->createFromException( new \Exception( "hello", 415 ) );
-               $this->assertSame( 500, $response->getStatusCode() );
-               $body = $response->getBody();
-               $body->rewind();
-               $data = json_decode( $body->getContents(), true );
-               $this->assertSame( 500, $data['httpCode'] );
-               $this->assertSame( 'Error: exception of type Exception', $data['message'] );
-       }
-
-       public static function provideCreateFromReturnValue() {
-               return [
-                       [ 'hello', '{"value":"hello"}' ],
-                       [ true, '{"value":true}' ],
-                       [ [ 'x' => 'y' ], '{"x":"y"}' ],
-                       [ [ 'x', 'y' ], '["x","y"]' ],
-                       [ [ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
-                       [ (object)[ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
-                       [ [], '[]' ],
-                       [ (object)[], '{}' ],
-               ];
-       }
-
-       /** @dataProvider provideCreateFromReturnValue */
-       public function testCreateFromReturnValue( $input, $expected ) {
-               $rf = new ResponseFactory;
-               $response = $rf->createFromReturnValue( $input );
-               $body = $response->getBody();
-               $body->rewind();
-               $this->assertSame( $expected, $body->getContents() );
-       }
-
-       /** @expectedException \InvalidArgumentException */
-       public function testCreateFromReturnValueInvalid() {
-               $rf = new ResponseFactory;
-               $rf->createFromReturnValue( new ArrayIterator );
-       }
-}
diff --git a/tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php
deleted file mode 100644 (file)
index 5e32574..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\MainSlotRoleHandler;
-use MediaWikiTestCase;
-use PHPUnit\Framework\MockObject\MockObject;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler
- */
-class MainSlotRoleHandlerTest extends MediaWikiTestCase {
-
-       private function makeTitleObject( $ns ) {
-               /** @var Title|MockObject $title */
-               $title = $this->getMockBuilder( Title::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $title->method( 'getNamespace' )
-                       ->willReturn( $ns );
-
-               return $title;
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
-        */
-       public function testConstruction() {
-               $handler = new MainSlotRoleHandler( [] );
-               $this->assertSame( 'main', $handler->getRole() );
-               $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
-
-               $hints = $handler->getOutputLayoutHints();
-               $this->assertArrayHasKey( 'display', $hints );
-               $this->assertArrayHasKey( 'region', $hints );
-               $this->assertArrayHasKey( 'placement', $hints );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
-        */
-       public function testFetDefaultModel() {
-               $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
-
-               // For the main handler, the namespace determins the default model
-               $titleMain = $this->makeTitleObject( NS_MAIN );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
-
-               $title100 = $this->makeTitleObject( 100 );
-               $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
-        */
-       public function testIsAllowedModel() {
-               $handler = new MainSlotRoleHandler( [] );
-
-               // For the main handler, (nearly) all models are allowed
-               $title = $this->makeTitleObject( NS_MAIN );
-               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
-               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
-        */
-       public function testSupportsArticleCount() {
-               $handler = new MainSlotRoleHandler( [] );
-
-               $this->assertTrue( $handler->supportsArticleCount() );
-       }
-
-}
diff --git a/tests/phpunit/includes/Revision/SlotRecordTest.php b/tests/phpunit/includes/Revision/SlotRecordTest.php
deleted file mode 100644 (file)
index 6495967..0000000
+++ /dev/null
@@ -1,416 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use InvalidArgumentException;
-use LogicException;
-use MediaWiki\Revision\IncompleteRevisionException;
-use MediaWiki\Revision\SlotRecord;
-use MediaWiki\Revision\SuppressedDataException;
-use MediaWikiTestCase;
-use WikitextContent;
-
-/**
- * @covers \MediaWiki\Revision\SlotRecord
- */
-class SlotRecordTest extends MediaWikiTestCase {
-
-       private function makeRow( $data = [] ) {
-               $data = $data + [
-                       'slot_id' => 1234,
-                       'slot_content_id' => 33,
-                       'content_size' => '5',
-                       'content_sha1' => 'someHash',
-                       'content_address' => 'tt:456',
-                       'model_name' => CONTENT_MODEL_WIKITEXT,
-                       'format_name' => CONTENT_FORMAT_WIKITEXT,
-                       'slot_revision_id' => '2',
-                       'slot_origin' => '1',
-                       'role_name' => 'myRole',
-               ];
-               return (object)$data;
-       }
-
-       public function testCompleteConstruction() {
-               $row = $this->makeRow();
-               $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
-               $this->assertTrue( $record->hasAddress() );
-               $this->assertTrue( $record->hasContentId() );
-               $this->assertTrue( $record->hasRevision() );
-               $this->assertTrue( $record->isInherited() );
-               $this->assertSame( 'A', $record->getContent()->getText() );
-               $this->assertSame( 5, $record->getSize() );
-               $this->assertSame( 'someHash', $record->getSha1() );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
-               $this->assertSame( 2, $record->getRevision() );
-               $this->assertSame( 1, $record->getOrigin() );
-               $this->assertSame( 'tt:456', $record->getAddress() );
-               $this->assertSame( 33, $record->getContentId() );
-               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
-               $this->assertSame( 'myRole', $record->getRole() );
-       }
-
-       public function testConstructionDeferred() {
-               $row = $this->makeRow( [
-                       'content_size' => null, // to be computed
-                       'content_sha1' => null, // to be computed
-                       'format_name' => function () {
-                               return CONTENT_FORMAT_WIKITEXT;
-                       },
-                       'slot_revision_id' => '2',
-                       'slot_origin' => '2',
-                       'slot_content_id' => function () {
-                               return null;
-                       },
-               ] );
-
-               $content = function () {
-                       return new WikitextContent( 'A' );
-               };
-
-               $record = new SlotRecord( $row, $content );
-
-               $this->assertTrue( $record->hasAddress() );
-               $this->assertTrue( $record->hasRevision() );
-               $this->assertFalse( $record->hasContentId() );
-               $this->assertFalse( $record->isInherited() );
-               $this->assertSame( 'A', $record->getContent()->getText() );
-               $this->assertSame( 1, $record->getSize() );
-               $this->assertNotEmpty( $record->getSha1() );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
-               $this->assertSame( 2, $record->getRevision() );
-               $this->assertSame( 2, $record->getRevision() );
-               $this->assertSame( 'tt:456', $record->getAddress() );
-               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
-               $this->assertSame( 'myRole', $record->getRole() );
-       }
-
-       public function testNewUnsaved() {
-               $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
-
-               $this->assertFalse( $record->hasAddress() );
-               $this->assertFalse( $record->hasContentId() );
-               $this->assertFalse( $record->hasRevision() );
-               $this->assertFalse( $record->isInherited() );
-               $this->assertFalse( $record->hasOrigin() );
-               $this->assertSame( 'A', $record->getContent()->getText() );
-               $this->assertSame( 1, $record->getSize() );
-               $this->assertNotEmpty( $record->getSha1() );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
-               $this->assertSame( 'myRole', $record->getRole() );
-       }
-
-       public function provideInvalidConstruction() {
-               yield 'both null' => [ null, null ];
-               yield 'null row' => [ null, new WikitextContent( 'A' ) ];
-               yield 'array row' => [ [], new WikitextContent( 'A' ) ];
-               yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
-               yield 'null content' => [ (object)[], null ];
-       }
-
-       /**
-        * @dataProvider provideInvalidConstruction
-        */
-       public function testInvalidConstruction( $row, $content ) {
-               $this->setExpectedException( InvalidArgumentException::class );
-               new SlotRecord( $row, $content );
-       }
-
-       public function testGetContentId_fails() {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getContentId();
-       }
-
-       public function testGetAddress_fails() {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getAddress();
-       }
-
-       public function provideIncomplete() {
-               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               yield 'unsaved' => [ $unsaved ];
-
-               $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
-               $inherited = SlotRecord::newInherited( $parent );
-               yield 'inherited' => [ $inherited ];
-       }
-
-       /**
-        * @dataProvider provideIncomplete
-        */
-       public function testGetRevision_fails( SlotRecord $record ) {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getRevision();
-       }
-
-       /**
-        * @dataProvider provideIncomplete
-        */
-       public function testGetOrigin_fails( SlotRecord $record ) {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getOrigin();
-       }
-
-       public function provideHashStability() {
-               yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
-               yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
-       }
-
-       /**
-        * @dataProvider provideHashStability
-        */
-       public function testHashStability( $text, $hash ) {
-               // Changing the output of the hash function will break things horribly!
-
-               $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
-
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
-               $this->assertSame( $hash, $record->getSha1() );
-       }
-
-       public function testHashComputed() {
-               $row = $this->makeRow();
-               $row->content_sha1 = '';
-
-               $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
-               $this->assertNotEmpty( $rec->getSha1() );
-       }
-
-       public function testNewWithSuppressedContent() {
-               $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
-               $output = SlotRecord::newWithSuppressedContent( $input );
-
-               $this->setExpectedException( SuppressedDataException::class );
-               $output->getContent();
-       }
-
-       public function testNewInherited() {
-               $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
-               $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
-               // This would happen while doing an edit, before saving revision meta-data.
-               $inherited = SlotRecord::newInherited( $parent );
-
-               $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
-               $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
-               $this->assertSame( $parent->getContent(), $inherited->getContent() );
-               $this->assertTrue( $inherited->isInherited() );
-               $this->assertTrue( $inherited->hasOrigin() );
-               $this->assertFalse( $inherited->hasRevision() );
-
-               // make sure we didn't mess with the internal state of $parent
-               $this->assertFalse( $parent->isInherited() );
-               $this->assertSame( 7, $parent->getRevision() );
-
-               // This would happen while doing an edit, after saving the revision meta-data
-               // and content meta-data.
-               $saved = SlotRecord::newSaved(
-                       10,
-                       $inherited->getContentId(),
-                       $inherited->getAddress(),
-                       $inherited
-               );
-               $this->assertSame( $parent->getContentId(), $saved->getContentId() );
-               $this->assertSame( $parent->getAddress(), $saved->getAddress() );
-               $this->assertSame( $parent->getContent(), $saved->getContent() );
-               $this->assertTrue( $saved->isInherited() );
-               $this->assertTrue( $saved->hasRevision() );
-               $this->assertSame( 10, $saved->getRevision() );
-
-               // make sure we didn't mess with the internal state of $parent or $inherited
-               $this->assertSame( 7, $parent->getRevision() );
-               $this->assertFalse( $inherited->hasRevision() );
-       }
-
-       public function testNewSaved() {
-               // This would happen while doing an edit, before saving revision meta-data.
-               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
-               // This would happen while doing an edit, after saving the revision meta-data
-               // and content meta-data.
-               $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
-               $this->assertFalse( $saved->isInherited() );
-               $this->assertTrue( $saved->hasOrigin() );
-               $this->assertTrue( $saved->hasRevision() );
-               $this->assertTrue( $saved->hasAddress() );
-               $this->assertTrue( $saved->hasContentId() );
-               $this->assertSame( 'theNewAddress', $saved->getAddress() );
-               $this->assertSame( 20, $saved->getContentId() );
-               $this->assertSame( 'A', $saved->getContent()->getText() );
-               $this->assertSame( 10, $saved->getRevision() );
-               $this->assertSame( 10, $saved->getOrigin() );
-
-               // make sure we didn't mess with the internal state of $unsaved
-               $this->assertFalse( $unsaved->hasAddress() );
-               $this->assertFalse( $unsaved->hasContentId() );
-               $this->assertFalse( $unsaved->hasRevision() );
-       }
-
-       public function provideNewSaved_LogicException() {
-               $freshRow = $this->makeRow( [
-                       'content_id' => 10,
-                       'content_address' => 'address:1',
-                       'slot_origin' => 1,
-                       'slot_revision_id' => 1,
-               ] );
-
-               $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
-               yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
-               yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
-               yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
-
-               $inheritedRow = $this->makeRow( [
-                       'content_id' => null,
-                       'content_address' => null,
-                       'slot_origin' => 0,
-                       'slot_revision_id' => 1,
-               ] );
-
-               $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
-               yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
-       }
-
-       /**
-        * @dataProvider provideNewSaved_LogicException
-        */
-       public function testNewSaved_LogicException(
-               $revisionId,
-               $contentId,
-               $contentAddress,
-               SlotRecord $protoSlot
-       ) {
-               $this->setExpectedException( LogicException::class );
-               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
-       }
-
-       public function provideNewSaved_InvalidArgumentException() {
-               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
-               yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
-               yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
-               yield 'bad content address' => [ 7, 5, 77, $unsaved ];
-       }
-
-       /**
-        * @dataProvider provideNewSaved_InvalidArgumentException
-        */
-       public function testNewSaved_InvalidArgumentException(
-               $revisionId,
-               $contentId,
-               $contentAddress,
-               SlotRecord $protoSlot
-       ) {
-               $this->setExpectedException( InvalidArgumentException::class );
-               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
-       }
-
-       public function provideHasSameContent() {
-               $fail = function () {
-                       self::fail( 'There should be no need to actually load the content.' );
-               };
-
-               $a100a1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a1',
-                               ]
-                       ),
-                       $fail
-               );
-               $a100a1b = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a1',
-                               ]
-                       ),
-                       $fail
-               );
-               $a100null = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => null,
-                               ]
-                       ),
-                       $fail
-               );
-               $a100a2 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a2',
-                               ]
-                       ),
-                       $fail
-               );
-               $b100a1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'B',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a1',
-                               ]
-                       ),
-                       $fail
-               );
-               $a200a1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 200,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a2',
-                               ]
-                       ),
-                       $fail
-               );
-               $a100x1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-x',
-                                       'content_address' => 'xxx:x1',
-                               ]
-                       ),
-                       $fail
-               );
-
-               yield 'same instance' => [ $a100a1, $a100a1, true ];
-               yield 'no address' => [ $a100a1, $a100null, true ];
-               yield 'same address' => [ $a100a1, $a100a1b, true ];
-               yield 'different address' => [ $a100a1, $a100a2, true ];
-               yield 'different model' => [ $a100a1, $b100a1, false ];
-               yield 'different size' => [ $a100a1, $a200a1, false ];
-               yield 'different hash' => [ $a100a1, $a100x1, false ];
-       }
-
-       /**
-        * @dataProvider provideHasSameContent
-        */
-       public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
-               $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
-               $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
-       }
-
-}
diff --git a/tests/phpunit/includes/TitleArrayFromResultTest.php b/tests/phpunit/includes/TitleArrayFromResultTest.php
deleted file mode 100644 (file)
index 32c7571..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-<?php
-
-/**
- * @author Addshore
- * @covers TitleArrayFromResult
- */
-class TitleArrayFromResultTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       private function getMockResultWrapper( $row = null, $numRows = 1 ) {
-               $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
-                       ->disableOriginalConstructor();
-
-               $resultWrapper = $resultWrapper->getMock();
-               $resultWrapper->expects( $this->atLeastOnce() )
-                       ->method( 'current' )
-                       ->will( $this->returnValue( $row ) );
-               $resultWrapper->expects( $this->any() )
-                       ->method( 'numRows' )
-                       ->will( $this->returnValue( $numRows ) );
-
-               return $resultWrapper;
-       }
-
-       private function getRowWithTitle( $namespace = 3, $title = 'foo' ) {
-               $row = new stdClass();
-               $row->page_namespace = $namespace;
-               $row->page_title = $title;
-               return $row;
-       }
-
-       /**
-        * @covers TitleArrayFromResult::__construct
-        */
-       public function testConstructionWithFalseRow() {
-               $row = false;
-               $resultWrapper = $this->getMockResultWrapper( $row );
-
-               $object = new TitleArrayFromResult( $resultWrapper );
-
-               $this->assertEquals( $resultWrapper, $object->res );
-               $this->assertSame( 0, $object->key );
-               $this->assertEquals( $row, $object->current );
-       }
-
-       /**
-        * @covers TitleArrayFromResult::__construct
-        */
-       public function testConstructionWithRow() {
-               $namespace = 0;
-               $title = 'foo';
-               $row = $this->getRowWithTitle( $namespace, $title );
-               $resultWrapper = $this->getMockResultWrapper( $row );
-
-               $object = new TitleArrayFromResult( $resultWrapper );
-
-               $this->assertEquals( $resultWrapper, $object->res );
-               $this->assertSame( 0, $object->key );
-               $this->assertInstanceOf( Title::class, $object->current );
-               $this->assertEquals( $namespace, $object->current->mNamespace );
-               $this->assertEquals( $title, $object->current->mTextform );
-       }
-
-       public static function provideNumberOfRows() {
-               return [
-                       [ 0 ],
-                       [ 1 ],
-                       [ 122 ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideNumberOfRows
-        * @covers TitleArrayFromResult::count
-        */
-       public function testCountWithVaryingValues( $numRows ) {
-               $object = new TitleArrayFromResult( $this->getMockResultWrapper(
-                       $this->getRowWithTitle(),
-                       $numRows
-               ) );
-               $this->assertEquals( $numRows, $object->count() );
-       }
-
-       /**
-        * @covers TitleArrayFromResult::current
-        */
-       public function testCurrentAfterConstruction() {
-               $namespace = 0;
-               $title = 'foo';
-               $row = $this->getRowWithTitle( $namespace, $title );
-               $object = new TitleArrayFromResult( $this->getMockResultWrapper( $row ) );
-               $this->assertInstanceOf( Title::class, $object->current() );
-               $this->assertEquals( $namespace, $object->current->mNamespace );
-               $this->assertEquals( $title, $object->current->mTextform );
-       }
-
-       public function provideTestValid() {
-               return [
-                       [ $this->getRowWithTitle(), true ],
-                       [ false, false ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideTestValid
-        * @covers TitleArrayFromResult::valid
-        */
-       public function testValid( $input, $expected ) {
-               $object = new TitleArrayFromResult( $this->getMockResultWrapper( $input ) );
-               $this->assertEquals( $expected, $object->valid() );
-       }
-
-       // @todo unit test for key()
-       // @todo unit test for next()
-       // @todo unit test for rewind()
-}
diff --git a/tests/phpunit/includes/WikiReferenceTest.php b/tests/phpunit/includes/WikiReferenceTest.php
deleted file mode 100644 (file)
index e4b21ce..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-<?php
-
-/**
- * @covers WikiReference
- */
-class WikiReferenceTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       public function provideGetDisplayName() {
-               return [
-                       'http' => [ 'foo.bar', 'http://foo.bar' ],
-                       'https' => [ 'foo.bar', 'http://foo.bar' ],
-
-                       // apparently, this is the expected behavior
-                       'invalid' => [ 'purple kittens', 'purple kittens' ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetDisplayName
-        */
-       public function testGetDisplayName( $expected, $canonicalServer ) {
-               $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
-               $this->assertEquals( $expected, $reference->getDisplayName() );
-       }
-
-       public function testGetCanonicalServer() {
-               $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
-               $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
-       }
-
-       public function provideGetCanonicalUrl() {
-               return [
-                       'no fragment' => [
-                               'https://acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               null
-                       ],
-                       'empty fragment' => [
-                               'https://acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               ''
-                       ],
-                       'fragment' => [
-                               'https://acme.com/wiki/Foo#Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar'
-                       ],
-                       'double fragment' => [
-                               'https://acme.com/wiki/Foo#Bar%23Xus',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar#Xus'
-                       ],
-                       'escaped fragment' => [
-                               'https://acme.com/wiki/Foo%23Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo#Bar',
-                               null
-                       ],
-                       'empty path' => [
-                               'https://acme.com/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/$1',
-                               'Foo',
-                               null
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetCanonicalUrl
-        */
-       public function testGetCanonicalUrl(
-               $expected, $canonicalServer, $server, $path, $page, $fragmentId
-       ) {
-               $reference = new WikiReference( $canonicalServer, $path, $server );
-               $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
-       }
-
-       /**
-        * @dataProvider provideGetCanonicalUrl
-        * @note getUrl is an alias for getCanonicalUrl
-        */
-       public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
-               $reference = new WikiReference( $canonicalServer, $path, $server );
-               $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
-       }
-
-       public function provideGetFullUrl() {
-               return [
-                       'no fragment' => [
-                               '//acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               null
-                       ],
-                       'empty fragment' => [
-                               '//acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               ''
-                       ],
-                       'fragment' => [
-                               '//acme.com/wiki/Foo#Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar'
-                       ],
-                       'double fragment' => [
-                               '//acme.com/wiki/Foo#Bar%23Xus',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar#Xus'
-                       ],
-                       'escaped fragment' => [
-                               '//acme.com/wiki/Foo%23Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo#Bar',
-                               null
-                       ],
-                       'empty path' => [
-                               '//acme.com/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/$1',
-                               'Foo',
-                               null
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetFullUrl
-        */
-       public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
-               $reference = new WikiReference( $canonicalServer, $path, $server );
-               $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
-       }
-
-}
diff --git a/tests/phpunit/includes/debug/logger/monolog/CeeFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/CeeFormatterTest.php
deleted file mode 100644 (file)
index b30c7a4..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-namespace MediaWiki\Logger\Monolog;
-
-/**
- * Flay per https://phabricator.wikimedia.org/T218688.
- *
- * @group Broken
- * @covers \MediaWiki\Logger\Monolog\CeeFormatter
- */
-class CeeFormatterTest extends \PHPUnit\Framework\TestCase {
-       public function testV1() {
-               $ls_formatter = new LogstashFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
-               $cee_formatter = new CeeFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
-               $record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ];
-               $this->assertSame(
-                       $cee_formatter->format( $record ),
-                       "@cee: " . $ls_formatter->format( $record ) );
-       }
-}
diff --git a/tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php b/tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php
deleted file mode 100644 (file)
index fe129b7..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-/**
- * @covers DifferenceEngineSlotDiffRenderer
- */
-class DifferenceEngineSlotDiffRendererTest extends \PHPUnit\Framework\TestCase {
-
-       public function testGetDiff() {
-               $differenceEngine = new CustomDifferenceEngine();
-               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
-               $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
-               $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
-
-               $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
-               $this->assertEquals( 'xxx|yyy', $diff );
-
-               $diff = $slotDiffRenderer->getDiff( null, $newContent );
-               $this->assertEquals( '|yyy', $diff );
-
-               $diff = $slotDiffRenderer->getDiff( $oldContent, null );
-               $this->assertEquals( 'xxx|', $diff );
-       }
-
-       public function testAddModules() {
-               $output = $this->getMockBuilder( OutputPage::class )
-                       ->disableOriginalConstructor()
-                       ->setMethods( [ 'addModules' ] )
-                       ->getMock();
-               $output->expects( $this->once() )
-                       ->method( 'addModules' )
-                       ->with( 'foo' );
-               $differenceEngine = new CustomDifferenceEngine();
-               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
-               $slotDiffRenderer->addModules( $output );
-       }
-
-       public function testGetExtraCacheKeys() {
-               $differenceEngine = new CustomDifferenceEngine();
-               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
-               $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
-               $this->assertSame( [ 'foo' ], $extraCacheKeys );
-       }
-
-}
diff --git a/tests/phpunit/includes/diff/SlotDiffRendererTest.php b/tests/phpunit/includes/diff/SlotDiffRendererTest.php
deleted file mode 100644 (file)
index a03280d..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-
-use Wikimedia\Assert\ParameterTypeException;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers SlotDiffRenderer
- */
-class SlotDiffRendererTest extends \PHPUnit\Framework\TestCase {
-
-       /**
-        * @dataProvider provideNormalizeContents
-        */
-       public function testNormalizeContents(
-               $oldContent, $newContent, $allowedClasses,
-               $expectedOldContent, $expectedNewContent, $expectedExceptionClass
-       ) {
-               $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
-                       ->getMock();
-               try {
-                       // __call needs help deciding which parameter to take by reference
-                       call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
-                               'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
-                       $this->assertEquals( $expectedOldContent, $oldContent );
-                       $this->assertEquals( $expectedNewContent, $newContent );
-               } catch ( Exception $e ) {
-                       if ( !$expectedExceptionClass ) {
-                               throw $e;
-                       }
-                       $this->assertInstanceOf( $expectedExceptionClass, $e );
-               }
-       }
-
-       public function provideNormalizeContents() {
-               return [
-                       'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
-                       'left null' => [
-                               null, new WikitextContent( 'abc' ), null,
-                               new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
-                       ],
-                       'right null' => [
-                               new WikitextContent( 'def' ), null, null,
-                               new WikitextContent( 'def' ), new WikitextContent( '' ), null,
-                       ],
-                       'type filter' => [
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
-                       ],
-                       'type filter (subclass)' => [
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
-                       ],
-                       'type filter (null)' => [
-                               new WikitextContent( 'abc' ), null, TextContent::class,
-                               new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
-                       ],
-                       'type filter failure (left)' => [
-                               new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
-                               null, null, ParameterTypeException::class,
-                       ],
-                       'type filter failure (right)' => [
-                               new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
-                               null, null, ParameterTypeException::class,
-                       ],
-                       'type filter (array syntax)' => [
-                               new WikitextContent( 'abc' ), new JsonContent( 'def' ),
-                               [ JsonContent::class, WikitextContent::class ],
-                               new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
-                       ],
-                       'type filter failure (array syntax)' => [
-                               new WikitextContent( 'abc' ), new CssContent( 'def' ),
-                               [ JsonContent::class, WikitextContent::class ],
-                               null, null, ParameterTypeException::class,
-                       ],
-               ];
-       }
-
-}
diff --git a/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php
deleted file mode 100644 (file)
index 346be7a..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-<?php
-
-class FileBackendDBRepoWrapperTest extends MediaWikiTestCase {
-       protected $backendName = 'foo-backend';
-       protected $repoName = 'pureTestRepo';
-
-       /**
-        * @dataProvider getBackendPathsProvider
-        * @covers FileBackendDBRepoWrapper::getBackendPaths
-        */
-       public function testGetBackendPaths(
-               $mocks,
-               $latest,
-               $dbReadsExpected,
-               $dbReturnValue,
-               $originalPath,
-               $expectedBackendPath,
-               $message ) {
-               list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
-
-               $dbMock->expects( $dbReadsExpected )
-                       ->method( 'selectField' )
-                       ->will( $this->returnValue( $dbReturnValue ) );
-
-               $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
-
-               $this->assertEquals(
-                       $expectedBackendPath,
-                       $newPaths[0],
-                       $message );
-       }
-
-       public function getBackendPathsProvider() {
-               $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
-               $mocksForCaching = $this->getMocks();
-
-               return [
-                       [
-                               $mocksForCaching,
-                               false,
-                               $this->once(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-public/f/o/foobar.jpg',
-                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               'Public path translated correctly',
-                       ],
-                       [
-                               $mocksForCaching,
-                               false,
-                               $this->never(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-public/f/o/foobar.jpg',
-                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               'LRU cache leveraged',
-                       ],
-                       [
-                               $this->getMocks(),
-                               true,
-                               $this->once(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-public/f/o/foobar.jpg',
-                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               'Latest obtained',
-                       ],
-                       [
-                               $this->getMocks(),
-                               true,
-                               $this->never(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-deleted/f/o/foobar.jpg',
-                               $prefix . '-original/f/o/o/foobar',
-                               'Deleted path translated correctly',
-                       ],
-                       [
-                               $this->getMocks(),
-                               true,
-                               $this->once(),
-                               null,
-                               $prefix . '-public/b/a/baz.jpg',
-                               $prefix . '-public/b/a/baz.jpg',
-                               'Path left untouched if no sha1 can be found',
-                       ],
-               ];
-       }
-
-       /**
-        * @covers FileBackendDBRepoWrapper::getFileContentsMulti
-        */
-       public function testGetFileContentsMulti() {
-               list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
-
-               $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
-                       . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
-               $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
-                       . '-public/f/o/foobar.jpg';
-
-               $dbMock->expects( $this->once() )
-                       ->method( 'selectField' )
-                       ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
-
-               $backendMock->expects( $this->once() )
-                       ->method( 'getFileContentsMulti' )
-                       ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
-
-               $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
-
-               $this->assertEquals(
-                       [ $filenamePath => 'foo' ],
-                       $result,
-                       'File contents paths translated properly'
-               );
-       }
-
-       protected function getMocks() {
-               $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
-                       ->disableOriginalClone()
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $backendMock = $this->getMockBuilder( FSFileBackend::class )
-                       ->setConstructorArgs( [ [
-                                       'name' => $this->backendName,
-                                       'wikiId' => wfWikiID()
-                               ] ] )
-                       ->getMock();
-
-               $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
-                       ->setMethods( [ 'getDB' ] )
-                       ->setConstructorArgs( [ [
-                                       'backend' => $backendMock,
-                                       'repoName' => $this->repoName,
-                                       'dbHandleFactory' => null
-                               ] ] )
-                       ->getMock();
-
-               $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
-
-               return [ $dbMock, $backendMock, $wrapperMock ];
-       }
-}
diff --git a/tests/phpunit/includes/filerepo/file/ForeignDBFileTest.php b/tests/phpunit/includes/filerepo/file/ForeignDBFileTest.php
deleted file mode 100644 (file)
index 3c92ecb..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-/** @covers ForeignDBFile */
-class ForeignDBFileTest extends \PHPUnit\Framework\TestCase {
-
-       use PHPUnit4And6Compat;
-
-       public function testShouldConstructCorrectInstanceFromTitle() {
-               $title = Title::makeTitle( NS_FILE, 'Awesome_file' );
-               $repoMock = $this->createMock( LocalRepo::class );
-
-               $file = ForeignDBFile::newFromTitle( $title, $repoMock );
-
-               $this->assertInstanceOf( ForeignDBFile::class, $file );
-       }
-}
diff --git a/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php b/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php
deleted file mode 100644 (file)
index 05c567d..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-<?php
-
-/**
- * @covers HTMLCheckMatrix
- */
-class HTMLCheckMatrixTest extends MediaWikiTestCase {
-       private static $defaultOptions = [
-               'rows' => [ 'r1', 'r2' ],
-               'columns' => [ 'c1', 'c2' ],
-               'fieldname' => 'test',
-       ];
-
-       public function testPlainInstantiation() {
-               try {
-                       new HTMLCheckMatrix( [] );
-               } catch ( MWException $e ) {
-                       $this->assertInstanceOf( HTMLFormFieldRequiredOptionsException::class, $e );
-                       return;
-               }
-
-               $this->fail( 'Expected MWException indicating missing parameters but none was thrown.' );
-       }
-
-       public function testInstantiationWithMinimumRequiredParameters() {
-               new HTMLCheckMatrix( self::$defaultOptions );
-               $this->assertTrue( true ); // form instantiation must throw exception on failure
-       }
-
-       public function testValidateCallsUserDefinedValidationCallback() {
-               $called = false;
-               $field = new HTMLCheckMatrix( self::$defaultOptions + [
-                       'validation-callback' => function () use ( &$called ) {
-                               $called = true;
-
-                               return false;
-                       },
-               ] );
-               $this->assertEquals( false, $this->validate( $field, [] ) );
-               $this->assertTrue( $called );
-       }
-
-       public function testValidateRequiresArrayInput() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions );
-               $this->assertEquals( false, $this->validate( $field, null ) );
-               $this->assertEquals( false, $this->validate( $field, true ) );
-               $this->assertEquals( false, $this->validate( $field, 'abc' ) );
-               $this->assertEquals( false, $this->validate( $field, new stdClass ) );
-               $this->assertEquals( true, $this->validate( $field, [] ) );
-       }
-
-       public function testValidateAllowsOnlyKnownTags() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions );
-               $this->assertInstanceOf( Message::class, $this->validate( $field, [ 'foo' ] ) );
-       }
-
-       public function testValidateAcceptsPartialTagList() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions );
-               $this->assertTrue( $this->validate( $field, [] ) );
-               $this->assertTrue( $this->validate( $field, [ 'c1-r1' ] ) );
-               $this->assertTrue( $this->validate( $field, [ 'c1-r1', 'c1-r2', 'c2-r1', 'c2-r2' ] ) );
-       }
-
-       /**
-        * This form object actually has no visibility into what happens later on, but essentially
-        * if the data submitted by the user passes validate the following is run:
-        * foreach ( $field->filterDataForSubmit( $data ) as $k => $v ) {
-        *     $user->setOption( $k, $v );
-        * }
-        */
-       public function testValuesForcedOnRemainOn() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions + [
-                               'force-options-on' => [ 'c2-r1' ],
-                       ] );
-               $expected = [
-                       'c1-r1' => false,
-                       'c1-r2' => false,
-                       'c2-r1' => true,
-                       'c2-r2' => false,
-               ];
-               $this->assertEquals( $expected, $field->filterDataForSubmit( [] ) );
-       }
-
-       public function testValuesForcedOffRemainOff() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions + [
-                               'force-options-off' => [ 'c1-r2', 'c2-r2' ],
-                       ] );
-               $expected = [
-                       'c1-r1' => true,
-                       'c1-r2' => false,
-                       'c2-r1' => true,
-                       'c2-r2' => false,
-               ];
-               // array_keys on the result simulates submitting all fields checked
-               $this->assertEquals( $expected, $field->filterDataForSubmit( array_keys( $expected ) ) );
-       }
-
-       protected function validate( HTMLFormField $field, $submitted ) {
-               return $field->validate(
-                       $submitted,
-                       [ self::$defaultOptions['fieldname'] => $submitted ]
-               );
-       }
-
-}
index 09bcfc9..ef499a1 100644 (file)
@@ -7,20 +7,6 @@
  */
 class HttpTest extends MediaWikiTestCase {
 
-       /**
-        * Test Http::isValidURI()
-        * T29854 : Http::isValidURI is too lax
-        * @dataProvider provideURI
-        * @covers Http::isValidURI
-        */
-       public function testIsValidUri( $expect, $URI, $message = '' ) {
-               $this->assertEquals(
-                       $expect,
-                       (bool)Http::isValidURI( $URI ),
-                       $message
-               );
-       }
-
        /**
         * @covers Http::getProxy
         */
@@ -41,71 +27,4 @@ class HttpTest extends MediaWikiTestCase {
                );
        }
 
-       /**
-        * Feeds URI to test a long regular expression in Http::isValidURI
-        */
-       public static function provideURI() {
-               /** Format: 'boolean expectation', 'URI to test', 'Optional message' */
-               return [
-                       [ false, '¿non sens before!! http://a', 'Allow anything before URI' ],
-
-                       # (http|https) - only two schemes allowed
-                       [ true, 'http://www.example.org/' ],
-                       [ true, 'https://www.example.org/' ],
-                       [ true, 'http://www.example.org', 'URI without directory' ],
-                       [ true, 'http://a', 'Short name' ],
-                       [ true, 'http://étoile', 'Allow UTF-8 in hostname' ], # 'étoile' is french for 'star'
-                       [ false, '\\host\directory', 'CIFS share' ],
-                       [ false, 'gopher://host/dir', 'Reject gopher scheme' ],
-                       [ false, 'telnet://host', 'Reject telnet scheme' ],
-
-                       # :\/\/ - double slashes
-                       [ false, 'http//example.org', 'Reject missing colon in protocol' ],
-                       [ false, 'http:/example.org', 'Reject missing slash in protocol' ],
-                       [ false, 'http:example.org', 'Must have two slashes' ],
-                       # Following fail since hostname can be made of anything
-                       [ false, 'http:///example.org', 'Must have exactly two slashes, not three' ],
-
-                       # (\w+:{0,1}\w*@)? - optional user:pass
-                       [ true, 'http://user@host', 'Username provided' ],
-                       [ true, 'http://user:@host', 'Username provided, no password' ],
-                       [ true, 'http://user:pass@host', 'Username and password provided' ],
-
-                       # (\S+) - host part is made of anything not whitespaces
-                       // commented these out in order to remove @group Broken
-                       // @todo are these valid tests? if so, fix Http::isValidURI so it can handle them
-                       // [ false, 'http://!"èèè¿¿¿~~\'', 'hostname is made of any non whitespace' ],
-                       // [ false, 'http://exam:ple.org/', 'hostname can not use colons!' ],
-
-                       # (:[0-9]+)? - port number
-                       [ true, 'http://example.org:80/' ],
-                       [ true, 'https://example.org:80/' ],
-                       [ true, 'http://example.org:443/' ],
-                       [ true, 'https://example.org:443/' ],
-
-                       # Part after the hostname is / or / with something else
-                       [ true, 'http://example/#' ],
-                       [ true, 'http://example/!' ],
-                       [ true, 'http://example/:' ],
-                       [ true, 'http://example/.' ],
-                       [ true, 'http://example/?' ],
-                       [ true, 'http://example/+' ],
-                       [ true, 'http://example/=' ],
-                       [ true, 'http://example/&' ],
-                       [ true, 'http://example/%' ],
-                       [ true, 'http://example/@' ],
-                       [ true, 'http://example/-' ],
-                       [ true, 'http://example//' ],
-                       [ true, 'http://example/&' ],
-
-                       # Fragment
-                       [ true, 'http://exam#ple.org', ], # This one is valid, really!
-                       [ true, 'http://example.org:80#anchor' ],
-                       [ true, 'http://example.org/?id#anchor' ],
-                       [ true, 'http://example.org/?#anchor' ],
-
-                       [ false, 'http://a ¿non !!sens after', 'Allow anything after URI' ],
-               ];
-       }
-
 }
diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php
deleted file mode 100644 (file)
index 1a99775..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-/**
- * @covers FormatJson
- */
-class FormatJsonTest extends MediaWikiTestCase {
-
-       /**
-        * Test data for testParseTryFixing.
-        *
-        * Some PHP interpreters use json-c rather than the JSON.org canonical
-        * parser to avoid being encumbered by the "shall be used for Good, not
-        * Evil" clause of the JSON.org parser's license. By default, json-c
-        * parses in a non-strict mode which allows trailing commas for array and
-        * object delarations among other things, so our JSON_ERROR_SYNTAX rescue
-        * block is not always triggered. It however isn't lenient in exactly the
-        * same ways as our TRY_FIXING mode, so the assertions in this test are
-        * a bit more complicated than they ideally would be:
-        *
-        * Optional third argument: true if json-c parses the value without
-        * intervention, false otherwise. Defaults to true.
-        *
-        * Optional fourth argument: expected cannonical JSON serialization of
-        * json-c parsed result. Defaults to the second argument's value.
-        */
-       public static function provideParseTryFixing() {
-               return [
-                       [ "[,]", '[]', false ],
-                       [ "[ , ]", '[]', false ],
-                       [ "[ , }", false ],
-                       [ '[1],', false, true, '[1]' ],
-                       [ "[1,]", '[1]' ],
-                       [ "[1\n,]", '[1]' ],
-                       [ "[1,\n]", '[1]' ],
-                       [ "[1,]\n", '[1]' ],
-                       [ "[1\n,\n]\n", '[1]' ],
-                       [ '["a,",]', '["a,"]' ],
-                       [ "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ],
-                       // I wish we could parse this, but would need quote parsing
-                       [ '[[1,],[2,],[3,]]', false, true, '[[1],[2],[3]]' ],
-                       [ '[1,,]', false, false, '[1]' ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideParseTryFixing
-        * @param string $value
-        * @param string|bool $expected Expected result with strict parser
-        * @param bool $jsoncParses Will json-c parse this value without TRY_FIXING?
-        * @param string|bool $expectedJsonc Expected result with lenient parser
-        * if different from the strict expectation
-        */
-       public function testParseTryFixing(
-               $value, $expected,
-               $jsoncParses = true, $expectedJsonc = null
-       ) {
-               // PHP5 results are always expected to have isGood() === false
-               $expectedGoodStatus = false;
-
-               // Check to see if json parser allows trailing commas
-               if ( json_decode( '[1,]' ) !== null ) {
-                       // Use json-c specific expected result if provided
-                       $expected = ( $expectedJsonc === null ) ? $expected : $expectedJsonc;
-                       // If json-c parses the value natively, expect isGood() === true
-                       $expectedGoodStatus = $jsoncParses;
-               }
-
-               $st = FormatJson::parse( $value, FormatJson::TRY_FIXING );
-               $this->assertInstanceOf( Status::class, $st );
-               if ( $expected === false ) {
-                       $this->assertFalse( $st->isOK(), 'Expected isOK() == false' );
-               } else {
-                       $this->assertSame( $expectedGoodStatus, $st->isGood(),
-                               'Expected isGood() == ' . ( $expectedGoodStatus ? 'true' : 'false' )
-                       );
-                       $this->assertTrue( $st->isOK(), 'Expected isOK == true' );
-                       $val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK );
-                       $this->assertEquals( $expected, $val );
-               }
-       }
-
-}
diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
deleted file mode 100644 (file)
index c943cef..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php
-/**
- * @todo Could use a test of extended XMP segments. Hard to find programs that
- * create example files, and creating my own in vim propbably wouldn't
- * serve as a very good "test". (Adobe photoshop probably creates such files
- * but it costs money). The implementation of it currently in MediaWiki is based
- * solely on reading the standard, without any real world test files.
- *
- * @group Media
- * @covers JpegMetadataExtractor
- */
-class JpegMetadataExtractorTest extends MediaWikiTestCase {
-
-       protected $filePath;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->filePath = __DIR__ . '/../../data/media/';
-       }
-
-       /**
-        * We also use this test to test padding bytes don't
-        * screw stuff up
-        *
-        * @param string $file Filename
-        *
-        * @dataProvider provideUtf8Comment
-        */
-       public function testUtf8Comment( $file ) {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
-               $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
-       }
-
-       public static function provideUtf8Comment() {
-               return [
-                       [ 'jpeg-comment-utf.jpg' ],
-                       [ 'jpeg-padding-even.jpg' ],
-                       [ 'jpeg-padding-odd.jpg' ],
-               ];
-       }
-
-       /** The file is iso-8859-1, but it should get auto converted */
-       public function testIso88591Comment() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
-               $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
-       }
-
-       /** Comment values that are non-textual (random binary junk) should not be shown.
-        * The example test file has a comment with a 0x5 byte in it which is a control character
-        * and considered binary junk for our purposes.
-        */
-       public function testBinaryCommentStripped() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
-               $this->assertEmpty( $res['COM'] );
-       }
-
-       /* Very rarely a file can have multiple comments.
-        *   Order of comments is based on order inside the file.
-        */
-       public function testMultipleComment() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
-               $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
-       }
-
-       public function testXMPExtraction() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
-               $this->assertEquals( $expected, $res['XMP'] );
-       }
-
-       public function testPSIRExtraction() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-               $expected = '50686f746f73686f7020332e30003842494d04040000000'
-                       . '000181c02190004746573741c02190003666f6f1c020000020004';
-               $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
-       }
-
-       public function testXMPExtractionAltAppId() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
-               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
-               $this->assertEquals( $expected, $res['XMP'] );
-       }
-
-       public function testIPTCHashComparisionNoHash() {
-               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
-               $this->assertEquals( 'iptc-no-hash', $res );
-       }
-
-       public function testIPTCHashComparisionBadHash() {
-               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
-               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
-               $this->assertEquals( 'iptc-bad-hash', $res );
-       }
-
-       public function testIPTCHashComparisionGoodHash() {
-               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
-               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
-               $this->assertEquals( 'iptc-good-hash', $res );
-       }
-
-       public function testExifByteOrder() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
-               $expected = 'BE';
-               $this->assertEquals( $expected, $res['byteOrder'] );
-       }
-
-       public function testInfiniteRead() {
-               // test file truncated right after a segment, which previously
-               // caused an infinite loop looking for the next segment byte.
-               // Should get past infinite loop and throw in wfUnpack()
-               $this->setExpectedException( 'MWException' );
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
-       }
-
-       public function testInfiniteRead2() {
-               // test file truncated after a segment's marker and size, which
-               // would cause a seek past end of file. Seek past end of file
-               // doesn't actually fail, but prevents further reading and was
-               // devolving into the previous case (testInfiniteRead).
-               $this->setExpectedException( 'MWException' );
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
-       }
-}
diff --git a/tests/phpunit/includes/page/ArticleTest.php b/tests/phpunit/includes/page/ArticleTest.php
deleted file mode 100644 (file)
index df4a281..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-class ArticleTest extends MediaWikiTestCase {
-
-       /**
-        * @var Title
-        */
-       private $title;
-       /**
-        * @var Article
-        */
-       private $article;
-
-       /** creates a title object and its article object */
-       protected function setUp() {
-               parent::setUp();
-               $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
-               $this->article = new Article( $this->title );
-       }
-
-       /** cleanup title object and its article object */
-       protected function tearDown() {
-               parent::tearDown();
-               $this->title = null;
-               $this->article = null;
-       }
-
-       /**
-        * @covers Article::__get
-        */
-       public function testImplementsGetMagic() {
-               $this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
-       }
-
-       /**
-        * @depends testImplementsGetMagic
-        * @covers Article::__set
-        */
-       public function testImplementsSetMagic() {
-               $this->article->mLatest = 2;
-               $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
-       }
-
-       /**
-        * @covers Article::__get
-        * @covers Article::__set
-        */
-       public function testGetOrSetOnNewProperty() {
-               $this->article->ext_someNewProperty = 12;
-               $this->assertEquals( 12, $this->article->ext_someNewProperty,
-                       "Article get/set magic on new field" );
-
-               $this->article->ext_someNewProperty = -8;
-               $this->assertEquals( -8, $this->article->ext_someNewProperty,
-                       "Article get/set magic on update to new field" );
-       }
-}
index a74056d..0031cb3 100644 (file)
@@ -13,214 +13,6 @@ use Wikimedia\TestingAccessWrapper;
  */
 class SessionTest extends MediaWikiTestCase {
 
-       public function testConstructor() {
-               $backend = TestUtils::getDummySessionBackend();
-               TestingAccessWrapper::newFromObject( $backend )->requests = [ -1 => 'dummy' ];
-               TestingAccessWrapper::newFromObject( $backend )->id = new SessionId( 'abc' );
-
-               $session = new Session( $backend, 42, new \TestLogger );
-               $priv = TestingAccessWrapper::newFromObject( $session );
-               $this->assertSame( $backend, $priv->backend );
-               $this->assertSame( 42, $priv->index );
-
-               $request = new \FauxRequest();
-               $priv2 = TestingAccessWrapper::newFromObject( $session->sessionWithRequest( $request ) );
-               $this->assertSame( $backend, $priv2->backend );
-               $this->assertNotSame( $priv->index, $priv2->index );
-               $this->assertSame( $request, $priv2->getRequest() );
-       }
-
-       /**
-        * @dataProvider provideMethods
-        * @param string $m Method to test
-        * @param array $args Arguments to pass to the method
-        * @param bool $index Whether the backend method gets passed the index
-        * @param bool $ret Whether the method returns a value
-        */
-       public function testMethods( $m, $args, $index, $ret ) {
-               $mock = $this->getMockBuilder( DummySessionBackend::class )
-                       ->setMethods( [ $m, 'deregisterSession' ] )
-                       ->getMock();
-               $mock->expects( $this->once() )->method( 'deregisterSession' )
-                       ->with( $this->identicalTo( 42 ) );
-
-               $tmp = $mock->expects( $this->once() )->method( $m );
-               $expectArgs = [];
-               if ( $index ) {
-                       $expectArgs[] = $this->identicalTo( 42 );
-               }
-               foreach ( $args as $arg ) {
-                       $expectArgs[] = $this->identicalTo( $arg );
-               }
-               $tmp = call_user_func_array( [ $tmp, 'with' ], $expectArgs );
-
-               $retval = new \stdClass;
-               $tmp->will( $this->returnValue( $retval ) );
-
-               $session = TestUtils::getDummySession( $mock, 42 );
-
-               if ( $ret ) {
-                       $this->assertSame( $retval, call_user_func_array( [ $session, $m ], $args ) );
-               } else {
-                       $this->assertNull( call_user_func_array( [ $session, $m ], $args ) );
-               }
-
-               // Trigger Session destructor
-               $session = null;
-       }
-
-       public static function provideMethods() {
-               return [
-                       [ 'getId', [], false, true ],
-                       [ 'getSessionId', [], false, true ],
-                       [ 'resetId', [], false, true ],
-                       [ 'getProvider', [], false, true ],
-                       [ 'isPersistent', [], false, true ],
-                       [ 'persist', [], false, false ],
-                       [ 'unpersist', [], false, false ],
-                       [ 'shouldRememberUser', [], false, true ],
-                       [ 'setRememberUser', [ true ], false, false ],
-                       [ 'getRequest', [], true, true ],
-                       [ 'getUser', [], false, true ],
-                       [ 'getAllowedUserRights', [], false, true ],
-                       [ 'canSetUser', [], false, true ],
-                       [ 'setUser', [ new \stdClass ], false, false ],
-                       [ 'suggestLoginUsername', [], true, true ],
-                       [ 'shouldForceHTTPS', [], false, true ],
-                       [ 'setForceHTTPS', [ true ], false, false ],
-                       [ 'getLoggedOutTimestamp', [], false, true ],
-                       [ 'setLoggedOutTimestamp', [ 123 ], false, false ],
-                       [ 'getProviderMetadata', [], false, true ],
-                       [ 'save', [], false, false ],
-                       [ 'delaySave', [], false, true ],
-                       [ 'renew', [], false, false ],
-               ];
-       }
-
-       public function testDataAccess() {
-               $session = TestUtils::getDummySession();
-               $backend = TestingAccessWrapper::newFromObject( $session )->backend;
-
-               $this->assertEquals( 1, $session->get( 'foo' ) );
-               $this->assertEquals( 'zero', $session->get( 0 ) );
-               $this->assertFalse( $backend->dirty );
-
-               $this->assertEquals( null, $session->get( 'null' ) );
-               $this->assertEquals( 'default', $session->get( 'null', 'default' ) );
-               $this->assertFalse( $backend->dirty );
-
-               $session->set( 'foo', 55 );
-               $this->assertEquals( 55, $backend->data['foo'] );
-               $this->assertTrue( $backend->dirty );
-               $backend->dirty = false;
-
-               $session->set( 1, 'one' );
-               $this->assertEquals( 'one', $backend->data[1] );
-               $this->assertTrue( $backend->dirty );
-               $backend->dirty = false;
-
-               $session->set( 1, 'one' );
-               $this->assertFalse( $backend->dirty );
-
-               $this->assertTrue( $session->exists( 'foo' ) );
-               $this->assertTrue( $session->exists( 1 ) );
-               $this->assertFalse( $session->exists( 'null' ) );
-               $this->assertFalse( $session->exists( 100 ) );
-               $this->assertFalse( $backend->dirty );
-
-               $session->remove( 'foo' );
-               $this->assertArrayNotHasKey( 'foo', $backend->data );
-               $this->assertTrue( $backend->dirty );
-               $backend->dirty = false;
-               $session->remove( 1 );
-               $this->assertArrayNotHasKey( 1, $backend->data );
-               $this->assertTrue( $backend->dirty );
-               $backend->dirty = false;
-
-               $session->remove( 101 );
-               $this->assertFalse( $backend->dirty );
-
-               $backend->data = [ 'a', 'b', '?' => 'c' ];
-               $this->assertSame( 3, $session->count() );
-               $this->assertSame( 3, count( $session ) );
-               $this->assertFalse( $backend->dirty );
-
-               $data = [];
-               foreach ( $session as $key => $value ) {
-                       $data[$key] = $value;
-               }
-               $this->assertEquals( $backend->data, $data );
-               $this->assertFalse( $backend->dirty );
-
-               $this->assertEquals( $backend->data, iterator_to_array( $session ) );
-               $this->assertFalse( $backend->dirty );
-       }
-
-       public function testArrayAccess() {
-               $logger = new \TestLogger;
-               $session = TestUtils::getDummySession( null, -1, $logger );
-               $backend = TestingAccessWrapper::newFromObject( $session )->backend;
-
-               $this->assertEquals( 1, $session['foo'] );
-               $this->assertEquals( 'zero', $session[0] );
-               $this->assertFalse( $backend->dirty );
-
-               $logger->setCollect( true );
-               $this->assertEquals( null, $session['null'] );
-               $logger->setCollect( false );
-               $this->assertFalse( $backend->dirty );
-               $this->assertSame( [
-                       [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): null' ]
-               ], $logger->getBuffer() );
-               $logger->clearBuffer();
-
-               $session['foo'] = 55;
-               $this->assertEquals( 55, $backend->data['foo'] );
-               $this->assertTrue( $backend->dirty );
-               $backend->dirty = false;
-
-               $session[1] = 'one';
-               $this->assertEquals( 'one', $backend->data[1] );
-               $this->assertTrue( $backend->dirty );
-               $backend->dirty = false;
-
-               $session[1] = 'one';
-               $this->assertFalse( $backend->dirty );
-
-               $session['bar'] = [ 'baz' => [] ];
-               $session['bar']['baz']['quux'] = 2;
-               $this->assertEquals( [ 'baz' => [ 'quux' => 2 ] ], $backend->data['bar'] );
-
-               $logger->setCollect( true );
-               $session['bar2']['baz']['quux'] = 3;
-               $logger->setCollect( false );
-               $this->assertEquals( [ 'baz' => [ 'quux' => 3 ] ], $backend->data['bar2'] );
-               $this->assertSame( [
-                       [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): bar2' ]
-               ], $logger->getBuffer() );
-               $logger->clearBuffer();
-
-               $backend->dirty = false;
-               $this->assertTrue( isset( $session['foo'] ) );
-               $this->assertTrue( isset( $session[1] ) );
-               $this->assertFalse( isset( $session['null'] ) );
-               $this->assertFalse( isset( $session['missing'] ) );
-               $this->assertFalse( isset( $session[100] ) );
-               $this->assertFalse( $backend->dirty );
-
-               unset( $session['foo'] );
-               $this->assertArrayNotHasKey( 'foo', $backend->data );
-               $this->assertTrue( $backend->dirty );
-               $backend->dirty = false;
-               unset( $session[1] );
-               $this->assertArrayNotHasKey( 1, $backend->data );
-               $this->assertTrue( $backend->dirty );
-               $backend->dirty = false;
-
-               unset( $session[101] );
-               $this->assertFalse( $backend->dirty );
-       }
-
        public function testClear() {
                $session = TestUtils::getDummySession();
                $priv = TestingAccessWrapper::newFromObject( $session );
@@ -268,66 +60,6 @@ class SessionTest extends MediaWikiTestCase {
                $this->assertTrue( $backend->dirty );
        }
 
-       public function testTokens() {
-               $session = TestUtils::getDummySession();
-               $priv = TestingAccessWrapper::newFromObject( $session );
-               $backend = $priv->backend;
-
-               $token = TestingAccessWrapper::newFromObject( $session->getToken() );
-               $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
-               $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
-               $secret = $backend->data['wsTokenSecrets']['default'];
-               $this->assertSame( $secret, $token->secret );
-               $this->assertSame( '', $token->salt );
-               $this->assertTrue( $token->wasNew() );
-
-               $token = TestingAccessWrapper::newFromObject( $session->getToken( 'foo' ) );
-               $this->assertSame( $secret, $token->secret );
-               $this->assertSame( 'foo', $token->salt );
-               $this->assertFalse( $token->wasNew() );
-
-               $backend->data['wsTokenSecrets']['secret'] = 'sekret';
-               $token = TestingAccessWrapper::newFromObject(
-                       $session->getToken( [ 'bar', 'baz' ], 'secret' )
-               );
-               $this->assertSame( 'sekret', $token->secret );
-               $this->assertSame( 'bar|baz', $token->salt );
-               $this->assertFalse( $token->wasNew() );
-
-               $session->resetToken( 'secret' );
-               $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
-               $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
-               $this->assertArrayNotHasKey( 'secret', $backend->data['wsTokenSecrets'] );
-
-               $session->resetAllTokens();
-               $this->assertArrayNotHasKey( 'wsTokenSecrets', $backend->data );
-       }
-
-       /**
-        * @dataProvider provideSecretsRoundTripping
-        * @param mixed $data
-        */
-       public function testSecretsRoundTripping( $data ) {
-               $session = TestUtils::getDummySession();
-
-               // Simple round-trip
-               $session->setSecret( 'secret', $data );
-               $this->assertNotEquals( $data, $session->get( 'secret' ) );
-               $this->assertEquals( $data, $session->getSecret( 'secret', 'defaulted' ) );
-       }
-
-       public static function provideSecretsRoundTripping() {
-               return [
-                       [ 'Foobar' ],
-                       [ 42 ],
-                       [ [ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
-                       [ (object)[ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
-                       [ true ],
-                       [ false ],
-                       [ null ],
-               ];
-       }
-
        public function testSecrets() {
                $logger = new \TestLogger;
                $session = TestUtils::getDummySession( null, -1, $logger );
@@ -370,4 +102,29 @@ class SessionTest extends MediaWikiTestCase {
                \Wikimedia\restoreWarnings();
        }
 
+       /**
+        * @dataProvider provideSecretsRoundTripping
+        * @param mixed $data
+        */
+       public function testSecretsRoundTripping( $data ) {
+               $session = TestUtils::getDummySession();
+
+               // Simple round-trip
+               $session->setSecret( 'secret', $data );
+               $this->assertNotEquals( $data, $session->get( 'secret' ) );
+               $this->assertEquals( $data, $session->getSecret( 'secret', 'defaulted' ) );
+       }
+
+       public static function provideSecretsRoundTripping() {
+               return [
+                       [ 'Foobar' ],
+                       [ 42 ],
+                       [ [ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
+                       [ (object)[ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
+                       [ true ],
+                       [ false ],
+                       [ null ],
+               ];
+       }
+
 }
diff --git a/tests/phpunit/includes/session/TokenTest.php b/tests/phpunit/includes/session/TokenTest.php
deleted file mode 100644 (file)
index 4797652..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-namespace MediaWiki\Session;
-
-use MediaWikiTestCase;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @group Session
- * @covers MediaWiki\Session\Token
- */
-class TokenTest extends MediaWikiTestCase {
-
-       public function testBasics() {
-               $token = $this->getMockBuilder( Token::class )
-                       ->setMethods( [ 'toStringAtTimestamp' ] )
-                       ->setConstructorArgs( [ 'sekret', 'salty', true ] )
-                       ->getMock();
-               $token->expects( $this->any() )->method( 'toStringAtTimestamp' )
-                       ->will( $this->returnValue( 'faketoken+\\' ) );
-
-               $this->assertSame( 'faketoken+\\', $token->toString() );
-               $this->assertSame( 'faketoken+\\', (string)$token );
-               $this->assertTrue( $token->wasNew() );
-
-               $token = new Token( 'sekret', 'salty', false );
-               $this->assertFalse( $token->wasNew() );
-       }
-
-       public function testToStringAtTimestamp() {
-               $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
-
-               $this->assertSame(
-                       'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\',
-                       $token->toStringAtTimestamp( 1447362018 )
-               );
-               $this->assertSame(
-                       'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\',
-                       $token->toStringAtTimestamp( 1447362026 )
-               );
-       }
-
-       public function testGetTimestamp() {
-               $this->assertSame(
-                       1447362018, Token::getTimestamp( 'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\' )
-               );
-               $this->assertSame(
-                       1447362026, Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\' )
-               );
-               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
-               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be+\\' ) );
-
-               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9x76c224cfb400d43be5644fdea+\\' ) );
-       }
-
-       public function testMatch() {
-               $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
-
-               $test = $token->toStringAtTimestamp( time() - 10 );
-               $this->assertTrue( $token->match( $test ) );
-               $this->assertTrue( $token->match( $test, 12 ) );
-               $this->assertFalse( $token->match( $test, 8 ) );
-
-               $this->assertFalse( $token->match( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
-       }
-
-}
diff --git a/tests/phpunit/includes/shell/FirejailCommandTest.php b/tests/phpunit/includes/shell/FirejailCommandTest.php
deleted file mode 100644 (file)
index 681c3dc..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-/**
- * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- */
-
-use MediaWiki\Shell\FirejailCommand;
-use MediaWiki\Shell\Shell;
-use Wikimedia\TestingAccessWrapper;
-
-class FirejailCommandTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       public function provideBuildFinalCommand() {
-               global $IP;
-               // phpcs:ignore Generic.Files.LineLength
-               $env = "'MW_INCLUDE_STDERR=;MW_CPU_LIMIT=180; MW_CGROUP='\'''\''; MW_MEM_LIMIT=307200; MW_FILE_SIZE_LIMIT=102400; MW_WALL_CLOCK_LIMIT=180; MW_USE_LOG_PIPE=yes'";
-               $limit = "/bin/bash '$IP/includes/shell/limit.sh'";
-               $profile = "--profile=$IP/includes/shell/firejail.profile";
-               $blacklist = '--blacklist=' . realpath( MW_CONFIG_FILE );
-               $default = "$blacklist --noroot --seccomp --private-dev";
-               return [
-                       [
-                               'No restrictions',
-                               'ls', 0, "$limit ''\''ls'\''' $env"
-                       ],
-                       [
-                               'default restriction',
-                               'ls', Shell::RESTRICT_DEFAULT,
-                               "$limit 'firejail --quiet $profile $default -- '\''ls'\''' $env"
-                       ],
-                       [
-                               'no network',
-                               'ls', Shell::NO_NETWORK,
-                               "$limit 'firejail --quiet $profile --net=none -- '\''ls'\''' $env"
-                       ],
-                       [
-                               'default restriction & no network',
-                               'ls', Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK,
-                               "$limit 'firejail --quiet $profile $default --net=none -- '\''ls'\''' $env"
-                       ],
-                       [
-                               'seccomp',
-                               'ls', Shell::SECCOMP,
-                               "$limit 'firejail --quiet $profile --seccomp -- '\''ls'\''' $env"
-                       ],
-                       [
-                               'seccomp & no execve',
-                               'ls', Shell::SECCOMP | Shell::NO_EXECVE,
-                               "$limit 'firejail --quiet $profile --shell=none --seccomp=execve -- '\''ls'\''' $env"
-                       ],
-               ];
-       }
-
-       /**
-        * @covers \MediaWiki\Shell\FirejailCommand::buildFinalCommand()
-        * @dataProvider provideBuildFinalCommand
-        */
-       public function testBuildFinalCommand( $desc, $params, $flags, $expected ) {
-               $command = new FirejailCommand( 'firejail' );
-               $command
-                       ->params( $params )
-                       ->restrict( $flags );
-               $wrapper = TestingAccessWrapper::newFromObject( $command );
-               $output = $wrapper->buildFinalCommand( $wrapper->command );
-               $this->assertEquals( $expected, $output[0], $desc );
-       }
-
-}
diff --git a/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php b/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php
deleted file mode 100644 (file)
index 15894a3..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-
-use MediaWiki\Site\MediaWikiPageNameNormalizer;
-
-/**
- * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @since 1.27
- *
- * @group Site
- * @group medium
- *
- * @author Marius Hoch
- */
-class MediaWikiPageNameNormalizerTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       /**
-        * @dataProvider normalizePageTitleProvider
-        */
-       public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
-               MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
-
-               $normalizer = new MediaWikiPageNameNormalizer(
-                       new MediaWikiPageNameNormalizerTestMockHttp()
-               );
-
-               $this->assertSame(
-                       $expected,
-                       $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
-               );
-       }
-
-       public function normalizePageTitleProvider() {
-               // Response are taken from wikidata and kkwiki using the following API request
-               // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
-               return [
-                       'universe (Q1)' => [
-                               'Q1',
-                               'Q1',
-                               '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
-                               . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
-                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
-                               . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
-                       ],
-                       'Q404 redirects to Q395' => [
-                               'Q395',
-                               'Q404',
-                               '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
-                               . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
-                               . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
-                               . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
-                       ],
-                       'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
-                               'Д',
-                               'D',
-                               '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
-                               . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
-                               . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
-                               . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
-                               . '"lastrevid":2373618,"length":3501}}}}'
-                       ],
-                       'there is no Q0' => [
-                               false,
-                               'Q0',
-                               '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
-                               . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
-                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
-                       ],
-                       'invalid title' => [
-                               false,
-                               '{{',
-                               '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
-                               . '"invalidreason":"The requested page title contains invalid '
-                               . 'characters: \"{\".","invalid":""}}}}'
-                       ],
-                       'error on get' => [ false, 'ABC', false ]
-               ];
-       }
-
-}
-
-/**
- * @private
- * @see Http
- */
-class MediaWikiPageNameNormalizerTestMockHttp extends Http {
-
-       /**
-        * @var mixed
-        */
-       public static $response;
-
-       public static function get( $url, array $options = [], $caller = __METHOD__ ) {
-               PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
-               PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
-
-               return self::$response;
-       }
-}
diff --git a/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php b/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php
deleted file mode 100644 (file)
index a1a3fd7..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-
-/**
- * @covers ZipDirectoryReader
- * NOTE: this test is more like an integration test than a unit test
- */
-class ZipDirectoryReaderTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       protected $zipDir;
-       protected $entries;
-
-       protected function setUp() {
-               parent::setUp();
-               $this->zipDir = __DIR__ . '/../../data/zip';
-       }
-
-       function zipCallback( $entry ) {
-               $this->entries[] = $entry;
-       }
-
-       function readZipAssertError( $file, $error, $assertMessage ) {
-               $this->entries = [];
-               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
-               $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
-       }
-
-       function readZipAssertSuccess( $file, $assertMessage ) {
-               $this->entries = [];
-               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
-               $this->assertTrue( $status->isOK(), $assertMessage );
-       }
-
-       public function testEmpty() {
-               $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
-       }
-
-       public function testMultiDisk0() {
-               $this->readZipAssertError( 'split.zip', 'zip-unsupported',
-                       'Split zip error' );
-       }
-
-       public function testNoSignature() {
-               $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
-                       'No signature should give "wrong format" error' );
-       }
-
-       public function testSimple() {
-               $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
-               $this->assertEquals( $this->entries, [ [
-                       'name' => 'Class.class',
-                       'mtime' => '20010115000000',
-                       'size' => 1,
-               ] ] );
-       }
-
-       public function testBadCentralEntrySignature() {
-               $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
-                       'Bad central entry error' );
-       }
-
-       public function testTrailingBytes() {
-               // Due to T40432 this is now zip-wrong-format instead of zip-bad
-               $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
-                       'Trailing bytes error' );
-       }
-
-       public function testWrongCDStart() {
-               $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
-                       'Wrong CD start disk error' );
-       }
-
-       public function testCentralDirectoryGap() {
-               $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
-                       'CD gap error' );
-       }
-
-       public function testCentralDirectoryTruncated() {
-               $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
-                       'CD truncated error (should hit unpack() overrun)' );
-       }
-
-       public function testLooksLikeZip64() {
-               $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
-                       'A file which looks like ZIP64 but isn\'t, should give error' );
-       }
-}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfAppendQueryTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfAppendQueryTest.php
new file mode 100644 (file)
index 0000000..11d4475
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfAppendQuery
+ */
+class WfAppendQueryTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideAppendQuery
+        */
+       public function testAppendQuery( $url, $query, $expected, $message = null ) {
+               $this->assertEquals( $expected, wfAppendQuery( $url, $query ), $message );
+       }
+
+       public static function provideAppendQuery() {
+               return [
+                       [
+                               'http://www.example.org/index.php',
+                               '',
+                               'http://www.example.org/index.php',
+                               'No query'
+                       ],
+                       [
+                               'http://www.example.org/index.php',
+                               [ 'foo' => 'bar' ],
+                               'http://www.example.org/index.php?foo=bar',
+                               'Set query array'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foz=baz',
+                               'foo=bar',
+                               'http://www.example.org/index.php?foz=baz&foo=bar',
+                               'Set query string'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               '',
+                               'http://www.example.org/index.php?foo=bar',
+                               'Empty string with query'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               [ 'baz' => 'quux' ],
+                               'http://www.example.org/index.php?foo=bar&baz=quux',
+                               'Add query array'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               'baz=quux',
+                               'http://www.example.org/index.php?foo=bar&baz=quux',
+                               'Add query string'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               [ 'baz' => 'quux', 'foo' => 'baz' ],
+                               'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
+                               'Modify query array'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               'baz=quux&foo=baz',
+                               'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
+                               'Modify query string'
+                       ],
+                       [
+                               'http://www.example.org/index.php#baz',
+                               'foo=bar',
+                               'http://www.example.org/index.php?foo=bar#baz',
+                               'URL with fragment'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar#baz',
+                               'quux=blah',
+                               'http://www.example.org/index.php?foo=bar&quux=blah#baz',
+                               'URL with query string and fragment'
+                       ]
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfArrayPlus2dTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfArrayPlus2dTest.php
new file mode 100644 (file)
index 0000000..9c3e56c
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+/**
+ * @group GlobalFunctions
+ * @covers ::wfArrayPlus2d
+ */
+class WfArrayPlus2dTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideArrays
+        */
+       public function testWfArrayPlus2d( $baseArray, $newValues, $expected, $testName ) {
+               $this->assertEquals(
+                       $expected,
+                       wfArrayPlus2d( $baseArray, $newValues ),
+                       $testName
+               );
+       }
+
+       /**
+        * Provider for testing wfArrayPlus2d
+        *
+        * @return array
+        */
+       public static function provideArrays() {
+               return [
+                       // target array, new values array, expected result
+                       [
+                               [ 0 => '1dArray' ],
+                               [ 1 => '1dArray' ],
+                               [ 0 => '1dArray', 1 => '1dArray' ],
+                               "Test simple union of two arrays with different keys",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => '2dArray' ],
+                               ],
+                               [
+                                       0 => [ 1 => '2dArray' ],
+                               ],
+                               [
+                                       0 => [ 0 => '2dArray', 1 => '2dArray' ],
+                               ],
+                               "Test union of 2d arrays with different keys in the value array",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => '2dArray' ],
+                               ],
+                               [
+                                       0 => [ 0 => '1dArray' ],
+                               ],
+                               [
+                                       0 => [ 0 => '2dArray' ],
+                               ],
+                               "Test union of 2d arrays with same keys in the value array",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 1 => '2dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               "Test union of 3d array with different keys",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 1 => [ 0 => '2dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ], 1 => [ 0 => '2dArray' ] ],
+                               ],
+                               "Test union of 3d array with different keys in the value array",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 0 => '2dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               "Test union of 3d array with same keys in the value array",
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfAssembleUrlTest.php
new file mode 100644 (file)
index 0000000..f1f1986
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+/**
+ * @group GlobalFunctions
+ * @covers ::wfAssembleUrl
+ */
+class WfAssembleUrlTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideURLParts
+        */
+       public function testWfAssembleUrl( $parts, $output ) {
+               $partsDump = print_r( $parts, true );
+               $this->assertEquals(
+                       $output,
+                       wfAssembleUrl( $parts ),
+                       "Testing $partsDump assembles to $output"
+               );
+       }
+
+       /**
+        * Provider of URL parts for testing wfAssembleUrl()
+        *
+        * @return array
+        */
+       public static function provideURLParts() {
+               $schemes = [
+                       '' => [],
+                       '//' => [
+                               'delimiter' => '//',
+                       ],
+                       'http://' => [
+                               'scheme' => 'http',
+                               'delimiter' => '://',
+                       ],
+               ];
+
+               $hosts = [
+                       '' => [],
+                       'example.com' => [
+                               'host' => 'example.com',
+                       ],
+                       'example.com:123' => [
+                               'host' => 'example.com',
+                               'port' => 123,
+                       ],
+                       'id@example.com' => [
+                               'user' => 'id',
+                               'host' => 'example.com',
+                       ],
+                       'id@example.com:123' => [
+                               'user' => 'id',
+                               'host' => 'example.com',
+                               'port' => 123,
+                       ],
+                       'id:key@example.com' => [
+                               'user' => 'id',
+                               'pass' => 'key',
+                               'host' => 'example.com',
+                       ],
+                       'id:key@example.com:123' => [
+                               'user' => 'id',
+                               'pass' => 'key',
+                               'host' => 'example.com',
+                               'port' => 123,
+                       ],
+               ];
+
+               $cases = [];
+               foreach ( $schemes as $scheme => $schemeParts ) {
+                       foreach ( $hosts as $host => $hostParts ) {
+                               foreach ( [ '', '/path' ] as $path ) {
+                                       foreach ( [ '', 'query' ] as $query ) {
+                                               foreach ( [ '', 'fragment' ] as $fragment ) {
+                                                       $parts = array_merge(
+                                                               $schemeParts,
+                                                               $hostParts
+                                                       );
+                                                       $url = $scheme .
+                                                               $host .
+                                                               $path;
+
+                                                       if ( $path ) {
+                                                               $parts['path'] = $path;
+                                                       }
+                                                       if ( $query ) {
+                                                               $parts['query'] = $query;
+                                                               $url .= '?' . $query;
+                                                       }
+                                                       if ( $fragment ) {
+                                                               $parts['fragment'] = $fragment;
+                                                               $url .= '#' . $fragment;
+                                                       }
+
+                                                       $cases[] = [
+                                                               $parts,
+                                                               $url,
+                                                       ];
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               $complexURL = 'http://id:key@example.org:321' .
+                       '/over/there?name=ferret&foo=bar#nose';
+               $cases[] = [
+                       wfParseUrl( $complexURL ),
+                       $complexURL,
+               ];
+
+               return $cases;
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfBaseNameTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfBaseNameTest.php
new file mode 100644 (file)
index 0000000..4d55fb7
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @group GlobalFunctions
+ * @covers ::wfBaseName
+ */
+class WfBaseNameTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider providePaths
+        */
+       public function testBaseName( $fullpath, $basename ) {
+               $this->assertEquals( $basename, wfBaseName( $fullpath ),
+                       "wfBaseName('$fullpath') => '$basename'" );
+       }
+
+       public static function providePaths() {
+               return [
+                       [ '', '' ],
+                       [ '/', '' ],
+                       [ '\\', '' ],
+                       [ '//', '' ],
+                       [ '\\\\', '' ],
+                       [ 'a', 'a' ],
+                       [ 'aaaa', 'aaaa' ],
+                       [ '/a', 'a' ],
+                       [ '\\a', 'a' ],
+                       [ '/aaaa', 'aaaa' ],
+                       [ '\\aaaa', 'aaaa' ],
+                       [ '/aaaa/', 'aaaa' ],
+                       [ '\\aaaa\\', 'aaaa' ],
+                       [ '\\aaaa\\', 'aaaa' ],
+                       [
+                               '/mnt/upload3/wikipedia/en/thumb/8/8b/'
+                                       . 'Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg',
+                               '93px-Zork_Grand_Inquisitor_box_cover.jpg'
+                       ],
+                       [ 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ],
+                       [ 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfEscapeShellArgTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfEscapeShellArgTest.php
new file mode 100644 (file)
index 0000000..d86b397
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfEscapeShellArg
+ */
+class WfEscapeShellArgTest extends MediaWikiUnitTestCase {
+       public function testSingleInput() {
+               if ( wfIsWindows() ) {
+                       $expected = '"blah"';
+               } else {
+                       $expected = "'blah'";
+               }
+
+               $actual = wfEscapeShellArg( 'blah' );
+
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function testMultipleArgs() {
+               if ( wfIsWindows() ) {
+                       $expected = '"foo" "bar" "baz"';
+               } else {
+                       $expected = "'foo' 'bar' 'baz'";
+               }
+
+               $actual = wfEscapeShellArg( 'foo', 'bar', 'baz' );
+
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function testMultipleArgsAsArray() {
+               if ( wfIsWindows() ) {
+                       $expected = '"foo" "bar" "baz"';
+               } else {
+                       $expected = "'foo' 'bar' 'baz'";
+               }
+
+               $actual = wfEscapeShellArg( [ 'foo', 'bar', 'baz' ] );
+
+               $this->assertEquals( $expected, $actual );
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfGetCallerTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfGetCallerTest.php
new file mode 100644 (file)
index 0000000..ae397d5
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfGetCaller
+ */
+class WfGetCallerTest extends MediaWikiUnitTestCase {
+       public function testZero() {
+               $this->assertEquals( 'WfGetCallerTest->testZero', wfGetCaller( 1 ) );
+       }
+
+       function callerOne() {
+               return wfGetCaller();
+       }
+
+       public function testOne() {
+               $this->assertEquals( 'WfGetCallerTest->testOne', self::callerOne() );
+       }
+
+       static function intermediateFunction( $level = 2, $n = 0 ) {
+               if ( $n > 0 ) {
+                       return self::intermediateFunction( $level, $n - 1 );
+               }
+
+               return wfGetCaller( $level );
+       }
+
+       public function testTwo() {
+               $this->assertEquals( 'WfGetCallerTest->testTwo', self::intermediateFunction() );
+       }
+
+       public function testN() {
+               $this->assertEquals( 'WfGetCallerTest->testN', self::intermediateFunction( 2, 0 ) );
+               $this->assertEquals(
+                       'WfGetCallerTest::intermediateFunction',
+                       self::intermediateFunction( 1, 0 )
+               );
+
+               for ( $i = 0; $i < 10; $i++ ) {
+                       $this->assertEquals(
+                               'WfGetCallerTest::intermediateFunction',
+                               self::intermediateFunction( $i + 1, $i )
+                       );
+               }
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
new file mode 100644 (file)
index 0000000..199fa21
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfRemoveDotSegments
+ */
+class WfRemoveDotSegmentsTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider providePaths
+        */
+       public function testWfRemoveDotSegments( $inputPath, $outputPath ) {
+               $this->assertEquals(
+                       $outputPath,
+                       wfRemoveDotSegments( $inputPath ),
+                       "Testing $inputPath expands to $outputPath"
+               );
+       }
+
+       /**
+        * Provider of URL paths for testing wfRemoveDotSegments()
+        *
+        * @return array
+        */
+       public static function providePaths() {
+               return [
+                       [ '/a/b/c/./../../g', '/a/g' ],
+                       [ 'mid/content=5/../6', 'mid/6' ],
+                       [ '/a//../b', '/a/b' ],
+                       [ '/.../a', '/.../a' ],
+                       [ '.../a', '.../a' ],
+                       [ '', '' ],
+                       [ '/', '/' ],
+                       [ '//', '//' ],
+                       [ '.', '' ],
+                       [ '..', '' ],
+                       [ '...', '...' ],
+                       [ '/.', '/' ],
+                       [ '/..', '/' ],
+                       [ './', '' ],
+                       [ '../', '' ],
+                       [ './a', 'a' ],
+                       [ '../a', 'a' ],
+                       [ '../../a', 'a' ],
+                       [ '.././a', 'a' ],
+                       [ './../a', 'a' ],
+                       [ '././a', 'a' ],
+                       [ '../../', '' ],
+                       [ '.././', '' ],
+                       [ './../', '' ],
+                       [ '././', '' ],
+                       [ '../..', '' ],
+                       [ '../.', '' ],
+                       [ './..', '' ],
+                       [ './.', '' ],
+                       [ '/../../a', '/a' ],
+                       [ '/.././a', '/a' ],
+                       [ '/./../a', '/a' ],
+                       [ '/././a', '/a' ],
+                       [ '/../../', '/' ],
+                       [ '/.././', '/' ],
+                       [ '/./../', '/' ],
+                       [ '/././', '/' ],
+                       [ '/../..', '/' ],
+                       [ '/../.', '/' ],
+                       [ '/./..', '/' ],
+                       [ '/./.', '/' ],
+                       [ 'b/../../a', '/a' ],
+                       [ 'b/.././a', '/a' ],
+                       [ 'b/./../a', '/a' ],
+                       [ 'b/././a', 'b/a' ],
+                       [ 'b/../../', '/' ],
+                       [ 'b/.././', '/' ],
+                       [ 'b/./../', '/' ],
+                       [ 'b/././', 'b/' ],
+                       [ 'b/../..', '/' ],
+                       [ 'b/../.', '/' ],
+                       [ 'b/./..', '/' ],
+                       [ 'b/./.', 'b/' ],
+                       [ '/b/../../a', '/a' ],
+                       [ '/b/.././a', '/a' ],
+                       [ '/b/./../a', '/a' ],
+                       [ '/b/././a', '/b/a' ],
+                       [ '/b/../../', '/' ],
+                       [ '/b/.././', '/' ],
+                       [ '/b/./../', '/' ],
+                       [ '/b/././', '/b/' ],
+                       [ '/b/../..', '/' ],
+                       [ '/b/../.', '/' ],
+                       [ '/b/./..', '/' ],
+                       [ '/b/./.', '/b/' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
new file mode 100644 (file)
index 0000000..c2d1f4a
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfShorthandToInteger
+ */
+class WfShorthandToIntegerTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideABunchOfShorthands
+        */
+       public function testWfShorthandToInteger( $input, $output, $description ) {
+               $this->assertEquals(
+                       wfShorthandToInteger( $input ),
+                       $output,
+                       $description
+               );
+       }
+
+       public static function provideABunchOfShorthands() {
+               return [
+                       [ '', -1, 'Empty string' ],
+                       [ '     ', -1, 'String of spaces' ],
+                       [ '1G', 1024 * 1024 * 1024, 'One gig uppercased' ],
+                       [ '1g', 1024 * 1024 * 1024, 'One gig lowercased' ],
+                       [ '1M', 1024 * 1024, 'One meg uppercased' ],
+                       [ '1m', 1024 * 1024, 'One meg lowercased' ],
+                       [ '1K', 1024, 'One kb uppercased' ],
+                       [ '1k', 1024, 'One kb lowercased' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfStringToBoolTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfStringToBoolTest.php
new file mode 100644 (file)
index 0000000..b99d695
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfStringToBool
+ */
+class WfStringToBoolTest extends MediaWikiUnitTestCase {
+
+       public function getTestCases() {
+               return [
+                       [ 'true', true ],
+                       [ 'on', true ],
+                       [ 'yes', true ],
+                       [ 'TRUE', true ],
+                       [ 'YeS', true ],
+                       [ 'On', true ],
+                       [ '1', true ],
+                       [ '+1', true ],
+                       [ '01', true ],
+                       [ '-001', true ],
+                       [ '  1', true ],
+                       [ '-1  ', true ],
+                       [ '', false ],
+                       [ '0', false ],
+                       [ 'false', false ],
+                       [ 'NO', false ],
+                       [ 'NOT', false ],
+                       [ 'never', false ],
+                       [ '!&', false ],
+                       [ '-0', false ],
+                       [ '+0', false ],
+                       [ 'forget about it', false ],
+                       [ ' on', false ],
+                       [ 'true ', false ],
+               ];
+       }
+
+       /**
+        * @dataProvider getTestCases
+        * @param string $str
+        * @param bool $bool
+        */
+       public function testStr2Bool( $str, $bool ) {
+               if ( $bool ) {
+                       $this->assertTrue( wfStringToBool( $str ) );
+               } else {
+                       $this->assertFalse( wfStringToBool( $str ) );
+               }
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfTimestampTest.php
new file mode 100644 (file)
index 0000000..94347bd
--- /dev/null
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfTimestamp
+ */
+class WfTimestampTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideNormalTimestamps
+        */
+       public function testNormalTimestamps( $input, $format, $output, $desc ) {
+               $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+       }
+
+       public static function provideNormalTimestamps() {
+               $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
+
+               return [
+                       // TS_UNIX
+                       [ $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ],
+                       [ -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ],
+                       [ $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ],
+                       [ $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ],
+                       [ $t + 0.01, TS_MW, '20010115123456', 'TS_UNIX float to TS_MW' ],
+
+                       [ $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ],
+
+                       // TS_MW
+                       [ '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ],
+                       [ '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ],
+                       [ '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ],
+                       [ '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ],
+
+                       // TS_DB
+                       [ '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ],
+                       [ '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ],
+                       [ '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ],
+                       [
+                               '2001-01-15 12:34:56',
+                               TS_ISO_8601_BASIC,
+                               '20010115T123456Z',
+                               'TS_DB to TS_ISO_8601_BASIC'
+                       ],
+
+                       # rfc2822 section 3.3
+                       [ '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ],
+                       [ 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
+                       [
+                               ' Mon, 15 Jan 2001 12:34:56 GMT',
+                               TS_MW,
+                               '20010115123456',
+                               'TS_RFC2822 with leading space to TS_MW'
+                       ],
+                       [
+                               '15 Jan 2001 12:34:56 GMT',
+                               TS_MW,
+                               '20010115123456',
+                               'TS_RFC2822 without optional day-of-week to TS_MW'
+                       ],
+
+                       # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
+                       # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
+                       [ 'Mon, 15         Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
+
+                       # WSP = SP / HTAB ; rfc2234
+                       [
+                               "Mon, 15 Jan\x092001 12:34:56 GMT",
+                               TS_MW,
+                               '20010115123456',
+                               'TS_RFC2822 with HTAB to TS_MW'
+                       ],
+                       [
+                               "Mon, 15 Jan\x09 \x09  2001 12:34:56 GMT",
+                               TS_MW,
+                               '20010115123456',
+                               'TS_RFC2822 with HTAB and SP to TS_MW'
+                       ],
+                       [
+                               'Sun, 6 Nov 94 08:49:37 GMT',
+                               TS_MW,
+                               '19941106084937',
+                               'TS_RFC2822 with obsolete year to TS_MW'
+                       ],
+               ];
+       }
+
+       /**
+        * This test checks wfTimestamp() with values outside.
+        * It needs PHP 64 bits or PHP > 5.1.
+        * See r74778 and T27451
+        * @dataProvider provideOldTimestamps
+        */
+       public function testOldTimestamps( $input, $outputType, $output, $message ) {
+               $timestamp = wfTimestamp( $outputType, $input );
+               if ( substr( $output, 0, 1 ) === '/' ) {
+                       // T66946: Day of the week calculations for very old
+                       // timestamps varies from system to system.
+                       $this->assertRegExp( $output, $timestamp, $message );
+               } else {
+                       $this->assertEquals( $output, $timestamp, $message );
+               }
+       }
+
+       public static function provideOldTimestamps() {
+               return [
+                       [
+                               '19011213204554',
+                               TS_RFC2822,
+                               'Fri, 13 Dec 1901 20:45:54 GMT',
+                               'Earliest time according to PHP documentation'
+                       ],
+                       [ '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ],
+                       [ '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ],
+                       [ '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ],
+                       [ '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ],
+                       [
+                               '19011213204551',
+                               TS_RFC2822,
+                               'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1'
+                       ],
+                       [ '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ],
+                       [ '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ],
+                       [ '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ],
+                       [ '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ],
+                       [ '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ],
+                       [ '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ],
+                       [
+                               '0117-08-09 12:34:56',
+                               TS_RFC2822,
+                               '/, 09 Aug 0117 12:34:56 GMT$/',
+                               'Death of Roman Emperor [[Trajan]]'
+                       ],
+
+                       /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
+                       [ '-58979923200', TS_RFC2822, '/, 01 Jan 0101 00:00:00 GMT$/', '1/1/101' ],
+                       [ '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ],
+
+                       /* It is not clear if we should generate a year 0 or not
+                        * We are completely off RFC2822 requirement of year being
+                        * 1900 or later.
+                        */
+                       [
+                               '-62142076800',
+                               TS_RFC2822,
+                               'Wed, 18 Oct 0000 00:00:00 GMT',
+                               'ISO 8601:2004 [[year 0]], also called [[1 BC]]'
+                       ],
+               ];
+       }
+
+       /**
+        * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
+        * @dataProvider provideHttpDates
+        */
+       public function testHttpDate( $input, $output, $desc ) {
+               $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
+       }
+
+       public static function provideHttpDates() {
+               return [
+                       [ 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ],
+                       [ 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ],
+                       [ 'Sun Nov  6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ],
+                       // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
+                       [
+                               'Mon, 22 Nov 2010 14:12:42 GMT; length=52626',
+                               '20101122141242',
+                               'Netscape extension to HTTP/1.0'
+                       ],
+               ];
+       }
+
+       /**
+        * There are a number of assumptions in our codebase where wfTimestamp()
+        * should give the current date but it is not given a 0 there. See r71751 CR
+        */
+       public function testTimestampParameter() {
+               $now = wfTimestamp( TS_UNIX );
+               // We check that wfTimestamp doesn't return false (error) and use a LessThan assert
+               // for the cases where the test is run in a second boundary.
+
+               $zero = wfTimestamp( TS_UNIX, 0 );
+               $this->assertNotEquals( false, $zero );
+               $this->assertLessThan( 5, $zero - $now );
+
+               $empty = wfTimestamp( TS_UNIX, '' );
+               $this->assertNotEquals( false, $empty );
+               $this->assertLessThan( 5, $empty - $now );
+
+               $null = wfTimestamp( TS_UNIX, null );
+               $this->assertNotEquals( false, $null );
+               $this->assertLessThan( 5, $null - $now );
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfUrlencodeTest.php
new file mode 100644 (file)
index 0000000..2fc038c
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * The function only need a string parameter and might react to IIS7.0
+ *
+ * @group GlobalFunctions
+ * @covers ::wfUrlencode
+ */
+class WfUrlencodeTest extends MediaWikiUnitTestCase {
+       # ### TESTS ##############################################################
+
+       /**
+        * @dataProvider provideURLS
+        */
+       public function testEncodingUrlWith( $input, $expected ) {
+               $this->verifyEncodingFor( 'Apache', $input, $expected );
+       }
+
+       /**
+        * @dataProvider provideURLS
+        */
+       public function testEncodingUrlWithMicrosoftIis7( $input, $expected ) {
+               $this->verifyEncodingFor( 'Microsoft-IIS/7', $input, $expected );
+       }
+
+       # ### HELPERS #############################################################
+
+       /**
+        * Internal helper that actually run the test.
+        * Called by the public methods testEncodingUrlWith...()
+        */
+       private function verifyEncodingFor( $server, $input, $expectations ) {
+               $expected = $this->extractExpect( $server, $expectations );
+
+               // save up global
+               $old = $_SERVER['SERVER_SOFTWARE'] ?? null;
+               $_SERVER['SERVER_SOFTWARE'] = $server;
+               wfUrlencode( null );
+
+               // do the requested test
+               $this->assertEquals(
+                       $expected,
+                       wfUrlencode( $input ),
+                       "Encoding '$input' for server '$server' should be '$expected'"
+               );
+
+               // restore global
+               if ( $old === null ) {
+                       unset( $_SERVER['SERVER_SOFTWARE'] );
+               } else {
+                       $_SERVER['SERVER_SOFTWARE'] = $old;
+               }
+               wfUrlencode( null );
+       }
+
+       /**
+        * Interprets the provider array. Return expected value depending
+        * the HTTP server name.
+        */
+       private function extractExpect( $server, $expectations ) {
+               if ( is_string( $expectations ) ) {
+                       return $expectations;
+               } elseif ( is_array( $expectations ) ) {
+                       if ( !array_key_exists( $server, $expectations ) ) {
+                               throw new MWException( __METHOD__ . " expectation does not have any "
+                                       . "value for server name $server. Check the provider array.\n" );
+                       } else {
+                               return $expectations[$server];
+                       }
+               } else {
+                       throw new MWException( __METHOD__ . " given invalid expectation for "
+                               . "'$server'. Should be a string or an array [ <http server name> => <string> ].\n" );
+               }
+       }
+
+       # ### PROVIDERS ###########################################################
+
+       /**
+        * Format is either:
+        *   [ 'input', 'expected' ];
+        * Or:
+        *   [ 'input',
+        *       [ 'Apache', 'expected' ],
+        *       [ 'Microsoft-IIS/7', 'expected' ],
+        *   ],
+        * If you want to add other HTTP server name, you will have to add a new
+        * testing method much like the testEncodingUrlWith() method above.
+        */
+       public static function provideURLS() {
+               return [
+                       # ## RFC 1738 chars
+                       // + is not safe
+                       [ '+', '%2B' ],
+                       // & and = not safe in queries
+                       [ '&', '%26' ],
+                       [ '=', '%3D' ],
+
+                       [ ':', [
+                               'Apache' => ':',
+                               'Microsoft-IIS/7' => '%3A',
+                       ] ],
+
+                       // remaining chars do not need encoding
+                       [
+                               ';@$-_.!*',
+                               ';@$-_.!*',
+                       ],
+
+                       # ## Other tests
+                       // slash remain unchanged. %2F seems to break things
+                       [ '/', '/' ],
+                       // T105265
+                       [ '~', '~' ],
+
+                       // Other 'funnies' chars
+                       [ '[]', '%5B%5D' ],
+                       [ '<>', '%3C%3E' ],
+
+                       // Apostrophe is encoded
+                       [ '\'', '%27' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/PathRouterTest.php b/tests/phpunit/unit/includes/PathRouterTest.php
new file mode 100644 (file)
index 0000000..fb23706
--- /dev/null
@@ -0,0 +1,325 @@
+<?php
+
+/**
+ * Tests for the PathRouter parsing.
+ *
+ * @covers PathRouter
+ */
+class PathRouterTest extends MediaWikiUnitTestCase {
+
+       /**
+        * @var PathRouter
+        */
+       protected $basicRouter;
+
+       protected function setUp() {
+               parent::setUp();
+               $router = new PathRouter;
+               $router->add( "/wiki/$1" );
+               $this->basicRouter = $router;
+       }
+
+       public static function provideParse() {
+               $tests = [
+                       // Basic path parsing
+                       'Basic path parsing' => [
+                               "/wiki/$1",
+                               "/wiki/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       //
+                       'Loose path auto-$1: /$1' => [
+                               "/",
+                               "/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       'Loose path auto-$1: /wiki' => [
+                               "/wiki",
+                               "/wiki/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       'Loose path auto-$1: /wiki/' => [
+                               "/wiki/",
+                               "/wiki/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       // Ensure that path is based on specificity, not order
+                       'Order, /$1 added first' => [
+                               [ "/$1", "/a/$1", "/b/$1" ],
+                               "/a/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       'Order, /$1 added last' => [
+                               [ "/b/$1", "/a/$1", "/$1" ],
+                               "/a/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       // Handling of key based arrays with a url parameter
+                       'Key based array' => [
+                               [ [
+                                       'path' => [ 'edit' => "/edit/$1" ],
+                                       'params' => [ 'action' => '$key' ],
+                               ] ],
+                               "/edit/Foo",
+                               [ 'title' => "Foo", 'action' => 'edit' ]
+                       ],
+                       // Additional parameter
+                       'Basic $2' => [
+                               [ [
+                                       'path' => '/$2/$1',
+                                       'params' => [ 'test' => '$2' ]
+                               ] ],
+                               "/asdf/Foo",
+                               [ 'title' => "Foo", 'test' => 'asdf' ]
+                       ],
+               ];
+               // Shared patterns for restricted value parameter tests
+               $restrictedPatterns = [
+                       [
+                               'path' => '/$2/$1',
+                               'params' => [ 'test' => '$2' ],
+                               'options' => [ '$2' => [ 'a', 'b' ] ]
+                       ],
+                       [
+                               'path' => '/$2/$1',
+                               'params' => [ 'test2' => '$2' ],
+                               'options' => [ '$2' => 'c' ]
+                       ],
+                       '/$1'
+               ];
+               $tests += [
+                       // Restricted value parameter tests
+                       'Restricted 1' => [
+                               $restrictedPatterns,
+                               "/asdf/Foo",
+                               [ 'title' => "asdf/Foo" ]
+                       ],
+                       'Restricted 2' => [
+                               $restrictedPatterns,
+                               "/a/Foo",
+                               [ 'title' => "Foo", 'test' => 'a' ]
+                       ],
+                       'Restricted 3' => [
+                               $restrictedPatterns,
+                               "/c/Foo",
+                               [ 'title' => "Foo", 'test2' => 'c' ]
+                       ],
+
+                       // Callback test
+                       'Callback' => [
+                               [ [
+                                       'path' => "/$1",
+                                       'params' => [ 'a' => 'b', 'data:foo' => 'bar' ],
+                                       'options' => [ 'callback' => [ __CLASS__, 'callbackForTest' ] ]
+                               ] ],
+                               '/Foo',
+                               [
+                                       'title' => "Foo",
+                                       'x' => 'Foo',
+                                       'a' => 'b',
+                                       'foo' => 'bar'
+                               ]
+                       ],
+
+                       // Test to ensure that matches are not made if a parameter expects nonexistent input
+                       'Fail' => [
+                               [ [
+                                       'path' => "/wiki/$1",
+                                       'params' => [ 'title' => "$1$2" ],
+                               ] ],
+                               "/wiki/A",
+                               []
+                       ],
+
+                       // Make sure the router handles titles like Special:Recentchanges correctly
+                       'Special title' => [
+                               "/wiki/$1",
+                               "/wiki/Special:Recentchanges",
+                               [ 'title' => "Special:Recentchanges" ]
+                       ],
+
+                       // Make sure the router decodes urlencoding properly
+                       'URL encoding' => [
+                               "/wiki/$1",
+                               "/wiki/Title_With%20Space",
+                               [ 'title' => "Title_With Space" ]
+                       ],
+
+                       // Double slash and dot expansion
+                       'Double slash in prefix' => [
+                               '/wiki/$1',
+                               '//wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+                       'Double slash at start of $1' => [
+                               '/wiki/$1',
+                               '/wiki//Foo',
+                               [ 'title' => '/Foo' ]
+                       ],
+                       'Double slash in middle of $1' => [
+                               '/wiki/$1',
+                               '/wiki/.hack//SIGN',
+                               [ 'title' => '.hack//SIGN' ]
+                       ],
+                       'Dots removed 1' => [
+                               '/wiki/$1',
+                               '/x/../wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+                       'Dots removed 2' => [
+                               '/wiki/$1',
+                               '/./wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+                       'Dots retained 1' => [
+                               '/wiki/$1',
+                               '/wiki/../wiki/Foo',
+                               [ 'title' => '../wiki/Foo' ]
+                       ],
+                       'Dots retained 2' => [
+                               '/wiki/$1',
+                               '/wiki/./Foo',
+                               [ 'title' => './Foo' ]
+                       ],
+                       'Triple slash' => [
+                               '/wiki/$1',
+                               '///wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+                       // '..' only traverses one slash, see e.g. RFC 3986
+                       'Dots traversing double slash 1' => [
+                               '/wiki/$1',
+                               '/a//b/../../wiki/Foo',
+                               []
+                       ],
+                       'Dots traversing double slash 2' => [
+                               '/wiki/$1',
+                               '/a//b/../../../wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+               ];
+
+               // Make sure the router doesn't break on special characters like $ used in regexp replacements
+               foreach ( [ "$", "$1", "\\", "\\$1" ] as $char ) {
+                       $tests["Regexp character $char"] = [
+                               "/wiki/$1",
+                               "/wiki/$char",
+                               [ 'title' => "$char" ]
+                       ];
+               }
+
+               $tests += [
+                       // Make sure the router handles characters like +&() properly
+                       "Special characters" => [
+                               "/wiki/$1",
+                               "/wiki/Plus+And&Dollar\\Stuff();[]{}*",
+                               [ 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ],
+                       ],
+
+                       // Make sure the router handles unicode characters correctly
+                       "Unicode 1" => [
+                               "/wiki/$1",
+                               "/wiki/Spécial:Modifications_récentes" ,
+                               [ 'title' => "Spécial:Modifications_récentes" ],
+                       ],
+
+                       "Unicode 2" => [
+                               "/wiki/$1",
+                               "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes",
+                               [ 'title' => "Spécial:Modifications_récentes" ],
+                       ]
+               ];
+
+               // Ensure the router doesn't choke on long paths.
+               $lorem = "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_" .
+                       "tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_" .
+                        "nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._" .
+                        "Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_" .
+                        "eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_" .
+                        "in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum.";
+
+               $tests += [
+                       "Long path" => [
+                               "/wiki/$1",
+                               "/wiki/$lorem",
+                               [ 'title' => $lorem ]
+                       ],
+
+                       // Ensure that the php passed site of parameter values are not urldecoded
+                       "Pattern urlencoding" => [
+                               [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => '%20:$1' ] ] ],
+                               "/wiki/Foo",
+                               [ 'title' => '%20:Foo' ]
+                       ],
+
+                       // Ensure that raw parameter values do not have any variable replacements or urldecoding
+                       "Raw param value" => [
+                               [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => [ 'value' => 'bar%20$1' ] ] ] ],
+                               "/wiki/Foo",
+                               [ 'title' => 'bar%20$1' ]
+                       ]
+               ];
+
+               return $tests;
+       }
+
+       /**
+        * Test path parsing
+        * @dataProvider provideParse
+        */
+       public function testParse( $patterns, $path, $expected ) {
+               $patterns = (array)$patterns;
+
+               $router = new PathRouter;
+               foreach ( $patterns as $pattern ) {
+                       if ( is_array( $pattern ) ) {
+                               $router->add( $pattern['path'], $pattern['params'] ?? [],
+                                       $pattern['options'] ?? [] );
+                       } else {
+                               $router->add( $pattern );
+                       }
+               }
+               $matches = $router->parse( $path );
+               $this->assertEquals( $matches, $expected );
+       }
+
+       public static function callbackForTest( &$matches, $data ) {
+               $matches['x'] = $data['$1'];
+               $matches['foo'] = $data['foo'];
+       }
+
+       public static function provideWeight() {
+               return [
+                       [ '/Foo', [ 'title' => 'Foo' ] ],
+                       [ '/Bar', [ 'ping' => 'pong' ] ],
+                       [ '/Baz', [ 'marco' => 'polo' ] ],
+                       [ '/asdf-foo', [ 'title' => 'qwerty-foo' ] ],
+                       [ '/qwerty-bar', [ 'title' => 'asdf-bar' ] ],
+                       [ '/a/Foo', [ 'title' => 'Foo' ] ],
+                       [ '/asdf/Foo', [ 'title' => 'Foo' ] ],
+                       [ '/qwerty/Foo', [ 'title' => 'Foo', 'qwerty' => 'qwerty' ] ],
+                       [ '/baz/Foo', [ 'title' => 'Foo', 'unrestricted' => 'baz' ] ],
+                       [ '/y/Foo', [ 'title' => 'Foo', 'restricted-to-y' => 'y' ] ],
+               ];
+       }
+
+       /**
+        * Test to ensure weight of paths is handled correctly
+        * @dataProvider provideWeight
+        */
+       public function testWeight( $path, $expected ) {
+               $router = new PathRouter;
+               $router->addStrict( "/Bar", [ 'ping' => 'pong' ] );
+               $router->add( "/asdf-$1", [ 'title' => 'qwerty-$1' ] );
+               $router->add( "/$1" );
+               $router->add( "/qwerty-$1", [ 'title' => 'asdf-$1' ] );
+               $router->addStrict( "/Baz", [ 'marco' => 'polo' ] );
+               $router->add( "/a/$1" );
+               $router->add( "/asdf/$1" );
+               $router->add( "/$2/$1", [ 'unrestricted' => '$2' ] );
+               $router->add( [ 'qwerty' => "/qwerty/$1" ], [ 'qwerty' => '$key' ] );
+               $router->add( "/$2/$1", [ 'restricted-to-y' => '$2' ], [ '$2' => 'y' ] );
+
+               $this->assertEquals( $router->parse( $path ), $expected );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Rest/ResponseFactoryTest.php b/tests/phpunit/unit/includes/Rest/ResponseFactoryTest.php
new file mode 100644 (file)
index 0000000..04d54de
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use ArrayIterator;
+use MediaWiki\Rest\HttpException;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWikiUnitTestCase;
+
+/** @covers \MediaWiki\Rest\ResponseFactory */
+class ResponseFactoryTest extends MediaWikiUnitTestCase {
+       public static function provideEncodeJson() {
+               return [
+                       [ (object)[], '{}' ],
+                       [ '/', '"/"' ],
+                       [ '£', '"£"' ],
+                       [ [], '[]' ],
+               ];
+       }
+
+       /** @dataProvider provideEncodeJson */
+       public function testEncodeJson( $input, $expected ) {
+               $rf = new ResponseFactory;
+               $this->assertSame( $expected, $rf->encodeJson( $input ) );
+       }
+
+       public function testCreateJson() {
+               $rf = new ResponseFactory;
+               $response = $rf->createJson( [] );
+               $response->getBody()->rewind();
+               $this->assertSame( 'application/json', $response->getHeaderLine( 'Content-Type' ) );
+               $this->assertSame( '[]', $response->getBody()->getContents() );
+               // Make sure getSize() is functional, since testCreateNoContent() depends on it
+               $this->assertSame( 2, $response->getBody()->getSize() );
+       }
+
+       public function testCreateNoContent() {
+               $rf = new ResponseFactory;
+               $response = $rf->createNoContent();
+               $this->assertSame( [], $response->getHeader( 'Content-Type' ) );
+               $this->assertSame( 0, $response->getBody()->getSize() );
+               $this->assertSame( 204, $response->getStatusCode() );
+       }
+
+       public function testCreatePermanentRedirect() {
+               $rf = new ResponseFactory;
+               $response = $rf->createPermanentRedirect( 'http://www.example.com/' );
+               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+               $this->assertSame( 301, $response->getStatusCode() );
+       }
+
+       public function testCreateLegacyTemporaryRedirect() {
+               $rf = new ResponseFactory;
+               $response = $rf->createLegacyTemporaryRedirect( 'http://www.example.com/' );
+               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+               $this->assertSame( 302, $response->getStatusCode() );
+       }
+
+       public function testCreateTemporaryRedirect() {
+               $rf = new ResponseFactory;
+               $response = $rf->createTemporaryRedirect( 'http://www.example.com/' );
+               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+               $this->assertSame( 307, $response->getStatusCode() );
+       }
+
+       public function testCreateSeeOther() {
+               $rf = new ResponseFactory;
+               $response = $rf->createSeeOther( 'http://www.example.com/' );
+               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+               $this->assertSame( 303, $response->getStatusCode() );
+       }
+
+       public function testCreateNotModified() {
+               $rf = new ResponseFactory;
+               $response = $rf->createNotModified();
+               $this->assertSame( 0, $response->getBody()->getSize() );
+               $this->assertSame( 304, $response->getStatusCode() );
+       }
+
+       /** @expectedException \InvalidArgumentException */
+       public function testCreateHttpErrorInvalid() {
+               $rf = new ResponseFactory;
+               $rf->createHttpError( 200 );
+       }
+
+       public function testCreateHttpError() {
+               $rf = new ResponseFactory;
+               $response = $rf->createHttpError( 415, [ 'message' => '...' ] );
+               $this->assertSame( 415, $response->getStatusCode() );
+               $body = $response->getBody();
+               $body->rewind();
+               $data = json_decode( $body->getContents(), true );
+               $this->assertSame( 415, $data['httpCode'] );
+               $this->assertSame( '...', $data['message'] );
+       }
+
+       public function testCreateFromExceptionUnlogged() {
+               $rf = new ResponseFactory;
+               $response = $rf->createFromException( new HttpException( 'hello', 415 ) );
+               $this->assertSame( 415, $response->getStatusCode() );
+               $body = $response->getBody();
+               $body->rewind();
+               $data = json_decode( $body->getContents(), true );
+               $this->assertSame( 415, $data['httpCode'] );
+               $this->assertSame( 'hello', $data['message'] );
+       }
+
+       public function testCreateFromExceptionLogged() {
+               $rf = new ResponseFactory;
+               $response = $rf->createFromException( new \Exception( "hello", 415 ) );
+               $this->assertSame( 500, $response->getStatusCode() );
+               $body = $response->getBody();
+               $body->rewind();
+               $data = json_decode( $body->getContents(), true );
+               $this->assertSame( 500, $data['httpCode'] );
+               $this->assertSame( 'Error: exception of type Exception', $data['message'] );
+       }
+
+       public static function provideCreateFromReturnValue() {
+               return [
+                       [ 'hello', '{"value":"hello"}' ],
+                       [ true, '{"value":true}' ],
+                       [ [ 'x' => 'y' ], '{"x":"y"}' ],
+                       [ [ 'x', 'y' ], '["x","y"]' ],
+                       [ [ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
+                       [ (object)[ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
+                       [ [], '[]' ],
+                       [ (object)[], '{}' ],
+               ];
+       }
+
+       /** @dataProvider provideCreateFromReturnValue */
+       public function testCreateFromReturnValue( $input, $expected ) {
+               $rf = new ResponseFactory;
+               $response = $rf->createFromReturnValue( $input );
+               $body = $response->getBody();
+               $body->rewind();
+               $this->assertSame( $expected, $body->getContents() );
+       }
+
+       /** @expectedException \InvalidArgumentException */
+       public function testCreateFromReturnValueInvalid() {
+               $rf = new ResponseFactory;
+               $rf->createFromReturnValue( new ArrayIterator );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Revision/MainSlotRoleHandlerTest.php b/tests/phpunit/unit/includes/Revision/MainSlotRoleHandlerTest.php
new file mode 100644 (file)
index 0000000..9dff2cc
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\MainSlotRoleHandler;
+use MediaWikiUnitTestCase;
+use PHPUnit\Framework\MockObject\MockObject;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler
+ */
+class MainSlotRoleHandlerTest extends MediaWikiUnitTestCase {
+
+       private function makeTitleObject( $ns ) {
+               /** @var Title|MockObject $title */
+               $title = $this->getMockBuilder( Title::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $title->method( 'getNamespace' )
+                       ->willReturn( $ns );
+
+               return $title;
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
+        */
+       public function testConstruction() {
+               $handler = new MainSlotRoleHandler( [] );
+               $this->assertSame( 'main', $handler->getRole() );
+               $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
+
+               $hints = $handler->getOutputLayoutHints();
+               $this->assertArrayHasKey( 'display', $hints );
+               $this->assertArrayHasKey( 'region', $hints );
+               $this->assertArrayHasKey( 'placement', $hints );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
+        */
+       public function testFetDefaultModel() {
+               $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
+
+               // For the main handler, the namespace determins the default model
+               $titleMain = $this->makeTitleObject( NS_MAIN );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
+
+               $title100 = $this->makeTitleObject( 100 );
+               $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
+        */
+       public function testIsAllowedModel() {
+               $handler = new MainSlotRoleHandler( [] );
+
+               // For the main handler, (nearly) all models are allowed
+               $title = $this->makeTitleObject( NS_MAIN );
+               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
+               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
+        */
+       public function testSupportsArticleCount() {
+               $handler = new MainSlotRoleHandler( [] );
+
+               $this->assertTrue( $handler->supportsArticleCount() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/Revision/SlotRecordTest.php b/tests/phpunit/unit/includes/Revision/SlotRecordTest.php
new file mode 100644 (file)
index 0000000..aab430a
--- /dev/null
@@ -0,0 +1,416 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use InvalidArgumentException;
+use LogicException;
+use MediaWiki\Revision\IncompleteRevisionException;
+use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SuppressedDataException;
+use MediaWikiUnitTestCase;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRecord
+ */
+class SlotRecordTest extends MediaWikiUnitTestCase {
+
+       private function makeRow( $data = [] ) {
+               $data = $data + [
+                       'slot_id' => 1234,
+                       'slot_content_id' => 33,
+                       'content_size' => '5',
+                       'content_sha1' => 'someHash',
+                       'content_address' => 'tt:456',
+                       'model_name' => CONTENT_MODEL_WIKITEXT,
+                       'format_name' => CONTENT_FORMAT_WIKITEXT,
+                       'slot_revision_id' => '2',
+                       'slot_origin' => '1',
+                       'role_name' => 'myRole',
+               ];
+               return (object)$data;
+       }
+
+       public function testCompleteConstruction() {
+               $row = $this->makeRow();
+               $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+               $this->assertTrue( $record->hasAddress() );
+               $this->assertTrue( $record->hasContentId() );
+               $this->assertTrue( $record->hasRevision() );
+               $this->assertTrue( $record->isInherited() );
+               $this->assertSame( 'A', $record->getContent()->getText() );
+               $this->assertSame( 5, $record->getSize() );
+               $this->assertSame( 'someHash', $record->getSha1() );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+               $this->assertSame( 2, $record->getRevision() );
+               $this->assertSame( 1, $record->getOrigin() );
+               $this->assertSame( 'tt:456', $record->getAddress() );
+               $this->assertSame( 33, $record->getContentId() );
+               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+               $this->assertSame( 'myRole', $record->getRole() );
+       }
+
+       public function testConstructionDeferred() {
+               $row = $this->makeRow( [
+                       'content_size' => null, // to be computed
+                       'content_sha1' => null, // to be computed
+                       'format_name' => function () {
+                               return CONTENT_FORMAT_WIKITEXT;
+                       },
+                       'slot_revision_id' => '2',
+                       'slot_origin' => '2',
+                       'slot_content_id' => function () {
+                               return null;
+                       },
+               ] );
+
+               $content = function () {
+                       return new WikitextContent( 'A' );
+               };
+
+               $record = new SlotRecord( $row, $content );
+
+               $this->assertTrue( $record->hasAddress() );
+               $this->assertTrue( $record->hasRevision() );
+               $this->assertFalse( $record->hasContentId() );
+               $this->assertFalse( $record->isInherited() );
+               $this->assertSame( 'A', $record->getContent()->getText() );
+               $this->assertSame( 1, $record->getSize() );
+               $this->assertNotEmpty( $record->getSha1() );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+               $this->assertSame( 2, $record->getRevision() );
+               $this->assertSame( 2, $record->getRevision() );
+               $this->assertSame( 'tt:456', $record->getAddress() );
+               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+               $this->assertSame( 'myRole', $record->getRole() );
+       }
+
+       public function testNewUnsaved() {
+               $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
+
+               $this->assertFalse( $record->hasAddress() );
+               $this->assertFalse( $record->hasContentId() );
+               $this->assertFalse( $record->hasRevision() );
+               $this->assertFalse( $record->isInherited() );
+               $this->assertFalse( $record->hasOrigin() );
+               $this->assertSame( 'A', $record->getContent()->getText() );
+               $this->assertSame( 1, $record->getSize() );
+               $this->assertNotEmpty( $record->getSha1() );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+               $this->assertSame( 'myRole', $record->getRole() );
+       }
+
+       public function provideInvalidConstruction() {
+               yield 'both null' => [ null, null ];
+               yield 'null row' => [ null, new WikitextContent( 'A' ) ];
+               yield 'array row' => [ [], new WikitextContent( 'A' ) ];
+               yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
+               yield 'null content' => [ (object)[], null ];
+       }
+
+       /**
+        * @dataProvider provideInvalidConstruction
+        */
+       public function testInvalidConstruction( $row, $content ) {
+               $this->setExpectedException( InvalidArgumentException::class );
+               new SlotRecord( $row, $content );
+       }
+
+       public function testGetContentId_fails() {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getContentId();
+       }
+
+       public function testGetAddress_fails() {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getAddress();
+       }
+
+       public function provideIncomplete() {
+               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               yield 'unsaved' => [ $unsaved ];
+
+               $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+               $inherited = SlotRecord::newInherited( $parent );
+               yield 'inherited' => [ $inherited ];
+       }
+
+       /**
+        * @dataProvider provideIncomplete
+        */
+       public function testGetRevision_fails( SlotRecord $record ) {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getRevision();
+       }
+
+       /**
+        * @dataProvider provideIncomplete
+        */
+       public function testGetOrigin_fails( SlotRecord $record ) {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getOrigin();
+       }
+
+       public function provideHashStability() {
+               yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
+               yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
+       }
+
+       /**
+        * @dataProvider provideHashStability
+        */
+       public function testHashStability( $text, $hash ) {
+               // Changing the output of the hash function will break things horribly!
+
+               $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
+
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
+               $this->assertSame( $hash, $record->getSha1() );
+       }
+
+       public function testHashComputed() {
+               $row = $this->makeRow();
+               $row->content_sha1 = '';
+
+               $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
+               $this->assertNotEmpty( $rec->getSha1() );
+       }
+
+       public function testNewWithSuppressedContent() {
+               $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+               $output = SlotRecord::newWithSuppressedContent( $input );
+
+               $this->setExpectedException( SuppressedDataException::class );
+               $output->getContent();
+       }
+
+       public function testNewInherited() {
+               $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
+               $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+               // This would happen while doing an edit, before saving revision meta-data.
+               $inherited = SlotRecord::newInherited( $parent );
+
+               $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
+               $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
+               $this->assertSame( $parent->getContent(), $inherited->getContent() );
+               $this->assertTrue( $inherited->isInherited() );
+               $this->assertTrue( $inherited->hasOrigin() );
+               $this->assertFalse( $inherited->hasRevision() );
+
+               // make sure we didn't mess with the internal state of $parent
+               $this->assertFalse( $parent->isInherited() );
+               $this->assertSame( 7, $parent->getRevision() );
+
+               // This would happen while doing an edit, after saving the revision meta-data
+               // and content meta-data.
+               $saved = SlotRecord::newSaved(
+                       10,
+                       $inherited->getContentId(),
+                       $inherited->getAddress(),
+                       $inherited
+               );
+               $this->assertSame( $parent->getContentId(), $saved->getContentId() );
+               $this->assertSame( $parent->getAddress(), $saved->getAddress() );
+               $this->assertSame( $parent->getContent(), $saved->getContent() );
+               $this->assertTrue( $saved->isInherited() );
+               $this->assertTrue( $saved->hasRevision() );
+               $this->assertSame( 10, $saved->getRevision() );
+
+               // make sure we didn't mess with the internal state of $parent or $inherited
+               $this->assertSame( 7, $parent->getRevision() );
+               $this->assertFalse( $inherited->hasRevision() );
+       }
+
+       public function testNewSaved() {
+               // This would happen while doing an edit, before saving revision meta-data.
+               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+               // This would happen while doing an edit, after saving the revision meta-data
+               // and content meta-data.
+               $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
+               $this->assertFalse( $saved->isInherited() );
+               $this->assertTrue( $saved->hasOrigin() );
+               $this->assertTrue( $saved->hasRevision() );
+               $this->assertTrue( $saved->hasAddress() );
+               $this->assertTrue( $saved->hasContentId() );
+               $this->assertSame( 'theNewAddress', $saved->getAddress() );
+               $this->assertSame( 20, $saved->getContentId() );
+               $this->assertSame( 'A', $saved->getContent()->getText() );
+               $this->assertSame( 10, $saved->getRevision() );
+               $this->assertSame( 10, $saved->getOrigin() );
+
+               // make sure we didn't mess with the internal state of $unsaved
+               $this->assertFalse( $unsaved->hasAddress() );
+               $this->assertFalse( $unsaved->hasContentId() );
+               $this->assertFalse( $unsaved->hasRevision() );
+       }
+
+       public function provideNewSaved_LogicException() {
+               $freshRow = $this->makeRow( [
+                       'content_id' => 10,
+                       'content_address' => 'address:1',
+                       'slot_origin' => 1,
+                       'slot_revision_id' => 1,
+               ] );
+
+               $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
+               yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
+               yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
+               yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
+
+               $inheritedRow = $this->makeRow( [
+                       'content_id' => null,
+                       'content_address' => null,
+                       'slot_origin' => 0,
+                       'slot_revision_id' => 1,
+               ] );
+
+               $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
+               yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
+       }
+
+       /**
+        * @dataProvider provideNewSaved_LogicException
+        */
+       public function testNewSaved_LogicException(
+               $revisionId,
+               $contentId,
+               $contentAddress,
+               SlotRecord $protoSlot
+       ) {
+               $this->setExpectedException( LogicException::class );
+               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+       }
+
+       public function provideNewSaved_InvalidArgumentException() {
+               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+               yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
+               yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
+               yield 'bad content address' => [ 7, 5, 77, $unsaved ];
+       }
+
+       /**
+        * @dataProvider provideNewSaved_InvalidArgumentException
+        */
+       public function testNewSaved_InvalidArgumentException(
+               $revisionId,
+               $contentId,
+               $contentAddress,
+               SlotRecord $protoSlot
+       ) {
+               $this->setExpectedException( InvalidArgumentException::class );
+               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+       }
+
+       public function provideHasSameContent() {
+               $fail = function () {
+                       self::fail( 'There should be no need to actually load the content.' );
+               };
+
+               $a100a1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a1',
+                               ]
+                       ),
+                       $fail
+               );
+               $a100a1b = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a1',
+                               ]
+                       ),
+                       $fail
+               );
+               $a100null = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => null,
+                               ]
+                       ),
+                       $fail
+               );
+               $a100a2 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a2',
+                               ]
+                       ),
+                       $fail
+               );
+               $b100a1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'B',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a1',
+                               ]
+                       ),
+                       $fail
+               );
+               $a200a1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 200,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a2',
+                               ]
+                       ),
+                       $fail
+               );
+               $a100x1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-x',
+                                       'content_address' => 'xxx:x1',
+                               ]
+                       ),
+                       $fail
+               );
+
+               yield 'same instance' => [ $a100a1, $a100a1, true ];
+               yield 'no address' => [ $a100a1, $a100null, true ];
+               yield 'same address' => [ $a100a1, $a100a1b, true ];
+               yield 'different address' => [ $a100a1, $a100a2, true ];
+               yield 'different model' => [ $a100a1, $b100a1, false ];
+               yield 'different size' => [ $a100a1, $a200a1, false ];
+               yield 'different hash' => [ $a100a1, $a100x1, false ];
+       }
+
+       /**
+        * @dataProvider provideHasSameContent
+        */
+       public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
+               $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
+               $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/TitleArrayFromResultTest.php b/tests/phpunit/unit/includes/TitleArrayFromResultTest.php
new file mode 100644 (file)
index 0000000..10b7d36
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * @author Addshore
+ * @covers TitleArrayFromResult
+ */
+class TitleArrayFromResultTest extends MediaWikiUnitTestCase {
+
+       private function getMockResultWrapper( $row = null, $numRows = 1 ) {
+               $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
+                       ->disableOriginalConstructor();
+
+               $resultWrapper = $resultWrapper->getMock();
+               $resultWrapper->expects( $this->atLeastOnce() )
+                       ->method( 'current' )
+                       ->will( $this->returnValue( $row ) );
+               $resultWrapper->expects( $this->any() )
+                       ->method( 'numRows' )
+                       ->will( $this->returnValue( $numRows ) );
+
+               return $resultWrapper;
+       }
+
+       private function getRowWithTitle( $namespace = 3, $title = 'foo' ) {
+               $row = new stdClass();
+               $row->page_namespace = $namespace;
+               $row->page_title = $title;
+               return $row;
+       }
+
+       /**
+        * @covers TitleArrayFromResult::__construct
+        */
+       public function testConstructionWithFalseRow() {
+               $row = false;
+               $resultWrapper = $this->getMockResultWrapper( $row );
+
+               $object = new TitleArrayFromResult( $resultWrapper );
+
+               $this->assertEquals( $resultWrapper, $object->res );
+               $this->assertSame( 0, $object->key );
+               $this->assertEquals( $row, $object->current );
+       }
+
+       /**
+        * @covers TitleArrayFromResult::__construct
+        */
+       public function testConstructionWithRow() {
+               $namespace = 0;
+               $title = 'foo';
+               $row = $this->getRowWithTitle( $namespace, $title );
+               $resultWrapper = $this->getMockResultWrapper( $row );
+
+               $object = new TitleArrayFromResult( $resultWrapper );
+
+               $this->assertEquals( $resultWrapper, $object->res );
+               $this->assertSame( 0, $object->key );
+               $this->assertInstanceOf( Title::class, $object->current );
+               $this->assertEquals( $namespace, $object->current->mNamespace );
+               $this->assertEquals( $title, $object->current->mTextform );
+       }
+
+       public static function provideNumberOfRows() {
+               return [
+                       [ 0 ],
+                       [ 1 ],
+                       [ 122 ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideNumberOfRows
+        * @covers TitleArrayFromResult::count
+        */
+       public function testCountWithVaryingValues( $numRows ) {
+               $object = new TitleArrayFromResult( $this->getMockResultWrapper(
+                       $this->getRowWithTitle(),
+                       $numRows
+               ) );
+               $this->assertEquals( $numRows, $object->count() );
+       }
+
+       /**
+        * @covers TitleArrayFromResult::current
+        */
+       public function testCurrentAfterConstruction() {
+               $namespace = 0;
+               $title = 'foo';
+               $row = $this->getRowWithTitle( $namespace, $title );
+               $object = new TitleArrayFromResult( $this->getMockResultWrapper( $row ) );
+               $this->assertInstanceOf( Title::class, $object->current() );
+               $this->assertEquals( $namespace, $object->current->mNamespace );
+               $this->assertEquals( $title, $object->current->mTextform );
+       }
+
+       public function provideTestValid() {
+               return [
+                       [ $this->getRowWithTitle(), true ],
+                       [ false, false ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideTestValid
+        * @covers TitleArrayFromResult::valid
+        */
+       public function testValid( $input, $expected ) {
+               $object = new TitleArrayFromResult( $this->getMockResultWrapper( $input ) );
+               $this->assertEquals( $expected, $object->valid() );
+       }
+
+       // @todo unit test for key()
+       // @todo unit test for next()
+       // @todo unit test for rewind()
+}
diff --git a/tests/phpunit/unit/includes/WikiReferenceTest.php b/tests/phpunit/unit/includes/WikiReferenceTest.php
new file mode 100644 (file)
index 0000000..a4aae86
--- /dev/null
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * @covers WikiReference
+ */
+class WikiReferenceTest extends MediaWikiUnitTestCase {
+
+       public function provideGetDisplayName() {
+               return [
+                       'http' => [ 'foo.bar', 'http://foo.bar' ],
+                       'https' => [ 'foo.bar', 'http://foo.bar' ],
+
+                       // apparently, this is the expected behavior
+                       'invalid' => [ 'purple kittens', 'purple kittens' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetDisplayName
+        */
+       public function testGetDisplayName( $expected, $canonicalServer ) {
+               $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
+               $this->assertEquals( $expected, $reference->getDisplayName() );
+       }
+
+       public function testGetCanonicalServer() {
+               $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
+               $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
+       }
+
+       public function provideGetCanonicalUrl() {
+               return [
+                       'no fragment' => [
+                               'https://acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               null
+                       ],
+                       'empty fragment' => [
+                               'https://acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               ''
+                       ],
+                       'fragment' => [
+                               'https://acme.com/wiki/Foo#Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar'
+                       ],
+                       'double fragment' => [
+                               'https://acme.com/wiki/Foo#Bar%23Xus',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar#Xus'
+                       ],
+                       'escaped fragment' => [
+                               'https://acme.com/wiki/Foo%23Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo#Bar',
+                               null
+                       ],
+                       'empty path' => [
+                               'https://acme.com/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/$1',
+                               'Foo',
+                               null
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetCanonicalUrl
+        */
+       public function testGetCanonicalUrl(
+               $expected, $canonicalServer, $server, $path, $page, $fragmentId
+       ) {
+               $reference = new WikiReference( $canonicalServer, $path, $server );
+               $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
+       }
+
+       /**
+        * @dataProvider provideGetCanonicalUrl
+        * @note getUrl is an alias for getCanonicalUrl
+        */
+       public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+               $reference = new WikiReference( $canonicalServer, $path, $server );
+               $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
+       }
+
+       public function provideGetFullUrl() {
+               return [
+                       'no fragment' => [
+                               '//acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               null
+                       ],
+                       'empty fragment' => [
+                               '//acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               ''
+                       ],
+                       'fragment' => [
+                               '//acme.com/wiki/Foo#Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar'
+                       ],
+                       'double fragment' => [
+                               '//acme.com/wiki/Foo#Bar%23Xus',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar#Xus'
+                       ],
+                       'escaped fragment' => [
+                               '//acme.com/wiki/Foo%23Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo#Bar',
+                               null
+                       ],
+                       'empty path' => [
+                               '//acme.com/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/$1',
+                               'Foo',
+                               null
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetFullUrl
+        */
+       public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+               $reference = new WikiReference( $canonicalServer, $path, $server );
+               $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/debug/logger/monolog/CeeFormatterTest.php b/tests/phpunit/unit/includes/debug/logger/monolog/CeeFormatterTest.php
new file mode 100644 (file)
index 0000000..946a1a2
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace MediaWiki\Logger\Monolog;
+
+/**
+ * Flay per https://phabricator.wikimedia.org/T218688.
+ *
+ * @group Broken
+ * @covers \MediaWiki\Logger\Monolog\CeeFormatter
+ */
+class CeeFormatterTest extends \MediaWikiUnitTestCase {
+       public function testV1() {
+               $ls_formatter = new LogstashFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
+               $cee_formatter = new CeeFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
+               $record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ];
+               $this->assertSame(
+                       $cee_formatter->format( $record ),
+                       "@cee: " . $ls_formatter->format( $record ) );
+       }
+}
diff --git a/tests/phpunit/unit/includes/diff/DifferenceEngineSlotDiffRendererTest.php b/tests/phpunit/unit/includes/diff/DifferenceEngineSlotDiffRendererTest.php
new file mode 100644 (file)
index 0000000..1a8b585
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @covers DifferenceEngineSlotDiffRenderer
+ */
+class DifferenceEngineSlotDiffRendererTest extends \MediaWikiUnitTestCase {
+
+       public function testGetDiff() {
+               $differenceEngine = new CustomDifferenceEngine();
+               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+               $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+               $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+               $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+               $this->assertEquals( 'xxx|yyy', $diff );
+
+               $diff = $slotDiffRenderer->getDiff( null, $newContent );
+               $this->assertEquals( '|yyy', $diff );
+
+               $diff = $slotDiffRenderer->getDiff( $oldContent, null );
+               $this->assertEquals( 'xxx|', $diff );
+       }
+
+       public function testAddModules() {
+               $output = $this->getMockBuilder( OutputPage::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [ 'addModules' ] )
+                       ->getMock();
+               $output->expects( $this->once() )
+                       ->method( 'addModules' )
+                       ->with( 'foo' );
+               $differenceEngine = new CustomDifferenceEngine();
+               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+               $slotDiffRenderer->addModules( $output );
+       }
+
+       public function testGetExtraCacheKeys() {
+               $differenceEngine = new CustomDifferenceEngine();
+               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+               $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
+               $this->assertSame( [ 'foo' ], $extraCacheKeys );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/diff/SlotDiffRendererTest.php b/tests/phpunit/unit/includes/diff/SlotDiffRendererTest.php
new file mode 100644 (file)
index 0000000..f778115
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+
+use Wikimedia\Assert\ParameterTypeException;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers SlotDiffRenderer
+ */
+class SlotDiffRendererTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @dataProvider provideNormalizeContents
+        */
+       public function testNormalizeContents(
+               $oldContent, $newContent, $allowedClasses,
+               $expectedOldContent, $expectedNewContent, $expectedExceptionClass
+       ) {
+               $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+                       ->getMock();
+               try {
+                       // __call needs help deciding which parameter to take by reference
+                       call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
+                               'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
+                       $this->assertEquals( $expectedOldContent, $oldContent );
+                       $this->assertEquals( $expectedNewContent, $newContent );
+               } catch ( Exception $e ) {
+                       if ( !$expectedExceptionClass ) {
+                               throw $e;
+                       }
+                       $this->assertInstanceOf( $expectedExceptionClass, $e );
+               }
+       }
+
+       public function provideNormalizeContents() {
+               return [
+                       'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
+                       'left null' => [
+                               null, new WikitextContent( 'abc' ), null,
+                               new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
+                       ],
+                       'right null' => [
+                               new WikitextContent( 'def' ), null, null,
+                               new WikitextContent( 'def' ), new WikitextContent( '' ), null,
+                       ],
+                       'type filter' => [
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+                       ],
+                       'type filter (subclass)' => [
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+                       ],
+                       'type filter (null)' => [
+                               new WikitextContent( 'abc' ), null, TextContent::class,
+                               new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
+                       ],
+                       'type filter failure (left)' => [
+                               new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+                               null, null, ParameterTypeException::class,
+                       ],
+                       'type filter failure (right)' => [
+                               new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
+                               null, null, ParameterTypeException::class,
+                       ],
+                       'type filter (array syntax)' => [
+                               new WikitextContent( 'abc' ), new JsonContent( 'def' ),
+                               [ JsonContent::class, WikitextContent::class ],
+                               new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
+                       ],
+                       'type filter failure (array syntax)' => [
+                               new WikitextContent( 'abc' ), new CssContent( 'def' ),
+                               [ JsonContent::class, WikitextContent::class ],
+                               null, null, ParameterTypeException::class,
+                       ],
+               ];
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/unit/includes/filerepo/FileBackendDBRepoWrapperTest.php
new file mode 100644 (file)
index 0000000..6084601
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+
+class FileBackendDBRepoWrapperTest extends MediaWikiUnitTestCase {
+       protected $backendName = 'foo-backend';
+       protected $repoName = 'pureTestRepo';
+
+       /**
+        * @dataProvider getBackendPathsProvider
+        * @covers FileBackendDBRepoWrapper::getBackendPaths
+        */
+       public function testGetBackendPaths(
+               $mocks,
+               $latest,
+               $dbReadsExpected,
+               $dbReturnValue,
+               $originalPath,
+               $expectedBackendPath,
+               $message ) {
+               list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
+
+               $dbMock->expects( $dbReadsExpected )
+                       ->method( 'selectField' )
+                       ->will( $this->returnValue( $dbReturnValue ) );
+
+               $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
+
+               $this->assertEquals(
+                       $expectedBackendPath,
+                       $newPaths[0],
+                       $message );
+       }
+
+       public function getBackendPathsProvider() {
+               $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
+               $mocksForCaching = $this->getMocks();
+
+               return [
+                       [
+                               $mocksForCaching,
+                               false,
+                               $this->once(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-public/f/o/foobar.jpg',
+                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               'Public path translated correctly',
+                       ],
+                       [
+                               $mocksForCaching,
+                               false,
+                               $this->never(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-public/f/o/foobar.jpg',
+                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               'LRU cache leveraged',
+                       ],
+                       [
+                               $this->getMocks(),
+                               true,
+                               $this->once(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-public/f/o/foobar.jpg',
+                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               'Latest obtained',
+                       ],
+                       [
+                               $this->getMocks(),
+                               true,
+                               $this->never(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-deleted/f/o/foobar.jpg',
+                               $prefix . '-original/f/o/o/foobar',
+                               'Deleted path translated correctly',
+                       ],
+                       [
+                               $this->getMocks(),
+                               true,
+                               $this->once(),
+                               null,
+                               $prefix . '-public/b/a/baz.jpg',
+                               $prefix . '-public/b/a/baz.jpg',
+                               'Path left untouched if no sha1 can be found',
+                       ],
+               ];
+       }
+
+       /**
+        * @covers FileBackendDBRepoWrapper::getFileContentsMulti
+        */
+       public function testGetFileContentsMulti() {
+               list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
+
+               $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
+                       . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
+               $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
+                       . '-public/f/o/foobar.jpg';
+
+               $dbMock->expects( $this->once() )
+                       ->method( 'selectField' )
+                       ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
+
+               $backendMock->expects( $this->once() )
+                       ->method( 'getFileContentsMulti' )
+                       ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
+
+               $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
+
+               $this->assertEquals(
+                       [ $filenamePath => 'foo' ],
+                       $result,
+                       'File contents paths translated properly'
+               );
+       }
+
+       protected function getMocks() {
+               $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
+                       ->disableOriginalClone()
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $backendMock = $this->getMockBuilder( FSFileBackend::class )
+                       ->setConstructorArgs( [ [
+                                       'name' => $this->backendName,
+                                       'wikiId' => wfWikiID()
+                               ] ] )
+                       ->getMock();
+
+               $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
+                       ->setMethods( [ 'getDB' ] )
+                       ->setConstructorArgs( [ [
+                                       'backend' => $backendMock,
+                                       'repoName' => $this->repoName,
+                                       'dbHandleFactory' => null
+                               ] ] )
+                       ->getMock();
+
+               $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
+
+               return [ $dbMock, $backendMock, $wrapperMock ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/filerepo/file/ForeignDBFileTest.php b/tests/phpunit/unit/includes/filerepo/file/ForeignDBFileTest.php
new file mode 100644 (file)
index 0000000..32624aa
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+/** @covers ForeignDBFile */
+class ForeignDBFileTest extends \MediaWikiUnitTestCase {
+
+       public function testShouldConstructCorrectInstanceFromTitle() {
+               $title = Title::makeTitle( NS_FILE, 'Awesome_file' );
+               $repoMock = $this->createMock( LocalRepo::class );
+
+               $file = ForeignDBFile::newFromTitle( $title, $repoMock );
+
+               $this->assertInstanceOf( ForeignDBFile::class, $file );
+       }
+}
diff --git a/tests/phpunit/unit/includes/htmlform/HTMLCheckMatrixTest.php b/tests/phpunit/unit/includes/htmlform/HTMLCheckMatrixTest.php
new file mode 100644 (file)
index 0000000..659d48d
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @covers HTMLCheckMatrix
+ */
+class HTMLCheckMatrixTest extends MediaWikiUnitTestCase {
+       private static $defaultOptions = [
+               'rows' => [ 'r1', 'r2' ],
+               'columns' => [ 'c1', 'c2' ],
+               'fieldname' => 'test',
+       ];
+
+       public function testPlainInstantiation() {
+               try {
+                       new HTMLCheckMatrix( [] );
+               } catch ( MWException $e ) {
+                       $this->assertInstanceOf( HTMLFormFieldRequiredOptionsException::class, $e );
+                       return;
+               }
+
+               $this->fail( 'Expected MWException indicating missing parameters but none was thrown.' );
+       }
+
+       public function testInstantiationWithMinimumRequiredParameters() {
+               new HTMLCheckMatrix( self::$defaultOptions );
+               $this->assertTrue( true ); // form instantiation must throw exception on failure
+       }
+
+       public function testValidateCallsUserDefinedValidationCallback() {
+               $called = false;
+               $field = new HTMLCheckMatrix( self::$defaultOptions + [
+                       'validation-callback' => function () use ( &$called ) {
+                               $called = true;
+
+                               return false;
+                       },
+               ] );
+               $this->assertEquals( false, $this->validate( $field, [] ) );
+               $this->assertTrue( $called );
+       }
+
+       public function testValidateRequiresArrayInput() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions );
+               $this->assertEquals( false, $this->validate( $field, null ) );
+               $this->assertEquals( false, $this->validate( $field, true ) );
+               $this->assertEquals( false, $this->validate( $field, 'abc' ) );
+               $this->assertEquals( false, $this->validate( $field, new stdClass ) );
+               $this->assertEquals( true, $this->validate( $field, [] ) );
+       }
+
+       public function testValidateAllowsOnlyKnownTags() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions );
+               $this->assertInstanceOf( Message::class, $this->validate( $field, [ 'foo' ] ) );
+       }
+
+       public function testValidateAcceptsPartialTagList() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions );
+               $this->assertTrue( $this->validate( $field, [] ) );
+               $this->assertTrue( $this->validate( $field, [ 'c1-r1' ] ) );
+               $this->assertTrue( $this->validate( $field, [ 'c1-r1', 'c1-r2', 'c2-r1', 'c2-r2' ] ) );
+       }
+
+       /**
+        * This form object actually has no visibility into what happens later on, but essentially
+        * if the data submitted by the user passes validate the following is run:
+        * foreach ( $field->filterDataForSubmit( $data ) as $k => $v ) {
+        *     $user->setOption( $k, $v );
+        * }
+        */
+       public function testValuesForcedOnRemainOn() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions + [
+                               'force-options-on' => [ 'c2-r1' ],
+                       ] );
+               $expected = [
+                       'c1-r1' => false,
+                       'c1-r2' => false,
+                       'c2-r1' => true,
+                       'c2-r2' => false,
+               ];
+               $this->assertEquals( $expected, $field->filterDataForSubmit( [] ) );
+       }
+
+       public function testValuesForcedOffRemainOff() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions + [
+                               'force-options-off' => [ 'c1-r2', 'c2-r2' ],
+                       ] );
+               $expected = [
+                       'c1-r1' => true,
+                       'c1-r2' => false,
+                       'c2-r1' => true,
+                       'c2-r2' => false,
+               ];
+               // array_keys on the result simulates submitting all fields checked
+               $this->assertEquals( $expected, $field->filterDataForSubmit( array_keys( $expected ) ) );
+       }
+
+       protected function validate( HTMLFormField $field, $submitted ) {
+               return $field->validate(
+                       $submitted,
+                       [ self::$defaultOptions['fieldname'] => $submitted ]
+               );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/http/HttpUnitTest.php b/tests/phpunit/unit/includes/http/HttpUnitTest.php
new file mode 100644 (file)
index 0000000..af73f34
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @covers Http
+ * @group Http
+ * @group small
+ */
+class HttpUnitTest extends MediaWikiUnitTestCase {
+
+       /**
+        * Test Http::isValidURI()
+        * T29854 : Http::isValidURI is too lax
+        * @dataProvider provideURI
+        * @covers Http::isValidURI
+        */
+       public function testIsValidUri( $expect, $URI, $message = '' ) {
+               $this->assertEquals(
+                       $expect,
+                       (bool)Http::isValidURI( $URI ),
+                       $message
+               );
+       }
+
+       /**
+        * Feeds URI to test a long regular expression in Http::isValidURI
+        */
+       public static function provideURI() {
+               /** Format: 'boolean expectation', 'URI to test', 'Optional message' */
+               return [
+                       [ false, '¿non sens before!! http://a', 'Allow anything before URI' ],
+
+                       # (http|https) - only two schemes allowed
+                       [ true, 'http://www.example.org/' ],
+                       [ true, 'https://www.example.org/' ],
+                       [ true, 'http://www.example.org', 'URI without directory' ],
+                       [ true, 'http://a', 'Short name' ],
+                       [ true, 'http://étoile', 'Allow UTF-8 in hostname' ], # 'étoile' is french for 'star'
+                       [ false, '\\host\directory', 'CIFS share' ],
+                       [ false, 'gopher://host/dir', 'Reject gopher scheme' ],
+                       [ false, 'telnet://host', 'Reject telnet scheme' ],
+
+                       # :\/\/ - double slashes
+                       [ false, 'http//example.org', 'Reject missing colon in protocol' ],
+                       [ false, 'http:/example.org', 'Reject missing slash in protocol' ],
+                       [ false, 'http:example.org', 'Must have two slashes' ],
+                       # Following fail since hostname can be made of anything
+                       [ false, 'http:///example.org', 'Must have exactly two slashes, not three' ],
+
+                       # (\w+:{0,1}\w*@)? - optional user:pass
+                       [ true, 'http://user@host', 'Username provided' ],
+                       [ true, 'http://user:@host', 'Username provided, no password' ],
+                       [ true, 'http://user:pass@host', 'Username and password provided' ],
+
+                       # (\S+) - host part is made of anything not whitespaces
+                       // commented these out in order to remove @group Broken
+                       // @todo are these valid tests? if so, fix Http::isValidURI so it can handle them
+                       // [ false, 'http://!"èèè¿¿¿~~\'', 'hostname is made of any non whitespace' ],
+                       // [ false, 'http://exam:ple.org/', 'hostname can not use colons!' ],
+
+                       # (:[0-9]+)? - port number
+                       [ true, 'http://example.org:80/' ],
+                       [ true, 'https://example.org:80/' ],
+                       [ true, 'http://example.org:443/' ],
+                       [ true, 'https://example.org:443/' ],
+
+                       # Part after the hostname is / or / with something else
+                       [ true, 'http://example/#' ],
+                       [ true, 'http://example/!' ],
+                       [ true, 'http://example/:' ],
+                       [ true, 'http://example/.' ],
+                       [ true, 'http://example/?' ],
+                       [ true, 'http://example/+' ],
+                       [ true, 'http://example/=' ],
+                       [ true, 'http://example/&' ],
+                       [ true, 'http://example/%' ],
+                       [ true, 'http://example/@' ],
+                       [ true, 'http://example/-' ],
+                       [ true, 'http://example//' ],
+                       [ true, 'http://example/&' ],
+
+                       # Fragment
+                       [ true, 'http://exam#ple.org', ], # This one is valid, really!
+                       [ true, 'http://example.org:80#anchor' ],
+                       [ true, 'http://example.org/?id#anchor' ],
+                       [ true, 'http://example.org/?#anchor' ],
+
+                       [ false, 'http://a ¿non !!sens after', 'Allow anything after URI' ],
+               ];
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/json/FormatJsonTest.php b/tests/phpunit/unit/includes/json/FormatJsonTest.php
new file mode 100644 (file)
index 0000000..94c7d91
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @covers FormatJson
+ */
+class FormatJsonTest extends MediaWikiUnitTestCase {
+
+       /**
+        * Test data for testParseTryFixing.
+        *
+        * Some PHP interpreters use json-c rather than the JSON.org canonical
+        * parser to avoid being encumbered by the "shall be used for Good, not
+        * Evil" clause of the JSON.org parser's license. By default, json-c
+        * parses in a non-strict mode which allows trailing commas for array and
+        * object delarations among other things, so our JSON_ERROR_SYNTAX rescue
+        * block is not always triggered. It however isn't lenient in exactly the
+        * same ways as our TRY_FIXING mode, so the assertions in this test are
+        * a bit more complicated than they ideally would be:
+        *
+        * Optional third argument: true if json-c parses the value without
+        * intervention, false otherwise. Defaults to true.
+        *
+        * Optional fourth argument: expected cannonical JSON serialization of
+        * json-c parsed result. Defaults to the second argument's value.
+        */
+       public static function provideParseTryFixing() {
+               return [
+                       [ "[,]", '[]', false ],
+                       [ "[ , ]", '[]', false ],
+                       [ "[ , }", false ],
+                       [ '[1],', false, true, '[1]' ],
+                       [ "[1,]", '[1]' ],
+                       [ "[1\n,]", '[1]' ],
+                       [ "[1,\n]", '[1]' ],
+                       [ "[1,]\n", '[1]' ],
+                       [ "[1\n,\n]\n", '[1]' ],
+                       [ '["a,",]', '["a,"]' ],
+                       [ "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ],
+                       // I wish we could parse this, but would need quote parsing
+                       [ '[[1,],[2,],[3,]]', false, true, '[[1],[2],[3]]' ],
+                       [ '[1,,]', false, false, '[1]' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideParseTryFixing
+        * @param string $value
+        * @param string|bool $expected Expected result with strict parser
+        * @param bool $jsoncParses Will json-c parse this value without TRY_FIXING?
+        * @param string|bool $expectedJsonc Expected result with lenient parser
+        * if different from the strict expectation
+        */
+       public function testParseTryFixing(
+               $value, $expected,
+               $jsoncParses = true, $expectedJsonc = null
+       ) {
+               // PHP5 results are always expected to have isGood() === false
+               $expectedGoodStatus = false;
+
+               // Check to see if json parser allows trailing commas
+               if ( json_decode( '[1,]' ) !== null ) {
+                       // Use json-c specific expected result if provided
+                       $expected = ( $expectedJsonc === null ) ? $expected : $expectedJsonc;
+                       // If json-c parses the value natively, expect isGood() === true
+                       $expectedGoodStatus = $jsoncParses;
+               }
+
+               $st = FormatJson::parse( $value, FormatJson::TRY_FIXING );
+               $this->assertInstanceOf( Status::class, $st );
+               if ( $expected === false ) {
+                       $this->assertFalse( $st->isOK(), 'Expected isOK() == false' );
+               } else {
+                       $this->assertSame( $expectedGoodStatus, $st->isGood(),
+                               'Expected isGood() == ' . ( $expectedGoodStatus ? 'true' : 'false' )
+                       );
+                       $this->assertTrue( $st->isOK(), 'Expected isOK == true' );
+                       $val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK );
+                       $this->assertEquals( $expected, $val );
+               }
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/language/LanguageCodeTest.php b/tests/phpunit/unit/includes/language/LanguageCodeTest.php
new file mode 100644 (file)
index 0000000..f3a7ae4
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * @covers LanguageCode
+ * @group Language
+ *
+ * @author Thiemo Kreuz
+ */
+class LanguageCodeTest extends MediaWikiUnitTestCase {
+
+       public function testConstructor() {
+               $instance = new LanguageCode();
+
+               $this->assertInstanceOf( LanguageCode::class, $instance );
+       }
+
+       public function testGetDeprecatedCodeMapping() {
+               $map = LanguageCode::getDeprecatedCodeMapping();
+
+               $this->assertInternalType( 'array', $map );
+               $this->assertContainsOnly( 'string', array_keys( $map ) );
+               $this->assertArrayNotHasKey( '', $map );
+               $this->assertContainsOnly( 'string', $map );
+               $this->assertNotContains( '', $map );
+
+               // Codes special to MediaWiki should never appear in a map of "deprecated" codes
+               $this->assertArrayNotHasKey( 'qqq', $map, 'documentation' );
+               $this->assertNotContains( 'qqq', $map, 'documentation' );
+               $this->assertArrayNotHasKey( 'qqx', $map, 'debug code' );
+               $this->assertNotContains( 'qqx', $map, 'debug code' );
+
+               // Valid language codes that are currently not "deprecated"
+               $this->assertArrayNotHasKey( 'bh', $map, 'family of Bihari languages' );
+               $this->assertArrayNotHasKey( 'no', $map, 'family of Norwegian languages' );
+               $this->assertArrayNotHasKey( 'simple', $map );
+       }
+
+       public function testReplaceDeprecatedCodes() {
+               $this->assertEquals( 'gsw', LanguageCode::replaceDeprecatedCodes( 'als' ) );
+               $this->assertEquals( 'gsw', LanguageCode::replaceDeprecatedCodes( 'gsw' ) );
+               $this->assertEquals( null, LanguageCode::replaceDeprecatedCodes( null ) );
+       }
+
+       /**
+        * test @see LanguageCode::bcp47().
+        * Please note the BCP 47 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 https://tools.ietf.org/html/bcp47
+        * @dataProvider provideLanguageCodes()
+        */
+       public function testBcp47( $code, $expected ) {
+               $this->assertEquals( $expected, LanguageCode::bcp47( $code ),
+                       "Applying BCP 47 standard to '$code'"
+               );
+
+               $code = strtolower( $code );
+               $this->assertEquals( $expected, LanguageCode::bcp47( $code ),
+                       "Applying BCP 47 standard to lower case '$code'"
+               );
+
+               $code = strtoupper( $code );
+               $this->assertEquals( $expected, LanguageCode::bcp47( $code ),
+                       "Applying BCP 47 standard to upper case '$code'"
+               );
+       }
+
+       /**
+        * Array format is ($code, $expected)
+        */
+       public static function provideLanguageCodes() {
+               return [
+                       // Extracted from BCP 47 (list not exhaustive)
+                       # 2.1.1
+                       [ 'en-ca-x-ca', 'en-CA-x-ca' ],
+                       [ 'sgn-be-fr', 'sgn-BE-FR' ],
+                       [ 'az-latn-x-latn', 'az-Latn-x-latn' ],
+                       # 2.2
+                       [ 'sr-Latn-RS', 'sr-Latn-RS' ],
+                       [ 'az-arab-ir', 'az-Arab-IR' ],
+
+                       # 2.2.5
+                       [ 'sl-nedis', 'sl-nedis' ],
+                       [ 'de-ch-1996', 'de-CH-1996' ],
+
+                       # 2.2.6
+                       [
+                               'en-latn-gb-boont-r-extended-sequence-x-private',
+                               'en-Latn-GB-boont-r-extended-sequence-x-private'
+                       ],
+
+                       // Examples from BCP 47 Appendix A
+                       # Simple language subtag:
+                       [ 'DE', 'de' ],
+                       [ 'fR', 'fr' ],
+                       [ 'ja', 'ja' ],
+
+                       # Language subtag plus script subtag:
+                       [ 'zh-hans', 'zh-Hans' ],
+                       [ 'sr-cyrl', 'sr-Cyrl' ],
+                       [ 'sr-latn', 'sr-Latn' ],
+
+                       # Extended language subtags and their primary language subtag
+                       # counterparts:
+                       [ 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ],
+                       [ 'cmn-hans-cn', 'cmn-Hans-CN' ],
+                       [ 'zh-yue-hk', 'zh-yue-HK' ],
+                       [ 'yue-hk', 'yue-HK' ],
+
+                       # Language-Script-Region:
+                       [ 'zh-hans-cn', 'zh-Hans-CN' ],
+                       [ 'sr-latn-RS', 'sr-Latn-RS' ],
+
+                       # Language-Variant:
+                       [ 'sl-rozaj', 'sl-rozaj' ],
+                       [ 'sl-rozaj-biske', 'sl-rozaj-biske' ],
+                       [ 'sl-nedis', 'sl-nedis' ],
+
+                       # Language-Region-Variant:
+                       [ 'de-ch-1901', 'de-CH-1901' ],
+                       [ 'sl-it-nedis', 'sl-IT-nedis' ],
+
+                       # Language-Script-Region-Variant:
+                       [ 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ],
+
+                       # Language-Region:
+                       [ 'de-de', 'de-DE' ],
+                       [ 'en-us', 'en-US' ],
+                       [ 'es-419', 'es-419' ],
+
+                       # Private use subtags:
+                       [ 'de-ch-x-phonebk', 'de-CH-x-phonebk' ],
+                       [ 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ],
+                       /**
+                        * Previous test does not reflect the BCP 47 which states:
+                        *  az-Arab-x-AZE-derbend
+                        * AZE being private, it should be lower case, hence the test above
+                        * should probably be:
+                        * [ 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ],
+                        */
+
+                       # Private use registry values:
+                       [ 'x-whatever', 'x-whatever' ],
+                       [ 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ],
+                       [ 'de-qaaa', 'de-Qaaa' ],
+                       [ 'sr-latn-qm', 'sr-Latn-QM' ],
+                       [ 'sr-qaaa-rs', 'sr-Qaaa-RS' ],
+
+                       # Tags that use extensions
+                       [ 'en-us-u-islamcal', 'en-US-u-islamcal' ],
+                       [ 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ],
+                       [ 'en-a-myext-b-another', 'en-a-myext-b-another' ],
+
+                       # Invalid:
+                       // de-419-DE
+                       // a-DE
+                       // ar-a-aaa-b-bbb-a-ccc
+
+                       # Non-standard and deprecated language codes used by MediaWiki
+                       [ 'als', 'gsw' ],
+                       [ 'bat-smg', 'sgs' ],
+                       [ 'be-x-old', 'be-tarask' ],
+                       [ 'fiu-vro', 'vro' ],
+                       [ 'roa-rup', 'rup' ],
+                       [ 'zh-classical', 'lzh' ],
+                       [ 'zh-min-nan', 'nan' ],
+                       [ 'zh-yue', 'yue' ],
+                       [ 'cbk-zam', 'cbk' ],
+                       [ 'de-formal', 'de-x-formal' ],
+                       [ 'eml', 'egl' ],
+                       [ 'en-rtl', 'en-x-rtl' ],
+                       [ 'es-formal', 'es-x-formal' ],
+                       [ 'hu-formal', 'hu-x-formal' ],
+                       [ 'kk-Arab', 'kk-Arab' ],
+                       [ 'kk-Cyrl', 'kk-Cyrl' ],
+                       [ 'kk-Latn', 'kk-Latn' ],
+                       [ 'map-bms', 'jv-x-bms' ],
+                       [ 'mo', 'ro-Cyrl-MD' ],
+                       [ 'nrm', 'nrf' ],
+                       [ 'nl-informal', 'nl-x-informal' ],
+                       [ 'roa-tara', 'nap-x-tara' ],
+                       [ 'simple', 'en-simple' ],
+                       [ 'sr-ec', 'sr-Cyrl' ],
+                       [ 'sr-el', 'sr-Latn' ],
+                       [ 'zh-cn', 'zh-Hans-CN' ],
+                       [ 'zh-sg', 'zh-Hans-SG' ],
+                       [ 'zh-my', 'zh-Hans-MY' ],
+                       [ 'zh-tw', 'zh-Hant-TW' ],
+                       [ 'zh-hk', 'zh-Hant-HK' ],
+                       [ 'zh-mo', 'zh-Hant-MO' ],
+                       [ 'zh-hans', 'zh-Hans' ],
+                       [ 'zh-hant', 'zh-Hant' ],
+               ];
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/language/SpecialPageAliasTest.php b/tests/phpunit/unit/includes/language/SpecialPageAliasTest.php
new file mode 100644 (file)
index 0000000..cce9d0e
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * Verifies that special page aliases are valid, with no slashes.
+ *
+ * @group Language
+ * @group SpecialPageAliases
+ * @group SystemTest
+ * @group medium
+ * @todo This should be a structure test
+ *
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class SpecialPageAliasTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @coversNothing
+        * @dataProvider validSpecialPageAliasesProvider
+        */
+       public function testValidSpecialPageAliases( $code, $specialPageAliases ) {
+               foreach ( $specialPageAliases as $specialPage => $aliases ) {
+                       foreach ( $aliases as $alias ) {
+                               $msg = "$specialPage alias '$alias' in $code is valid with no slashes";
+                               $this->assertRegExp( '/^[^\/]*$/', $msg );
+                       }
+               }
+       }
+
+       public function validSpecialPageAliasesProvider() {
+               $codes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
+
+               $data = [];
+
+               foreach ( $codes as $code ) {
+                       $specialPageAliases = $this->getSpecialPageAliases( $code );
+
+                       if ( $specialPageAliases !== [] ) {
+                               $data[] = [ $code, $specialPageAliases ];
+                       }
+               }
+
+               return $data;
+       }
+
+       /**
+        * @param string $code
+        *
+        * @return array
+        */
+       protected function getSpecialPageAliases( $code ) {
+               $file = Language::getMessagesFileName( $code );
+
+               if ( is_readable( $file ) ) {
+                       include $file;
+
+                       if ( isset( $specialPageAliases ) && $specialPageAliases !== null ) {
+                               return $specialPageAliases;
+                       }
+               }
+
+               return [];
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/unit/includes/media/JpegMetadataExtractorTest.php
new file mode 100644 (file)
index 0000000..365c140
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+/**
+ * @todo Could use a test of extended XMP segments. Hard to find programs that
+ * create example files, and creating my own in vim propbably wouldn't
+ * serve as a very good "test". (Adobe photoshop probably creates such files
+ * but it costs money). The implementation of it currently in MediaWiki is based
+ * solely on reading the standard, without any real world test files.
+ *
+ * @group Media
+ * @covers JpegMetadataExtractor
+ */
+class JpegMetadataExtractorTest extends MediaWikiUnitTestCase {
+
+       protected $filePath;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->filePath = __DIR__ . '/../../../data/media/';
+       }
+
+       /**
+        * We also use this test to test padding bytes don't
+        * screw stuff up
+        *
+        * @param string $file Filename
+        *
+        * @dataProvider provideUtf8Comment
+        */
+       public function testUtf8Comment( $file ) {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
+               $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
+       }
+
+       public static function provideUtf8Comment() {
+               return [
+                       [ 'jpeg-comment-utf.jpg' ],
+                       [ 'jpeg-padding-even.jpg' ],
+                       [ 'jpeg-padding-odd.jpg' ],
+               ];
+       }
+
+       /** The file is iso-8859-1, but it should get auto converted */
+       public function testIso88591Comment() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
+               $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
+       }
+
+       /** Comment values that are non-textual (random binary junk) should not be shown.
+        * The example test file has a comment with a 0x5 byte in it which is a control character
+        * and considered binary junk for our purposes.
+        */
+       public function testBinaryCommentStripped() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
+               $this->assertEmpty( $res['COM'] );
+       }
+
+       /* Very rarely a file can have multiple comments.
+        *   Order of comments is based on order inside the file.
+        */
+       public function testMultipleComment() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
+               $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
+       }
+
+       public function testXMPExtraction() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+               $this->assertEquals( $expected, $res['XMP'] );
+       }
+
+       public function testPSIRExtraction() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+               $expected = '50686f746f73686f7020332e30003842494d04040000000'
+                       . '000181c02190004746573741c02190003666f6f1c020000020004';
+               $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
+       }
+
+       public function testXMPExtractionAltAppId() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
+               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+               $this->assertEquals( $expected, $res['XMP'] );
+       }
+
+       public function testIPTCHashComparisionNoHash() {
+               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+               $this->assertEquals( 'iptc-no-hash', $res );
+       }
+
+       public function testIPTCHashComparisionBadHash() {
+               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
+               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+               $this->assertEquals( 'iptc-bad-hash', $res );
+       }
+
+       public function testIPTCHashComparisionGoodHash() {
+               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
+               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+               $this->assertEquals( 'iptc-good-hash', $res );
+       }
+
+       public function testExifByteOrder() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
+               $expected = 'BE';
+               $this->assertEquals( $expected, $res['byteOrder'] );
+       }
+
+       public function testInfiniteRead() {
+               // test file truncated right after a segment, which previously
+               // caused an infinite loop looking for the next segment byte.
+               // Should get past infinite loop and throw in wfUnpack()
+               $this->setExpectedException( 'MWException' );
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
+       }
+
+       public function testInfiniteRead2() {
+               // test file truncated after a segment's marker and size, which
+               // would cause a seek past end of file. Seek past end of file
+               // doesn't actually fail, but prevents further reading and was
+               // devolving into the previous case (testInfiniteRead).
+               $this->setExpectedException( 'MWException' );
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
+       }
+}
diff --git a/tests/phpunit/unit/includes/page/ArticleTest.php b/tests/phpunit/unit/includes/page/ArticleTest.php
new file mode 100644 (file)
index 0000000..9995793
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+class ArticleTest extends MediaWikiUnitTestCase {
+
+       /**
+        * @var Title
+        */
+       private $title;
+       /**
+        * @var Article
+        */
+       private $article;
+
+       /** creates a title object and its article object */
+       protected function setUp() {
+               parent::setUp();
+               $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
+               $this->article = new Article( $this->title );
+       }
+
+       /** cleanup title object and its article object */
+       protected function tearDown() {
+               parent::tearDown();
+               $this->title = null;
+               $this->article = null;
+       }
+
+       /**
+        * @covers Article::__get
+        */
+       public function testImplementsGetMagic() {
+               $this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
+       }
+
+       /**
+        * @depends testImplementsGetMagic
+        * @covers Article::__set
+        */
+       public function testImplementsSetMagic() {
+               $this->article->mLatest = 2;
+               $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
+       }
+
+       /**
+        * @covers Article::__get
+        * @covers Article::__set
+        */
+       public function testGetOrSetOnNewProperty() {
+               $this->article->ext_someNewProperty = 12;
+               $this->assertEquals( 12, $this->article->ext_someNewProperty,
+                       "Article get/set magic on new field" );
+
+               $this->article->ext_someNewProperty = -8;
+               $this->assertEquals( -8, $this->article->ext_someNewProperty,
+                       "Article get/set magic on update to new field" );
+       }
+}
diff --git a/tests/phpunit/unit/includes/session/SessionUnitTest.php b/tests/phpunit/unit/includes/session/SessionUnitTest.php
new file mode 100644 (file)
index 0000000..b6e1d3a
--- /dev/null
@@ -0,0 +1,258 @@
+<?php
+
+namespace MediaWiki\Session;
+
+use Psr\Log\LogLevel;
+use MediaWikiUnitTestCase;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Session
+ * @covers MediaWiki\Session\Session
+ */
+class SessionUnitTest extends MediaWikiUnitTestCase {
+
+       public function testConstructor() {
+               $backend = TestUtils::getDummySessionBackend();
+               TestingAccessWrapper::newFromObject( $backend )->requests = [ -1 => 'dummy' ];
+               TestingAccessWrapper::newFromObject( $backend )->id = new SessionId( 'abc' );
+
+               $session = new Session( $backend, 42, new \TestLogger );
+               $priv = TestingAccessWrapper::newFromObject( $session );
+               $this->assertSame( $backend, $priv->backend );
+               $this->assertSame( 42, $priv->index );
+
+               $request = new \FauxRequest();
+               $priv2 = TestingAccessWrapper::newFromObject( $session->sessionWithRequest( $request ) );
+               $this->assertSame( $backend, $priv2->backend );
+               $this->assertNotSame( $priv->index, $priv2->index );
+               $this->assertSame( $request, $priv2->getRequest() );
+       }
+
+       /**
+        * @dataProvider provideMethods
+        * @param string $m Method to test
+        * @param array $args Arguments to pass to the method
+        * @param bool $index Whether the backend method gets passed the index
+        * @param bool $ret Whether the method returns a value
+        */
+       public function testMethods( $m, $args, $index, $ret ) {
+               $mock = $this->getMockBuilder( DummySessionBackend::class )
+                       ->setMethods( [ $m, 'deregisterSession' ] )
+                       ->getMock();
+               $mock->expects( $this->once() )->method( 'deregisterSession' )
+                       ->with( $this->identicalTo( 42 ) );
+
+               $tmp = $mock->expects( $this->once() )->method( $m );
+               $expectArgs = [];
+               if ( $index ) {
+                       $expectArgs[] = $this->identicalTo( 42 );
+               }
+               foreach ( $args as $arg ) {
+                       $expectArgs[] = $this->identicalTo( $arg );
+               }
+               $tmp = call_user_func_array( [ $tmp, 'with' ], $expectArgs );
+
+               $retval = new \stdClass;
+               $tmp->will( $this->returnValue( $retval ) );
+
+               $session = TestUtils::getDummySession( $mock, 42 );
+
+               if ( $ret ) {
+                       $this->assertSame( $retval, call_user_func_array( [ $session, $m ], $args ) );
+               } else {
+                       $this->assertNull( call_user_func_array( [ $session, $m ], $args ) );
+               }
+
+               // Trigger Session destructor
+               $session = null;
+       }
+
+       public static function provideMethods() {
+               return [
+                       [ 'getId', [], false, true ],
+                       [ 'getSessionId', [], false, true ],
+                       [ 'resetId', [], false, true ],
+                       [ 'getProvider', [], false, true ],
+                       [ 'isPersistent', [], false, true ],
+                       [ 'persist', [], false, false ],
+                       [ 'unpersist', [], false, false ],
+                       [ 'shouldRememberUser', [], false, true ],
+                       [ 'setRememberUser', [ true ], false, false ],
+                       [ 'getRequest', [], true, true ],
+                       [ 'getUser', [], false, true ],
+                       [ 'getAllowedUserRights', [], false, true ],
+                       [ 'canSetUser', [], false, true ],
+                       [ 'setUser', [ new \stdClass ], false, false ],
+                       [ 'suggestLoginUsername', [], true, true ],
+                       [ 'shouldForceHTTPS', [], false, true ],
+                       [ 'setForceHTTPS', [ true ], false, false ],
+                       [ 'getLoggedOutTimestamp', [], false, true ],
+                       [ 'setLoggedOutTimestamp', [ 123 ], false, false ],
+                       [ 'getProviderMetadata', [], false, true ],
+                       [ 'save', [], false, false ],
+                       [ 'delaySave', [], false, true ],
+                       [ 'renew', [], false, false ],
+               ];
+       }
+
+       public function testDataAccess() {
+               $session = TestUtils::getDummySession();
+               $backend = TestingAccessWrapper::newFromObject( $session )->backend;
+
+               $this->assertEquals( 1, $session->get( 'foo' ) );
+               $this->assertEquals( 'zero', $session->get( 0 ) );
+               $this->assertFalse( $backend->dirty );
+
+               $this->assertEquals( null, $session->get( 'null' ) );
+               $this->assertEquals( 'default', $session->get( 'null', 'default' ) );
+               $this->assertFalse( $backend->dirty );
+
+               $session->set( 'foo', 55 );
+               $this->assertEquals( 55, $backend->data['foo'] );
+               $this->assertTrue( $backend->dirty );
+               $backend->dirty = false;
+
+               $session->set( 1, 'one' );
+               $this->assertEquals( 'one', $backend->data[1] );
+               $this->assertTrue( $backend->dirty );
+               $backend->dirty = false;
+
+               $session->set( 1, 'one' );
+               $this->assertFalse( $backend->dirty );
+
+               $this->assertTrue( $session->exists( 'foo' ) );
+               $this->assertTrue( $session->exists( 1 ) );
+               $this->assertFalse( $session->exists( 'null' ) );
+               $this->assertFalse( $session->exists( 100 ) );
+               $this->assertFalse( $backend->dirty );
+
+               $session->remove( 'foo' );
+               $this->assertArrayNotHasKey( 'foo', $backend->data );
+               $this->assertTrue( $backend->dirty );
+               $backend->dirty = false;
+               $session->remove( 1 );
+               $this->assertArrayNotHasKey( 1, $backend->data );
+               $this->assertTrue( $backend->dirty );
+               $backend->dirty = false;
+
+               $session->remove( 101 );
+               $this->assertFalse( $backend->dirty );
+
+               $backend->data = [ 'a', 'b', '?' => 'c' ];
+               $this->assertSame( 3, $session->count() );
+               $this->assertSame( 3, count( $session ) );
+               $this->assertFalse( $backend->dirty );
+
+               $data = [];
+               foreach ( $session as $key => $value ) {
+                       $data[$key] = $value;
+               }
+               $this->assertEquals( $backend->data, $data );
+               $this->assertFalse( $backend->dirty );
+
+               $this->assertEquals( $backend->data, iterator_to_array( $session ) );
+               $this->assertFalse( $backend->dirty );
+       }
+
+       public function testArrayAccess() {
+               $logger = new \TestLogger;
+               $session = TestUtils::getDummySession( null, -1, $logger );
+               $backend = TestingAccessWrapper::newFromObject( $session )->backend;
+
+               $this->assertEquals( 1, $session['foo'] );
+               $this->assertEquals( 'zero', $session[0] );
+               $this->assertFalse( $backend->dirty );
+
+               $logger->setCollect( true );
+               $this->assertEquals( null, $session['null'] );
+               $logger->setCollect( false );
+               $this->assertFalse( $backend->dirty );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): null' ]
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               $session['foo'] = 55;
+               $this->assertEquals( 55, $backend->data['foo'] );
+               $this->assertTrue( $backend->dirty );
+               $backend->dirty = false;
+
+               $session[1] = 'one';
+               $this->assertEquals( 'one', $backend->data[1] );
+               $this->assertTrue( $backend->dirty );
+               $backend->dirty = false;
+
+               $session[1] = 'one';
+               $this->assertFalse( $backend->dirty );
+
+               $session['bar'] = [ 'baz' => [] ];
+               $session['bar']['baz']['quux'] = 2;
+               $this->assertEquals( [ 'baz' => [ 'quux' => 2 ] ], $backend->data['bar'] );
+
+               $logger->setCollect( true );
+               $session['bar2']['baz']['quux'] = 3;
+               $logger->setCollect( false );
+               $this->assertEquals( [ 'baz' => [ 'quux' => 3 ] ], $backend->data['bar2'] );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): bar2' ]
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               $backend->dirty = false;
+               $this->assertTrue( isset( $session['foo'] ) );
+               $this->assertTrue( isset( $session[1] ) );
+               $this->assertFalse( isset( $session['null'] ) );
+               $this->assertFalse( isset( $session['missing'] ) );
+               $this->assertFalse( isset( $session[100] ) );
+               $this->assertFalse( $backend->dirty );
+
+               unset( $session['foo'] );
+               $this->assertArrayNotHasKey( 'foo', $backend->data );
+               $this->assertTrue( $backend->dirty );
+               $backend->dirty = false;
+               unset( $session[1] );
+               $this->assertArrayNotHasKey( 1, $backend->data );
+               $this->assertTrue( $backend->dirty );
+               $backend->dirty = false;
+
+               unset( $session[101] );
+               $this->assertFalse( $backend->dirty );
+       }
+
+       public function testTokens() {
+               $session = TestUtils::getDummySession();
+               $priv = TestingAccessWrapper::newFromObject( $session );
+               $backend = $priv->backend;
+
+               $token = TestingAccessWrapper::newFromObject( $session->getToken() );
+               $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
+               $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
+               $secret = $backend->data['wsTokenSecrets']['default'];
+               $this->assertSame( $secret, $token->secret );
+               $this->assertSame( '', $token->salt );
+               $this->assertTrue( $token->wasNew() );
+
+               $token = TestingAccessWrapper::newFromObject( $session->getToken( 'foo' ) );
+               $this->assertSame( $secret, $token->secret );
+               $this->assertSame( 'foo', $token->salt );
+               $this->assertFalse( $token->wasNew() );
+
+               $backend->data['wsTokenSecrets']['secret'] = 'sekret';
+               $token = TestingAccessWrapper::newFromObject(
+                       $session->getToken( [ 'bar', 'baz' ], 'secret' )
+               );
+               $this->assertSame( 'sekret', $token->secret );
+               $this->assertSame( 'bar|baz', $token->salt );
+               $this->assertFalse( $token->wasNew() );
+
+               $session->resetToken( 'secret' );
+               $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
+               $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
+               $this->assertArrayNotHasKey( 'secret', $backend->data['wsTokenSecrets'] );
+
+               $session->resetAllTokens();
+               $this->assertArrayNotHasKey( 'wsTokenSecrets', $backend->data );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/session/TokenTest.php b/tests/phpunit/unit/includes/session/TokenTest.php
new file mode 100644 (file)
index 0000000..5546603
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+
+namespace MediaWiki\Session;
+
+use MediaWikiUnitTestCase;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Session
+ * @covers MediaWiki\Session\Token
+ */
+class TokenTest extends MediaWikiUnitTestCase {
+
+       public function testBasics() {
+               $token = $this->getMockBuilder( Token::class )
+                       ->setMethods( [ 'toStringAtTimestamp' ] )
+                       ->setConstructorArgs( [ 'sekret', 'salty', true ] )
+                       ->getMock();
+               $token->expects( $this->any() )->method( 'toStringAtTimestamp' )
+                       ->will( $this->returnValue( 'faketoken+\\' ) );
+
+               $this->assertSame( 'faketoken+\\', $token->toString() );
+               $this->assertSame( 'faketoken+\\', (string)$token );
+               $this->assertTrue( $token->wasNew() );
+
+               $token = new Token( 'sekret', 'salty', false );
+               $this->assertFalse( $token->wasNew() );
+       }
+
+       public function testToStringAtTimestamp() {
+               $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
+
+               $this->assertSame(
+                       'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\',
+                       $token->toStringAtTimestamp( 1447362018 )
+               );
+               $this->assertSame(
+                       'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\',
+                       $token->toStringAtTimestamp( 1447362026 )
+               );
+       }
+
+       public function testGetTimestamp() {
+               $this->assertSame(
+                       1447362018, Token::getTimestamp( 'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\' )
+               );
+               $this->assertSame(
+                       1447362026, Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\' )
+               );
+               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
+               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be+\\' ) );
+
+               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9x76c224cfb400d43be5644fdea+\\' ) );
+       }
+
+       public function testMatch() {
+               $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
+
+               $test = $token->toStringAtTimestamp( time() - 10 );
+               $this->assertTrue( $token->match( $test ) );
+               $this->assertTrue( $token->match( $test, 12 ) );
+               $this->assertFalse( $token->match( $test, 8 ) );
+
+               $this->assertFalse( $token->match( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/shell/FirejailCommandTest.php b/tests/phpunit/unit/includes/shell/FirejailCommandTest.php
new file mode 100644 (file)
index 0000000..e3a3b86
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+use MediaWiki\Shell\FirejailCommand;
+use MediaWiki\Shell\Shell;
+use Wikimedia\TestingAccessWrapper;
+
+class FirejailCommandTest extends MediaWikiUnitTestCase {
+
+       public function provideBuildFinalCommand() {
+               global $IP;
+               // phpcs:ignore Generic.Files.LineLength
+               $env = "'MW_INCLUDE_STDERR=;MW_CPU_LIMIT=180; MW_CGROUP='\'''\''; MW_MEM_LIMIT=307200; MW_FILE_SIZE_LIMIT=102400; MW_WALL_CLOCK_LIMIT=180; MW_USE_LOG_PIPE=yes'";
+               $limit = "/bin/bash '$IP/includes/shell/limit.sh'";
+               $profile = "--profile=$IP/includes/shell/firejail.profile";
+               $blacklist = '--blacklist=' . realpath( MW_CONFIG_FILE );
+               $default = "$blacklist --noroot --seccomp --private-dev";
+               return [
+                       [
+                               'No restrictions',
+                               'ls', 0, "$limit ''\''ls'\''' $env"
+                       ],
+                       [
+                               'default restriction',
+                               'ls', Shell::RESTRICT_DEFAULT,
+                               "$limit 'firejail --quiet $profile $default -- '\''ls'\''' $env"
+                       ],
+                       [
+                               'no network',
+                               'ls', Shell::NO_NETWORK,
+                               "$limit 'firejail --quiet $profile --net=none -- '\''ls'\''' $env"
+                       ],
+                       [
+                               'default restriction & no network',
+                               'ls', Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK,
+                               "$limit 'firejail --quiet $profile $default --net=none -- '\''ls'\''' $env"
+                       ],
+                       [
+                               'seccomp',
+                               'ls', Shell::SECCOMP,
+                               "$limit 'firejail --quiet $profile --seccomp -- '\''ls'\''' $env"
+                       ],
+                       [
+                               'seccomp & no execve',
+                               'ls', Shell::SECCOMP | Shell::NO_EXECVE,
+                               "$limit 'firejail --quiet $profile --shell=none --seccomp=execve -- '\''ls'\''' $env"
+                       ],
+               ];
+       }
+
+       /**
+        * @covers \MediaWiki\Shell\FirejailCommand::buildFinalCommand()
+        * @dataProvider provideBuildFinalCommand
+        */
+       public function testBuildFinalCommand( $desc, $params, $flags, $expected ) {
+               $command = new FirejailCommand( 'firejail' );
+               $command
+                       ->params( $params )
+                       ->restrict( $flags );
+               $wrapper = TestingAccessWrapper::newFromObject( $command );
+               $output = $wrapper->buildFinalCommand( $wrapper->command );
+               $this->assertEquals( $expected, $output[0], $desc );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/site/MediaWikiPageNameNormalizerTest.php b/tests/phpunit/unit/includes/site/MediaWikiPageNameNormalizerTest.php
new file mode 100644 (file)
index 0000000..d426306
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+
+use MediaWiki\Site\MediaWikiPageNameNormalizer;
+
+/**
+ * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.27
+ *
+ * @group Site
+ * @group medium
+ *
+ * @author Marius Hoch
+ */
+class MediaWikiPageNameNormalizerTest extends MediaWikiUnitTestCase {
+
+       /**
+        * @dataProvider normalizePageTitleProvider
+        */
+       public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
+               MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
+
+               $normalizer = new MediaWikiPageNameNormalizer(
+                       new MediaWikiPageNameNormalizerTestMockHttp()
+               );
+
+               $this->assertSame(
+                       $expected,
+                       $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
+               );
+       }
+
+       public function normalizePageTitleProvider() {
+               // Response are taken from wikidata and kkwiki using the following API request
+               // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
+               return [
+                       'universe (Q1)' => [
+                               'Q1',
+                               'Q1',
+                               '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
+                               . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
+                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+                               . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
+                       ],
+                       'Q404 redirects to Q395' => [
+                               'Q395',
+                               'Q404',
+                               '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
+                               . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
+                               . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+                               . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
+                       ],
+                       'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
+                               'Д',
+                               'D',
+                               '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
+                               . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
+                               . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
+                               . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
+                               . '"lastrevid":2373618,"length":3501}}}}'
+                       ],
+                       'there is no Q0' => [
+                               false,
+                               'Q0',
+                               '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
+                               . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
+                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
+                       ],
+                       'invalid title' => [
+                               false,
+                               '{{',
+                               '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
+                               . '"invalidreason":"The requested page title contains invalid '
+                               . 'characters: \"{\".","invalid":""}}}}'
+                       ],
+                       'error on get' => [ false, 'ABC', false ]
+               ];
+       }
+
+}
+
+/**
+ * @private
+ * @see Http
+ */
+class MediaWikiPageNameNormalizerTestMockHttp extends Http {
+
+       /**
+        * @var mixed
+        */
+       public static $response;
+
+       public static function get( $url, array $options = [], $caller = __METHOD__ ) {
+               PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
+               PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
+
+               return self::$response;
+       }
+}
diff --git a/tests/phpunit/unit/includes/utils/ZipDirectoryReaderTest.php b/tests/phpunit/unit/includes/utils/ZipDirectoryReaderTest.php
new file mode 100644 (file)
index 0000000..be7e224
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @covers ZipDirectoryReader
+ * NOTE: this test is more like an integration test than a unit test
+ */
+class ZipDirectoryReaderTest extends MediaWikiUnitTestCase {
+
+       protected $zipDir;
+       protected $entries;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->zipDir = __DIR__ . '/../../../data/zip';
+       }
+
+       function zipCallback( $entry ) {
+               $this->entries[] = $entry;
+       }
+
+       function readZipAssertError( $file, $error, $assertMessage ) {
+               $this->entries = [];
+               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+               $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
+       }
+
+       function readZipAssertSuccess( $file, $assertMessage ) {
+               $this->entries = [];
+               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+               $this->assertTrue( $status->isOK(), $assertMessage );
+       }
+
+       public function testEmpty() {
+               $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
+       }
+
+       public function testMultiDisk0() {
+               $this->readZipAssertError( 'split.zip', 'zip-unsupported',
+                       'Split zip error' );
+       }
+
+       public function testNoSignature() {
+               $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
+                       'No signature should give "wrong format" error' );
+       }
+
+       public function testSimple() {
+               $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
+               $this->assertEquals( $this->entries, [ [
+                       'name' => 'Class.class',
+                       'mtime' => '20010115000000',
+                       'size' => 1,
+               ] ] );
+       }
+
+       public function testBadCentralEntrySignature() {
+               $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
+                       'Bad central entry error' );
+       }
+
+       public function testTrailingBytes() {
+               // Due to T40432 this is now zip-wrong-format instead of zip-bad
+               $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
+                       'Trailing bytes error' );
+       }
+
+       public function testWrongCDStart() {
+               $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
+                       'Wrong CD start disk error' );
+       }
+
+       public function testCentralDirectoryGap() {
+               $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
+                       'CD gap error' );
+       }
+
+       public function testCentralDirectoryTruncated() {
+               $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
+                       'CD truncated error (should hit unpack() overrun)' );
+       }
+
+       public function testLooksLikeZip64() {
+               $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
+                       'A file which looks like ZIP64 but isn\'t, should give error' );
+       }
+}
diff --git a/tests/phpunit/unit/languages/LanguageCodeTest.php b/tests/phpunit/unit/languages/LanguageCodeTest.php
deleted file mode 100644 (file)
index f3a7ae4..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-<?php
-
-/**
- * @covers LanguageCode
- * @group Language
- *
- * @author Thiemo Kreuz
- */
-class LanguageCodeTest extends MediaWikiUnitTestCase {
-
-       public function testConstructor() {
-               $instance = new LanguageCode();
-
-               $this->assertInstanceOf( LanguageCode::class, $instance );
-       }
-
-       public function testGetDeprecatedCodeMapping() {
-               $map = LanguageCode::getDeprecatedCodeMapping();
-
-               $this->assertInternalType( 'array', $map );
-               $this->assertContainsOnly( 'string', array_keys( $map ) );
-               $this->assertArrayNotHasKey( '', $map );
-               $this->assertContainsOnly( 'string', $map );
-               $this->assertNotContains( '', $map );
-
-               // Codes special to MediaWiki should never appear in a map of "deprecated" codes
-               $this->assertArrayNotHasKey( 'qqq', $map, 'documentation' );
-               $this->assertNotContains( 'qqq', $map, 'documentation' );
-               $this->assertArrayNotHasKey( 'qqx', $map, 'debug code' );
-               $this->assertNotContains( 'qqx', $map, 'debug code' );
-
-               // Valid language codes that are currently not "deprecated"
-               $this->assertArrayNotHasKey( 'bh', $map, 'family of Bihari languages' );
-               $this->assertArrayNotHasKey( 'no', $map, 'family of Norwegian languages' );
-               $this->assertArrayNotHasKey( 'simple', $map );
-       }
-
-       public function testReplaceDeprecatedCodes() {
-               $this->assertEquals( 'gsw', LanguageCode::replaceDeprecatedCodes( 'als' ) );
-               $this->assertEquals( 'gsw', LanguageCode::replaceDeprecatedCodes( 'gsw' ) );
-               $this->assertEquals( null, LanguageCode::replaceDeprecatedCodes( null ) );
-       }
-
-       /**
-        * test @see LanguageCode::bcp47().
-        * Please note the BCP 47 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 https://tools.ietf.org/html/bcp47
-        * @dataProvider provideLanguageCodes()
-        */
-       public function testBcp47( $code, $expected ) {
-               $this->assertEquals( $expected, LanguageCode::bcp47( $code ),
-                       "Applying BCP 47 standard to '$code'"
-               );
-
-               $code = strtolower( $code );
-               $this->assertEquals( $expected, LanguageCode::bcp47( $code ),
-                       "Applying BCP 47 standard to lower case '$code'"
-               );
-
-               $code = strtoupper( $code );
-               $this->assertEquals( $expected, LanguageCode::bcp47( $code ),
-                       "Applying BCP 47 standard to upper case '$code'"
-               );
-       }
-
-       /**
-        * Array format is ($code, $expected)
-        */
-       public static function provideLanguageCodes() {
-               return [
-                       // Extracted from BCP 47 (list not exhaustive)
-                       # 2.1.1
-                       [ 'en-ca-x-ca', 'en-CA-x-ca' ],
-                       [ 'sgn-be-fr', 'sgn-BE-FR' ],
-                       [ 'az-latn-x-latn', 'az-Latn-x-latn' ],
-                       # 2.2
-                       [ 'sr-Latn-RS', 'sr-Latn-RS' ],
-                       [ 'az-arab-ir', 'az-Arab-IR' ],
-
-                       # 2.2.5
-                       [ 'sl-nedis', 'sl-nedis' ],
-                       [ 'de-ch-1996', 'de-CH-1996' ],
-
-                       # 2.2.6
-                       [
-                               'en-latn-gb-boont-r-extended-sequence-x-private',
-                               'en-Latn-GB-boont-r-extended-sequence-x-private'
-                       ],
-
-                       // Examples from BCP 47 Appendix A
-                       # Simple language subtag:
-                       [ 'DE', 'de' ],
-                       [ 'fR', 'fr' ],
-                       [ 'ja', 'ja' ],
-
-                       # Language subtag plus script subtag:
-                       [ 'zh-hans', 'zh-Hans' ],
-                       [ 'sr-cyrl', 'sr-Cyrl' ],
-                       [ 'sr-latn', 'sr-Latn' ],
-
-                       # Extended language subtags and their primary language subtag
-                       # counterparts:
-                       [ 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ],
-                       [ 'cmn-hans-cn', 'cmn-Hans-CN' ],
-                       [ 'zh-yue-hk', 'zh-yue-HK' ],
-                       [ 'yue-hk', 'yue-HK' ],
-
-                       # Language-Script-Region:
-                       [ 'zh-hans-cn', 'zh-Hans-CN' ],
-                       [ 'sr-latn-RS', 'sr-Latn-RS' ],
-
-                       # Language-Variant:
-                       [ 'sl-rozaj', 'sl-rozaj' ],
-                       [ 'sl-rozaj-biske', 'sl-rozaj-biske' ],
-                       [ 'sl-nedis', 'sl-nedis' ],
-
-                       # Language-Region-Variant:
-                       [ 'de-ch-1901', 'de-CH-1901' ],
-                       [ 'sl-it-nedis', 'sl-IT-nedis' ],
-
-                       # Language-Script-Region-Variant:
-                       [ 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ],
-
-                       # Language-Region:
-                       [ 'de-de', 'de-DE' ],
-                       [ 'en-us', 'en-US' ],
-                       [ 'es-419', 'es-419' ],
-
-                       # Private use subtags:
-                       [ 'de-ch-x-phonebk', 'de-CH-x-phonebk' ],
-                       [ 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ],
-                       /**
-                        * Previous test does not reflect the BCP 47 which states:
-                        *  az-Arab-x-AZE-derbend
-                        * AZE being private, it should be lower case, hence the test above
-                        * should probably be:
-                        * [ 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ],
-                        */
-
-                       # Private use registry values:
-                       [ 'x-whatever', 'x-whatever' ],
-                       [ 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ],
-                       [ 'de-qaaa', 'de-Qaaa' ],
-                       [ 'sr-latn-qm', 'sr-Latn-QM' ],
-                       [ 'sr-qaaa-rs', 'sr-Qaaa-RS' ],
-
-                       # Tags that use extensions
-                       [ 'en-us-u-islamcal', 'en-US-u-islamcal' ],
-                       [ 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ],
-                       [ 'en-a-myext-b-another', 'en-a-myext-b-another' ],
-
-                       # Invalid:
-                       // de-419-DE
-                       // a-DE
-                       // ar-a-aaa-b-bbb-a-ccc
-
-                       # Non-standard and deprecated language codes used by MediaWiki
-                       [ 'als', 'gsw' ],
-                       [ 'bat-smg', 'sgs' ],
-                       [ 'be-x-old', 'be-tarask' ],
-                       [ 'fiu-vro', 'vro' ],
-                       [ 'roa-rup', 'rup' ],
-                       [ 'zh-classical', 'lzh' ],
-                       [ 'zh-min-nan', 'nan' ],
-                       [ 'zh-yue', 'yue' ],
-                       [ 'cbk-zam', 'cbk' ],
-                       [ 'de-formal', 'de-x-formal' ],
-                       [ 'eml', 'egl' ],
-                       [ 'en-rtl', 'en-x-rtl' ],
-                       [ 'es-formal', 'es-x-formal' ],
-                       [ 'hu-formal', 'hu-x-formal' ],
-                       [ 'kk-Arab', 'kk-Arab' ],
-                       [ 'kk-Cyrl', 'kk-Cyrl' ],
-                       [ 'kk-Latn', 'kk-Latn' ],
-                       [ 'map-bms', 'jv-x-bms' ],
-                       [ 'mo', 'ro-Cyrl-MD' ],
-                       [ 'nrm', 'nrf' ],
-                       [ 'nl-informal', 'nl-x-informal' ],
-                       [ 'roa-tara', 'nap-x-tara' ],
-                       [ 'simple', 'en-simple' ],
-                       [ 'sr-ec', 'sr-Cyrl' ],
-                       [ 'sr-el', 'sr-Latn' ],
-                       [ 'zh-cn', 'zh-Hans-CN' ],
-                       [ 'zh-sg', 'zh-Hans-SG' ],
-                       [ 'zh-my', 'zh-Hans-MY' ],
-                       [ 'zh-tw', 'zh-Hant-TW' ],
-                       [ 'zh-hk', 'zh-Hant-HK' ],
-                       [ 'zh-mo', 'zh-Hant-MO' ],
-                       [ 'zh-hans', 'zh-Hans' ],
-                       [ 'zh-hant', 'zh-Hant' ],
-               ];
-       }
-
-}
diff --git a/tests/phpunit/unit/languages/SpecialPageAliasTest.php b/tests/phpunit/unit/languages/SpecialPageAliasTest.php
deleted file mode 100644 (file)
index cce9d0e..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-/**
- * Verifies that special page aliases are valid, with no slashes.
- *
- * @group Language
- * @group SpecialPageAliases
- * @group SystemTest
- * @group medium
- * @todo This should be a structure test
- *
- * @author Katie Filbert < aude.wiki@gmail.com >
- */
-class SpecialPageAliasTest extends \MediaWikiUnitTestCase {
-
-       /**
-        * @coversNothing
-        * @dataProvider validSpecialPageAliasesProvider
-        */
-       public function testValidSpecialPageAliases( $code, $specialPageAliases ) {
-               foreach ( $specialPageAliases as $specialPage => $aliases ) {
-                       foreach ( $aliases as $alias ) {
-                               $msg = "$specialPage alias '$alias' in $code is valid with no slashes";
-                               $this->assertRegExp( '/^[^\/]*$/', $msg );
-                       }
-               }
-       }
-
-       public function validSpecialPageAliasesProvider() {
-               $codes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
-
-               $data = [];
-
-               foreach ( $codes as $code ) {
-                       $specialPageAliases = $this->getSpecialPageAliases( $code );
-
-                       if ( $specialPageAliases !== [] ) {
-                               $data[] = [ $code, $specialPageAliases ];
-                       }
-               }
-
-               return $data;
-       }
-
-       /**
-        * @param string $code
-        *
-        * @return array
-        */
-       protected function getSpecialPageAliases( $code ) {
-               $file = Language::getMessagesFileName( $code );
-
-               if ( is_readable( $file ) ) {
-                       include $file;
-
-                       if ( isset( $specialPageAliases ) && $specialPageAliases !== null ) {
-                               return $specialPageAliases;
-                       }
-               }
-
-               return [];
-       }
-
-}