From: jenkins-bot Date: Thu, 29 Aug 2019 21:00:23 +0000 (+0000) Subject: Merge "Move User::getAllRights to PermissionManager." X-Git-Tag: 1.34.0-rc.0~490 X-Git-Url: https://git.heureux-cyclage.org/?a=commitdiff_plain;h=296e3d4f98d9d277569bd1838ce56799158965c5;hp=49e2aec53ac9d0015dbfc6d14f0787c4e72a3605;p=lhc%2Fweb%2Fwiklou.git Merge "Move User::getAllRights to PermissionManager." --- diff --git a/RELEASE-NOTES-1.34 b/RELEASE-NOTES-1.34 index f7790cb628..e57dacc498 100644 --- a/RELEASE-NOTES-1.34 +++ b/RELEASE-NOTES-1.34 @@ -81,6 +81,7 @@ For notes on 1.33.x and older releases, see HISTORY. password setups and deprecated since 1.24, is now removed. * $wgDBOracleDRCP - If you must use persistent connections, set DBO_PERSISTENT in the 'flags' field for servers in $wgDBServers (or $wgLBFactoryConf). +* $wgMemCachedDebug - Set the cache "debug" field in $wgObjectCaches instead. === New user-facing features in 1.34 === * Special:Mute has been added as a quick way for users to block unwanted emails diff --git a/autoload.php b/autoload.php index 35c9b0a7d5..eb54f7c592 100644 --- a/autoload.php +++ b/autoload.php @@ -1521,9 +1521,12 @@ $wgAutoloadLocalClasses = [ 'UncategorizedTemplatesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedtemplates.php', 'Undelete' => __DIR__ . '/maintenance/undelete.php', 'UnifiedDiffFormatter' => __DIR__ . '/includes/diff/UnifiedDiffFormatter.php', + 'UnknownContent' => __DIR__ . '/includes/content/UnknownContent.php', + 'UnknownContentHandler' => __DIR__ . '/includes/content/UnknownContentHandler.php', 'UnlistedSpecialPage' => __DIR__ . '/includes/specialpage/UnlistedSpecialPage.php', 'UnprotectAction' => __DIR__ . '/includes/actions/UnprotectAction.php', 'UnregisteredLocalFile' => __DIR__ . '/includes/filerepo/file/UnregisteredLocalFile.php', + 'UnsupportedSlotDiffRenderer' => __DIR__ . '/includes/diff/UnsupportedSlotDiffRenderer.php', 'UnusedCategoriesPage' => __DIR__ . '/includes/specials/SpecialUnusedcategories.php', 'UnusedimagesPage' => __DIR__ . '/includes/specials/SpecialUnusedimages.php', 'UnusedtemplatesPage' => __DIR__ . '/includes/specials/SpecialUnusedtemplates.php', diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 93a59190c4..9577a481d3 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -2554,11 +2554,6 @@ $wgPHPSessionHandling = 'enable'; */ $wgSessionPbkdf2Iterations = 10001; -/** - * If enabled, will send MemCached debugging information to $wgDebugLogFile - */ -$wgMemCachedDebug = false; - /** * The list of MemCached servers and port numbers */ diff --git a/includes/EditPage.php b/includes/EditPage.php index d0a5080d0b..e51fc52bd7 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -689,10 +689,6 @@ class EditPage { # checking, etc. if ( $this->formtype == 'initial' || $this->firsttime ) { if ( $this->initialiseForm() === false ) { - $out = $this->context->getOutput(); - if ( $out->getRedirect() === '' ) { // mcrundo hack redirects, don't override it - $this->noSuchSectionPage(); - } return; } @@ -1145,8 +1141,26 @@ class EditPage { $content = $this->getContentObject( false ); # TODO: track content object?! if ( $content === false ) { + $out = $this->context->getOutput(); + if ( $out->getRedirect() === '' ) { // mcrundo hack redirects, don't override it + $this->noSuchSectionPage(); + } + return false; + } + + if ( !$this->isSupportedContentModel( $content->getModel() ) ) { + $modelMsg = $this->getContext()->msg( 'content-model-' . $content->getModel() ); + $modelName = $modelMsg->exists() ? $modelMsg->text() : $content->getModel(); + + $out = $this->context->getOutput(); + $out->showErrorPage( + 'modeleditnotsupported-title', + 'modeleditnotsupported-text', + $modelName + ); return false; } + $this->textbox1 = $this->toEditText( $content ); $user = $this->context->getUser(); diff --git a/includes/OutputPage.php b/includes/OutputPage.php index b2ca53aa18..9af16d39f8 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -3311,12 +3311,10 @@ class OutputPage extends ContextSource { $vars['wgUserVariant'] = $contLang->getPreferredVariant(); } // Same test as SkinTemplate - $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user ) - && ( $title->exists() || $title->quickUserCan( 'create', $user ) ); + $vars['wgIsProbablyEditable'] = $this->userCanEditOrCreate( $user, $title ); - $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle - && $relevantTitle->quickUserCan( 'edit', $user ) - && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) ); + $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle && + $this->userCanEditOrCreate( $user, $relevantTitle ); foreach ( $title->getRestrictionTypes() as $type ) { // Following keys are set in $vars: @@ -3383,6 +3381,21 @@ class OutputPage extends ContextSource { return true; } + /** + * @param User $user + * @param LinkTarget $title + * @return bool + */ + private function userCanEditOrCreate( + User $user, + LinkTarget $title + ) { + $pm = MediaWikiServices::getInstance()->getPermissionManager(); + return $pm->quickUserCan( 'edit', $user, $title ) + && ( $this->getTitle()->exists() || + $pm->quickUserCan( 'create', $user, $title ) ); + } + /** * @return array Array in format "link name or number => 'link html'". */ @@ -3447,11 +3460,7 @@ class OutputPage extends ContextSource { # Universal edit button if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) { - $user = $this->getUser(); - if ( $this->getTitle()->quickUserCan( 'edit', $user ) - && ( $this->getTitle()->exists() || - $this->getTitle()->quickUserCan( 'create', $user ) ) - ) { + if ( $this->userCanEditOrCreate( $this->getUser(), $this->getTitle() ) ) { // Original UniversalEditButton $msg = $this->msg( 'edit' )->text(); $tags['universal-edit-button'] = Html::element( 'link', [ diff --git a/includes/Permissions/PermissionManager.php b/includes/Permissions/PermissionManager.php index f9ad3ebb93..37791d0a14 100644 --- a/includes/Permissions/PermissionManager.php +++ b/includes/Permissions/PermissionManager.php @@ -231,6 +231,25 @@ class PermissionManager { return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) ); } + /** + * A convenience method for calling PermissionManager::userCan + * with PermissionManager::RIGOR_QUICK + * + * Suitable for use for nonessential UI controls in common cases, but + * _not_ for functional access control. + * May provide false positives, but should never provide a false negative. + * + * @see PermissionManager::userCan() + * + * @param string $action + * @param User $user + * @param LinkTarget $page + * @return bool + */ + public function quickUserCan( $action, User $user, LinkTarget $page ) { + return $this->userCan( $action, $user, $page, self::RIGOR_QUICK ); + } + /** * Can $user perform $action on a page? * diff --git a/includes/TemplatesOnThisPageFormatter.php b/includes/TemplatesOnThisPageFormatter.php index bca1ef651a..215e4ec813 100644 --- a/includes/TemplatesOnThisPageFormatter.php +++ b/includes/TemplatesOnThisPageFormatter.php @@ -20,6 +20,7 @@ use MediaWiki\Linker\LinkRenderer; use MediaWiki\Linker\LinkTarget; +use MediaWiki\MediaWikiServices; /** * Handles formatting for the "templates used on this page" @@ -158,11 +159,13 @@ class TemplatesOnThisPageFormatter { * Return a link to the edit page, with the text * saying "view source" if the user can't edit the page * - * @param Title $titleObj + * @param LinkTarget $titleObj * @return string */ - private function buildEditLink( Title $titleObj ) { - if ( $titleObj->quickUserCan( 'edit', $this->context->getUser() ) ) { + private function buildEditLink( LinkTarget $titleObj ) { + if ( MediaWikiServices::getInstance()->getPermissionManager() + ->quickUserCan( 'edit', $this->context->getUser(), $titleObj ) + ) { $linkMsg = 'editlink'; } else { $linkMsg = 'viewsourcelink'; diff --git a/includes/actions/pagers/HistoryPager.php b/includes/actions/pagers/HistoryPager.php index fd2fbd0d99..f178911ab3 100644 --- a/includes/actions/pagers/HistoryPager.php +++ b/includes/actions/pagers/HistoryPager.php @@ -401,8 +401,11 @@ class HistoryPager extends ReverseChronologicalPager { $tools = []; # Rollback and undo links - if ( $prevRev && $this->getTitle()->quickUserCan( 'edit', $user ) ) { - if ( $latest && $this->getTitle()->quickUserCan( 'rollback', $user ) ) { + + if ( $prevRev && $permissionManager->quickUserCan( 'edit', $user, $this->getTitle() ) ) { + if ( $latest && $permissionManager->quickUserCan( 'rollback', + $user, $this->getTitle() ) + ) { // Get a rollback link without the brackets $rollbackLink = Linker::generateRollback( $rev, diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php index dfbc50a23f..3a6d8924ba 100644 --- a/includes/cache/MessageCache.php +++ b/includes/cache/MessageCache.php @@ -165,8 +165,8 @@ class MessageCache { global $wgUser; if ( !$this->mParserOptions ) { - if ( !$wgUser->isSafeToLoad() ) { - // $wgUser isn't unstubbable yet, so don't try to get a + if ( !$wgUser || !$wgUser->isSafeToLoad() ) { + // $wgUser isn't available yet, so don't try to get a // ParserOptions for it. And don't cache this ParserOptions // either. $po = ParserOptions::newFromAnon(); diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php index 78078770b2..0382d7360f 100644 --- a/includes/changes/ChangesList.php +++ b/includes/changes/ChangesList.php @@ -716,7 +716,9 @@ class ChangesList extends ContextSource { /** Check for rollback permissions, disallow special pages, and only * show a link on the top-most revision */ - if ( $title->quickUserCan( 'rollback', $this->getUser() ) ) { + if ( MediaWikiServices::getInstance()->getPermissionManager() + ->quickUserCan( 'rollback', $this->getUser(), $title ) + ) { $rev = new Revision( [ 'title' => $title, 'id' => $rc->mAttribs['rc_this_oldid'], diff --git a/includes/content/UnknownContent.php b/includes/content/UnknownContent.php new file mode 100644 index 0000000000..27199a0038 --- /dev/null +++ b/includes/content/UnknownContent.php @@ -0,0 +1,149 @@ +data = $data; + } + + /** + * @return Content $this + */ + public function copy() { + // UnknownContent is immutable, so no need to copy. + return $this; + } + + /** + * Returns an empty string. + * + * @param int $maxlength + * + * @return string + */ + public function getTextForSummary( $maxlength = 250 ) { + return ''; + } + + /** + * Returns the data size in bytes. + * + * @return int + */ + public function getSize() { + return strlen( $this->data ); + } + + /** + * Returns false. + * + * @param bool|null $hasLinks If it is known whether this content contains links, + * provide this information here, to avoid redundant parsing to find out. + * + * @return bool + */ + public function isCountable( $hasLinks = null ) { + return false; + } + + /** + * @return string data of unknown format and meaning + */ + public function getNativeData() { + return $this->getData(); + } + + /** + * @return string data of unknown format and meaning + */ + public function getData() { + return $this->data; + } + + /** + * Returns an empty string. + * + * @return string The raw text. + */ + public function getTextForSearchIndex() { + return ''; + } + + /** + * Returns false. + */ + public function getWikitextForTransclusion() { + return false; + } + + /** + * Fills the ParserOutput with an error message. + */ + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output + ) { + $msg = wfMessage( 'unsupported-content-model', [ $this->getModel() ] ); + $html = Html::rawElement( 'div', [ 'class' => 'error' ], $msg->inContentLanguage()->parse() ); + $output->setText( $html ); + } + + /** + * Returns false. + */ + public function convert( $toModel, $lossy = '' ) { + return false; + } + + protected function equalsInternal( Content $that ) { + if ( !$that instanceof UnknownContent ) { + return false; + } + + return $this->getData() == $that->getData(); + } + +} diff --git a/includes/content/UnknownContentHandler.php b/includes/content/UnknownContentHandler.php new file mode 100644 index 0000000000..1427e2b3bd --- /dev/null +++ b/includes/content/UnknownContentHandler.php @@ -0,0 +1,114 @@ +getData(); + } + + /** + * Constructs an UnknownContent instance wrapping the given data. + * + * @since 1.21 + * + * @param string $blob serialized content in an unknown format + * @param string|null $format ignored + * + * @return Content The UnknownContent object wrapping $data + */ + public function unserializeContent( $blob, $format = null ) { + return new UnknownContent( $blob, $this->getModelID() ); + } + + /** + * Creates an empty UnknownContent object. + * + * @since 1.21 + * + * @return Content A new UnknownContent object with empty text. + */ + public function makeEmptyContent() { + return $this->unserializeContent( '' ); + } + + /** + * @return false + */ + public function supportsDirectEditing() { + return false; + } + + /** + * @param IContextSource $context + * + * @return SlotDiffRenderer + */ + protected function getSlotDiffRendererInternal( IContextSource $context ) { + return new UnsupportedSlotDiffRenderer( $context ); + } +} diff --git a/includes/db/MWLBFactory.php b/includes/db/MWLBFactory.php index 80eb2f70fb..18030090df 100644 --- a/includes/db/MWLBFactory.php +++ b/includes/db/MWLBFactory.php @@ -66,7 +66,7 @@ abstract class MWLBFactory { * @param array $lbConf Config for LBFactory::__construct() * @param ServiceOptions $options * @param ConfiguredReadOnlyMode $readOnlyMode - * @param BagOStuff $srvCace + * @param BagOStuff $srvCache * @param BagOStuff $mainStash * @param WANObjectCache $wanCache * @return array @@ -76,7 +76,7 @@ abstract class MWLBFactory { array $lbConf, ServiceOptions $options, ConfiguredReadOnlyMode $readOnlyMode, - BagOStuff $srvCace, + BagOStuff $srvCache, BagOStuff $mainStash, WANObjectCache $wanCache ) { @@ -159,7 +159,7 @@ abstract class MWLBFactory { $options->get( 'DBprefix' ) ); - $lbConf = self::injectObjectCaches( $lbConf, $srvCace, $mainStash, $wanCache ); + $lbConf = self::injectObjectCaches( $lbConf, $srvCache, $mainStash, $wanCache ); return $lbConf; } @@ -222,6 +222,11 @@ abstract class MWLBFactory { private static function injectObjectCaches( array $lbConf, BagOStuff $sCache, BagOStuff $mStash, WANObjectCache $wCache ) { + // Fallback if APC style caching is not an option + if ( $sCache instanceof EmptyBagOStuff ) { + $sCache = new HashBagOStuff( [ 'maxKeys' => 100 ] ); + } + // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804) if ( $sCache->getQoS( $sCache::ATTR_EMULATION ) > $sCache::QOS_EMULATION_SQL ) { $lbConf['srvCache'] = $sCache; diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php index 1d3b402076..b8697e53bd 100644 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@ -22,7 +22,6 @@ */ use MediaWiki\MediaWikiServices; -use MediaWiki\Permissions\PermissionManager; use MediaWiki\Revision\RevisionRecord; use MediaWiki\Revision\SlotRecord; use MediaWiki\Storage\NameTableAccessException; @@ -542,8 +541,8 @@ class DifferenceEngine extends ContextSource { $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); - if ( $samePage && $this->mNewPage && $permissionManager->userCan( - 'edit', $user, $this->mNewPage, PermissionManager::RIGOR_QUICK + if ( $samePage && $this->mNewPage && $permissionManager->quickUserCan( + 'edit', $user, $this->mNewPage ) ) { if ( $this->mNewRev->isCurrent() && $permissionManager->userCan( 'rollback', $user, $this->mNewPage @@ -556,8 +555,8 @@ class DifferenceEngine extends ContextSource { } } - if ( !$this->mOldRev->isDeleted( RevisionRecord::DELETED_TEXT ) && - !$this->mNewRev->isDeleted( RevisionRecord::DELETED_TEXT ) + if ( $this->userCanEdit( $this->mOldRev ) && + $this->userCanEdit( $this->mNewRev ) ) { $undoLink = Html::element( 'a', [ 'href' => $this->mNewPage->getLocalURL( [ @@ -766,12 +765,14 @@ class DifferenceEngine extends ContextSource { protected function getMarkPatrolledLinkInfo() { $user = $this->getUser(); $config = $this->getConfig(); + $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); // Prepare a change patrol link, if applicable if ( // Is patrolling enabled and the user allowed to? $config->get( 'UseRCPatrol' ) && - $this->mNewPage && $this->mNewPage->quickUserCan( 'patrol', $user ) && + $this->mNewPage && + $permissionManager->quickUserCan( 'patrol', $user, $this->mNewPage ) && // Only do this if the revision isn't more than 6 hours older // than the Max RC age (6h because the RC might not be cleaned out regularly) RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 ) @@ -804,8 +805,7 @@ class DifferenceEngine extends ContextSource { // Build the link if ( $rcid ) { $this->getOutput()->preventClickjacking(); - if ( MediaWikiServices::getInstance()->getPermissionManager() - ->userHasRight( $user, 'writeapi' ) ) { + if ( $permissionManager->userHasRight( $user, 'writeapi' ) ) { $this->getOutput()->addModules( 'mediawiki.page.patrol.ajax' ); } @@ -900,7 +900,11 @@ class DifferenceEngine extends ContextSource { ) { $out->addParserOutput( $parserOutput, [ 'enableSectionEditLinks' => $this->mNewRev->isCurrent() - && $this->mNewRev->getTitle()->quickUserCan( 'edit', $this->getUser() ), + && MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan( + 'edit', + $this->getUser(), + $this->mNewRev->getTitle() + ) ] ); } } @@ -1500,6 +1504,24 @@ class DifferenceEngine extends ContextSource { return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse(); } + /** + * @param Revision $rev + * @return bool whether the user can see and edit the revision. + */ + private function userCanEdit( Revision $rev ) { + $user = $this->getUser(); + + if ( !$rev->getContentHandler()->supportsDirectEditing() ) { + return false; + } + + if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) { + return false; + } + + return true; + } + /** * Get a header for a specified revision. * @@ -1533,13 +1555,14 @@ class DifferenceEngine extends ContextSource { $header = Linker::linkKnown( $title, $header, [], [ 'oldid' => $rev->getId() ] ); - if ( $rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) { + if ( $this->userCanEdit( $rev ) ) { $editQuery = [ 'action' => 'edit' ]; if ( !$rev->isCurrent() ) { $editQuery['oldid'] = $rev->getId(); } - $key = $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold'; + $key = MediaWikiServices::getInstance()->getPermissionManager() + ->quickUserCan( 'edit', $user, $title ) ? 'editold' : 'viewsourceold'; $msg = $this->msg( $key )->escaped(); $editLink = $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $title, $msg, [], $editQuery ) )->escaped(); diff --git a/includes/diff/SlotDiffRenderer.php b/includes/diff/SlotDiffRenderer.php index 969e0ba7a3..c58502b2b7 100644 --- a/includes/diff/SlotDiffRenderer.php +++ b/includes/diff/SlotDiffRenderer.php @@ -44,7 +44,7 @@ abstract class SlotDiffRenderer { * must have the same content model that was used to obtain this diff renderer. * @param Content|null $oldContent * @param Content|null $newContent - * @return string + * @return string HTML, one or more tags. */ abstract public function getDiff( Content $oldContent = null, Content $newContent = null ); diff --git a/includes/diff/TextSlotDiffRenderer.php b/includes/diff/TextSlotDiffRenderer.php index 510465bd96..935172a11a 100644 --- a/includes/diff/TextSlotDiffRenderer.php +++ b/includes/diff/TextSlotDiffRenderer.php @@ -112,7 +112,7 @@ class TextSlotDiffRenderer extends SlotDiffRenderer { * Diff the text representations of two content objects (or just two pieces of text in general). * @param string $oldText * @param string $newText - * @return string + * @return string HTML, one or more tags. */ public function getTextDiff( $oldText, $newText ) { Assert::parameterType( 'string', $oldText, '$oldText' ); diff --git a/includes/diff/UnsupportedSlotDiffRenderer.php b/includes/diff/UnsupportedSlotDiffRenderer.php new file mode 100644 index 0000000000..db1b868238 --- /dev/null +++ b/includes/diff/UnsupportedSlotDiffRenderer.php @@ -0,0 +1,71 @@ +localizer = $localizer; + } + + /** @inheritDoc */ + public function getDiff( Content $oldContent = null, Content $newContent = null ) { + $this->normalizeContents( $oldContent, $newContent ); + + $oldModel = $oldContent->getModel(); + $newModel = $newContent->getModel(); + + if ( $oldModel !== $newModel ) { + $msg = $this->localizer->msg( 'unsupported-content-diff2', $oldModel, $newModel ); + } else { + $msg = $this->localizer->msg( 'unsupported-content-diff', $oldModel ); + } + + return Html::rawElement( + 'tr', + [], + Html::rawElement( + 'td', + [ 'colspan' => 4, 'class' => 'error' ], + $msg->parse() + ) + ); + } + +} diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php index 60f1607c7e..42e78fffc6 100644 --- a/includes/filerepo/FileRepo.php +++ b/includes/filerepo/FileRepo.php @@ -899,20 +899,15 @@ class FileRepo { } // Resolve source to a storage path if virtual - $srcPath = $this->resolveToStoragePath( $srcPath ); + $srcPath = $this->resolveToStoragePathIfVirtual( $srcPath ); - // Get the appropriate file operation - if ( FileBackend::isStoragePath( $srcPath ) ) { - $opName = 'copy'; - } else { - $opName = 'store'; - } + // Copy the source file to the destination $operations[] = [ - 'op' => $opName, - 'src' => $srcPath, + 'op' => FileBackend::isStoragePath( $srcPath ) ? 'copy' : 'store', + 'src' => $srcPath, // storage path (copy) or local file path (store) 'dst' => $dstPath, - 'overwrite' => $flags & self::OVERWRITE, - 'overwriteSame' => $flags & self::OVERWRITE_SAME, + 'overwrite' => ( $flags & self::OVERWRITE ) ? true : false, + 'overwriteSame' => ( $flags & self::OVERWRITE_SAME ) ? true : false, ]; } @@ -949,7 +944,7 @@ class FileRepo { $path = $this->getZonePath( $zone ) . "/$rel"; } else { // Resolve source to a storage path if virtual - $path = $this->resolveToStoragePath( $path ); + $path = $this->resolveToStoragePathIfVirtual( $path ); } $operations[] = [ 'op' => 'delete', 'src' => $path ]; } @@ -1002,7 +997,7 @@ class FileRepo { public function quickCleanDir( $dir ) { $status = $this->newGood(); $status->merge( $this->backend->clean( - [ 'dir' => $this->resolveToStoragePath( $dir ) ] ) ); + [ 'dir' => $this->resolveToStoragePathIfVirtual( $dir ) ] ) ); return $status; } @@ -1027,10 +1022,10 @@ class FileRepo { if ( $src instanceof FSFile ) { $op = 'store'; } else { - $src = $this->resolveToStoragePath( $src ); + $src = $this->resolveToStoragePathIfVirtual( $src ); $op = FileBackend::isStoragePath( $src ) ? 'copy' : 'store'; } - $dst = $this->resolveToStoragePath( $dst ); + $dst = $this->resolveToStoragePathIfVirtual( $dst ); if ( !isset( $triple[2] ) ) { $headers = []; @@ -1070,7 +1065,7 @@ class FileRepo { foreach ( $paths as $path ) { $operations[] = [ 'op' => 'delete', - 'src' => $this->resolveToStoragePath( $path ), + 'src' => $this->resolveToStoragePathIfVirtual( $path ), 'ignoreMissingSource' => true ]; } @@ -1139,7 +1134,7 @@ class FileRepo { $sources = []; foreach ( $srcPaths as $srcPath ) { // Resolve source to a storage path if virtual - $source = $this->resolveToStoragePath( $srcPath ); + $source = $this->resolveToStoragePathIfVirtual( $srcPath ); $sources[] = $source; // chunk to merge } @@ -1226,7 +1221,7 @@ class FileRepo { $options = $ntuple[3] ?? []; // Resolve source to a storage path if virtual - $srcPath = $this->resolveToStoragePath( $srcPath ); + $srcPath = $this->resolveToStoragePathIfVirtual( $srcPath ); if ( !$this->validateFilename( $dstRel ) ) { throw new MWException( 'Validation error in $dstRel' ); } @@ -1266,27 +1261,17 @@ class FileRepo { // Copy (or move) the source file to the destination if ( FileBackend::isStoragePath( $srcPath ) ) { - if ( $flags & self::DELETE_SOURCE ) { - $operations[] = [ - 'op' => 'move', - 'src' => $srcPath, - 'dst' => $dstPath, - 'overwrite' => true, // replace current - 'headers' => $headers - ]; - } else { - $operations[] = [ - 'op' => 'copy', - 'src' => $srcPath, - 'dst' => $dstPath, - 'overwrite' => true, // replace current - 'headers' => $headers - ]; - } - } else { // FS source path + $operations[] = [ + 'op' => ( $flags & self::DELETE_SOURCE ) ? 'move' : 'copy', + 'src' => $srcPath, + 'dst' => $dstPath, + 'overwrite' => true, // replace current + 'headers' => $headers + ]; + } else { $operations[] = [ 'op' => 'store', - 'src' => $src, // prefer FSFile objects + 'src' => $src, // FSFile (preferred) or local file path 'dst' => $dstPath, 'overwrite' => true, // replace current 'headers' => $headers @@ -1327,7 +1312,7 @@ class FileRepo { * @return Status */ protected function initDirectory( $dir ) { - $path = $this->resolveToStoragePath( $dir ); + $path = $this->resolveToStoragePathIfVirtual( $dir ); list( , $container, ) = FileBackend::splitStoragePath( $path ); $params = [ 'dir' => $path ]; @@ -1357,7 +1342,7 @@ class FileRepo { $status = $this->newGood(); $status->merge( $this->backend->clean( - [ 'dir' => $this->resolveToStoragePath( $dir ) ] ) ); + [ 'dir' => $this->resolveToStoragePathIfVirtual( $dir ) ] ) ); return $status; } @@ -1381,12 +1366,12 @@ class FileRepo { * @return array Map of files and existence flags, or false */ public function fileExistsBatch( array $files ) { - $paths = array_map( [ $this, 'resolveToStoragePath' ], $files ); + $paths = array_map( [ $this, 'resolveToStoragePathIfVirtual' ], $files ); $this->backend->preloadFileStat( [ 'srcs' => $paths ] ); $result = []; foreach ( $files as $key => $file ) { - $path = $this->resolveToStoragePath( $file ); + $path = $this->resolveToStoragePathIfVirtual( $file ); $result[$key] = $this->backend->fileExists( [ 'src' => $path ] ); } @@ -1517,7 +1502,7 @@ class FileRepo { * @return string * @throws MWException */ - protected function resolveToStoragePath( $path ) { + protected function resolveToStoragePathIfVirtual( $path ) { if ( self::isVirtualUrl( $path ) ) { return $this->resolveVirtualUrl( $path ); } @@ -1533,7 +1518,7 @@ class FileRepo { * @return TempFSFile|null Returns null on failure */ public function getLocalCopy( $virtualUrl ) { - $path = $this->resolveToStoragePath( $virtualUrl ); + $path = $this->resolveToStoragePathIfVirtual( $virtualUrl ); return $this->backend->getLocalCopy( [ 'src' => $path ] ); } @@ -1547,7 +1532,7 @@ class FileRepo { * @return FSFile|null Returns null on failure. */ public function getLocalReference( $virtualUrl ) { - $path = $this->resolveToStoragePath( $virtualUrl ); + $path = $this->resolveToStoragePathIfVirtual( $virtualUrl ); return $this->backend->getLocalReference( [ 'src' => $path ] ); } @@ -1578,7 +1563,7 @@ class FileRepo { * @return string|bool False on failure */ public function getFileTimestamp( $virtualUrl ) { - $path = $this->resolveToStoragePath( $virtualUrl ); + $path = $this->resolveToStoragePathIfVirtual( $virtualUrl ); return $this->backend->getFileTimestamp( [ 'src' => $path ] ); } @@ -1590,7 +1575,7 @@ class FileRepo { * @return int|bool False on failure */ public function getFileSize( $virtualUrl ) { - $path = $this->resolveToStoragePath( $virtualUrl ); + $path = $this->resolveToStoragePathIfVirtual( $virtualUrl ); return $this->backend->getFileSize( [ 'src' => $path ] ); } @@ -1602,7 +1587,7 @@ class FileRepo { * @return string|bool */ public function getFileSha1( $virtualUrl ) { - $path = $this->resolveToStoragePath( $virtualUrl ); + $path = $this->resolveToStoragePathIfVirtual( $virtualUrl ); return $this->backend->getFileSha1Base36( [ 'src' => $path ] ); } @@ -1617,7 +1602,7 @@ class FileRepo { * @since 1.27 */ public function streamFileWithStatus( $virtualUrl, $headers = [], $optHeaders = [] ) { - $path = $this->resolveToStoragePath( $virtualUrl ); + $path = $this->resolveToStoragePathIfVirtual( $virtualUrl ); $params = [ 'src' => $path, 'headers' => $headers, 'options' => $optHeaders ]; // T172851: HHVM does not flush the output properly, causing OOM diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php index 54fc251f4b..6143c3188e 100644 --- a/includes/filerepo/file/LocalFile.php +++ b/includes/filerepo/file/LocalFile.php @@ -1923,10 +1923,9 @@ class LocalFile extends File { wfDebugLog( 'imagemove', "Finished moving {$this->name}" ); - // Purge the source and target files... + // Purge the source and target files outside the transaction... $oldTitleFile = $localRepo->newFile( $this->title ); $newTitleFile = $localRepo->newFile( $target ); - // To avoid slow purges in the transaction, move them outside... DeferredUpdates::addUpdate( new AutoCommitUpdate( $this->getRepo()->getMasterDB(), @@ -1934,6 +1933,7 @@ class LocalFile extends File { function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) { $oldTitleFile->purgeEverything(); foreach ( $archiveNames as $archiveName ) { + /** @var OldLocalFile $oldTitleFile */ $oldTitleFile->purgeOldThumbnails( $archiveName ); } $newTitleFile->purgeEverything(); diff --git a/includes/libs/HashRing.php b/includes/libs/HashRing.php index 251fa88bae..f8ab6a3d89 100644 --- a/includes/libs/HashRing.php +++ b/includes/libs/HashRing.php @@ -44,9 +44,9 @@ class HashRing implements Serializable { /** @var int[] Map of (location => UNIX timestamp) */ protected $ejectExpiryByLocation; - /** @var array[] Non-empty list of (float, node name, location name) */ + /** @var array[] Non-empty position-ordered list of (position, location name) */ protected $baseRing; - /** @var array[] Non-empty list of (float, node name, location name) */ + /** @var array[] Non-empty position-ordered list of (position, location name) */ protected $liveRing; /** @var float Number of positions on the ring */ @@ -96,7 +96,7 @@ class HashRing implements Serializable { $this->algo = $algo; $this->weightByLocation = $weightByLocation; $this->ejectExpiryByLocation = $ejections; - $this->baseRing = $this->buildLocationRing( $this->weightByLocation, $this->algo ); + $this->baseRing = $this->buildLocationRing( $this->weightByLocation ); } /** @@ -111,12 +111,13 @@ class HashRing implements Serializable { } /** - * Get the location of an item on the ring, as well as the next locations + * Get the location of an item on the ring followed by the next ring locations * * @param string $item * @param int $limit Maximum number of locations to return * @param int $from One of the RING_* class constants * @return string[] List of locations + * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function getLocations( $item, $limit, $from = self::RING_ALL ) { @@ -128,22 +129,25 @@ class HashRing implements Serializable { throw new InvalidArgumentException( "Invalid ring source specified." ); } - // Locate this item's position on the hash ring - $position = $this->getItemPosition( $item ); - $itemNodeIndex = $this->findNodeIndexForPosition( $position, $ring ); + // Locate the node index for this item's position on the hash ring + $itemIndex = $this->findNodeIndexForPosition( $this->getItemPosition( $item ), $ring ); $locations = []; - $currentIndex = $itemNodeIndex; + $currentIndex = null; while ( count( $locations ) < $limit ) { + if ( $currentIndex === null ) { + $currentIndex = $itemIndex; + } else { + $currentIndex = $this->getNextClockwiseNodeIndex( $currentIndex, $ring ); + if ( $currentIndex === $itemIndex ) { + break; // all nodes visited + } + } $nodeLocation = $ring[$currentIndex][self::KEY_LOCATION]; if ( !in_array( $nodeLocation, $locations, true ) ) { // Ignore other nodes for the same locations already added $locations[] = $nodeLocation; } - $currentIndex = $this->getNextClockwiseNodeIndex( $currentIndex, $ring ); - if ( $currentIndex === $itemNodeIndex ) { - break; // all nodes visited - } } return $locations; @@ -159,18 +163,22 @@ class HashRing implements Serializable { if ( $count === 0 ) { return null; } + + $index = null; $lowPos = 0; $highPos = $count; while ( true ) { - $midPos = intval( ( $lowPos + $highPos ) / 2 ); + $midPos = (int)( ( $lowPos + $highPos ) / 2 ); if ( $midPos === $count ) { - return 0; + $index = 0; + break; } - $midVal = $ring[$midPos][self::KEY_POS]; - $midMinusOneVal = $midPos === 0 ? 0 : $ring[$midPos - 1][self::KEY_POS]; + $midVal = $ring[$midPos][self::KEY_POS]; + $midMinusOneVal = ( $midPos === 0 ) ? 0 : $ring[$midPos - 1][self::KEY_POS]; if ( $position <= $midVal && $position > $midMinusOneVal ) { - return $midPos; + $index = $midPos; + break; } if ( $midVal < $position ) { @@ -180,9 +188,12 @@ class HashRing implements Serializable { } if ( $lowPos > $highPos ) { - return 0; + $index = 0; + break; } } + + return $index; } /** @@ -260,10 +271,9 @@ class HashRing implements Serializable { /** * @param int[] $weightByLocation - * @param string $algo Hashing algorithm * @return array[] */ - private function buildLocationRing( array $weightByLocation, $algo ) { + private function buildLocationRing( array $weightByLocation ) { $locationCount = count( $weightByLocation ); $totalWeight = array_sum( $weightByLocation ); @@ -323,7 +333,14 @@ class HashRing implements Serializable { throw new UnexpectedValueException( __METHOD__ . ": {$this->algo} is < 32 bits." ); } - return (float)sprintf( '%u', unpack( 'V', $octets )[1] ); + $pos = unpack( 'V', $octets )[1]; + if ( $pos < 0 ) { + // Most-significant-bit is set, causing unpack() to return a negative integer due + // to the fact that it returns a signed int. Cast it to an unsigned integer string. + $pos = sprintf( '%u', $pos ); + } + + return (float)$pos; } /** diff --git a/includes/libs/filebackend/FSFileBackend.php b/includes/libs/filebackend/FSFileBackend.php index c05dc286c8..c1a796f75a 100644 --- a/includes/libs/filebackend/FSFileBackend.php +++ b/includes/libs/filebackend/FSFileBackend.php @@ -593,7 +593,7 @@ class FSFileBackend extends FileBackendStore { } elseif ( !$hadError ) { return false; // file does not exist } else { - return null; // failure + return self::UNKNOWN; // failure } } @@ -610,7 +610,7 @@ class FSFileBackend extends FileBackendStore { $exists = is_dir( $dir ); $hadError = $this->untrapWarnings(); - return $hadError ? null : $exists; + return $hadError ? self::UNKNOWN : $exists; } /** @@ -632,7 +632,7 @@ class FSFileBackend extends FileBackendStore { } elseif ( !is_readable( $dir ) ) { $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" ); - return null; // bad permissions? + return self::UNKNOWN; // bad permissions? } return new FSFileBackendDirList( $dir, $params ); @@ -657,7 +657,7 @@ class FSFileBackend extends FileBackendStore { } elseif ( !is_readable( $dir ) ) { $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" ); - return null; // bad permissions? + return self::UNKNOWN; // bad permissions? } return new FSFileBackendFileList( $dir, $params ); diff --git a/includes/libs/filebackend/FileBackend.php b/includes/libs/filebackend/FileBackend.php index 4cacb7af63..905e925c40 100644 --- a/includes/libs/filebackend/FileBackend.php +++ b/includes/libs/filebackend/FileBackend.php @@ -131,6 +131,9 @@ abstract class FileBackend implements LoggerAwareInterface { const ATTR_METADATA = 2; // files can be stored with metadata key/values const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII) + /** @var null Idiom for "could not determine due to I/O errors" */ + const UNKNOWN = null; + /** * Create a new backend instance from configuration. * This should only be called from within FileBackendGroup. @@ -209,7 +212,8 @@ abstract class FileBackend implements LoggerAwareInterface { } /** - * Get the unique backend name. + * Get the unique backend name + * * We may have multiple different backends of the same type. * For example, we can have two Swift backends using different proxies. * @@ -231,6 +235,7 @@ abstract class FileBackend implements LoggerAwareInterface { /** * Alias to getDomainId() + * * @return string * @since 1.20 * @deprecated Since 1.34 Use getDomainId() @@ -1164,21 +1169,34 @@ abstract class FileBackend implements LoggerAwareInterface { abstract public function getFileHttpUrl( array $params ); /** - * Check if a directory exists at a given storage path. - * Backends using key/value stores will check if the path is a - * virtual directory, meaning there are files under the given directory. + * Check if a directory exists at a given storage path + * + * For backends using key/value stores, a directory is said to exist whenever + * there exist any files with paths using the given directory path as a prefix + * followed by a forward slash. For example, if there is a file called + * "mwstore://backend/container/dir/path.svg" then directories are said to exist + * at "mwstore://backend/container" and "mwstore://backend/container/dir". These + * can be thought of as "virtual" directories. + * + * Backends that directly use a filesystem layer might enumerate empty directories. + * The clean() method should always be used when files are deleted or moved if this + * is a concern. This is a trade-off to avoid write amplication/contention on file + * changes or read amplification when calling this method. * * Storage backends with eventual consistency might return stale data. * + * @see FileBackend::clean() + * * @param array $params Parameters include: * - dir : storage directory - * @return bool|null Returns null on failure + * @return bool|null Whether a directory exists or null on failure * @since 1.20 */ abstract public function directoryExists( array $params ); /** - * Get an iterator to list *all* directories under a storage directory. + * Get an iterator to list *all* directories under a storage directory + * * If the directory is of the form "mwstore://backend/container", * then all directories in the container will be listed. * If the directory is of form "mwstore://backend/container/dir", @@ -1189,10 +1207,12 @@ abstract class FileBackend implements LoggerAwareInterface { * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * + * @see FileBackend::directoryExists() + * * @param array $params Parameters include: * - dir : storage directory * - topOnly : only return direct child dirs of the directory - * @return Traversable|array|null Returns null on failure + * @return Traversable|array|null Directory list enumerator null on failure * @since 1.20 */ abstract public function getDirectoryList( array $params ); @@ -1205,9 +1225,11 @@ abstract class FileBackend implements LoggerAwareInterface { * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * + * @see FileBackend::directoryExists() + * * @param array $params Parameters include: * - dir : storage directory - * @return Traversable|array|null Returns null on failure + * @return Traversable|array|null Directory list enumerator or null on failure * @since 1.20 */ final public function getTopDirectoryList( array $params ) { @@ -1215,12 +1237,12 @@ abstract class FileBackend implements LoggerAwareInterface { } /** - * Get an iterator to list *all* stored files under a storage directory. - * If the directory is of the form "mwstore://backend/container", - * then all files in the container will be listed. - * If the directory is of form "mwstore://backend/container/dir", - * then all files under that directory will be listed. - * Results will be storage paths relative to the given directory. + * Get an iterator to list *all* stored files under a storage directory + * + * If the directory is of the form "mwstore://backend/container", then all + * files in the container will be listed. If the directory is of form + * "mwstore://backend/container/dir", then all files under that directory will + * be listed. Results will be storage paths relative to the given directory. * * Storage backends with eventual consistency might return stale data. * @@ -1230,7 +1252,7 @@ abstract class FileBackend implements LoggerAwareInterface { * - dir : storage directory * - topOnly : only return direct child files of the directory (since 1.20) * - adviseStat : set to true if stat requests will be made on the files (since 1.22) - * @return Traversable|array|null Returns null on failure + * @return Traversable|array|null File list enumerator or null on failure */ abstract public function getFileList( array $params ); @@ -1245,7 +1267,7 @@ abstract class FileBackend implements LoggerAwareInterface { * @param array $params Parameters include: * - dir : storage directory * - adviseStat : set to true if stat requests will be made on the files (since 1.22) - * @return Traversable|array|null Returns null on failure + * @return Traversable|array|null File list enumerator or null on failure * @since 1.20 */ final public function getTopFileList( array $params ) { @@ -1283,7 +1305,7 @@ abstract class FileBackend implements LoggerAwareInterface { * @param array $params Parameters include: * - srcs : list of source storage paths * - latest : use the latest available data - * @return bool All requests proceeded without I/O errors (since 1.24) + * @return bool Whether all requests proceeded without I/O errors (since 1.24) * @since 1.23 */ abstract public function preloadFileStat( array $params ); @@ -1526,7 +1548,7 @@ abstract class FileBackend implements LoggerAwareInterface { * * @param string $type One of (attachment, inline) * @param string $filename Suggested file name (should not contain slashes) - * @throws FileBackendError + * @throws InvalidArgumentException * @return string * @since 1.20 */ diff --git a/includes/libs/filebackend/FileBackendStore.php b/includes/libs/filebackend/FileBackendStore.php index aa95ee40bf..9b901dd1d1 100644 --- a/includes/libs/filebackend/FileBackendStore.php +++ b/includes/libs/filebackend/FileBackendStore.php @@ -604,7 +604,7 @@ abstract class FileBackendStore extends FileBackend { $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" ); $stat = $this->getFileStat( $params ); - return ( $stat === null ) ? null : (bool)$stat; // null => failure + return ( $stat === self::UNKNOWN ) ? self::UNKNOWN : (bool)$stat; } final public function getFileTimestamp( array $params ) { @@ -637,7 +637,7 @@ abstract class FileBackendStore extends FileBackend { // cache entries from mass object listings that do not include the SHA-1. In that // case, loading the persistent stat cache will likely yield the SHA-1. if ( - $stat === null || + $stat === self::UNKNOWN || ( $requireSHA1 && is_array( $stat ) && !isset( $stat['sha1'] ) ) ) { $this->primeFileCache( [ $path ] ); // check persistent cache @@ -936,7 +936,7 @@ abstract class FileBackendStore extends FileBackend { $res = true; break; // found one! } elseif ( $exists === null ) { // error? - $res = null; // if we don't find anything, it is indeterminate + $res = self::UNKNOWN; // if we don't find anything, it is indeterminate } } @@ -957,7 +957,7 @@ abstract class FileBackendStore extends FileBackend { final public function getDirectoryList( array $params ) { list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); if ( $dir === null ) { // invalid storage path - return null; + return self::UNKNOWN; } if ( $shard !== null ) { // File listing is confined to a single container/shard @@ -987,7 +987,7 @@ abstract class FileBackendStore extends FileBackend { final public function getFileList( array $params ) { list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); if ( $dir === null ) { // invalid storage path - return null; + return self::UNKNOWN; } if ( $shard !== null ) { // File listing is confined to a single container/shard diff --git a/includes/libs/filebackend/MemoryFileBackend.php b/includes/libs/filebackend/MemoryFileBackend.php index f0cbf3bbcd..88b281e380 100644 --- a/includes/libs/filebackend/MemoryFileBackend.php +++ b/includes/libs/filebackend/MemoryFileBackend.php @@ -148,7 +148,7 @@ class MemoryFileBackend extends FileBackendStore { protected function doGetFileStat( array $params ) { $src = $this->resolveHashKey( $params['src'] ); if ( $src === null ) { - return null; + return false; // invalid path } if ( isset( $this->files[$src] ) ) { diff --git a/includes/libs/filebackend/SwiftFileBackend.php b/includes/libs/filebackend/SwiftFileBackend.php index afd1688c1c..e576c6429e 100644 --- a/includes/libs/filebackend/SwiftFileBackend.php +++ b/includes/libs/filebackend/SwiftFileBackend.php @@ -593,7 +593,7 @@ class SwiftFileBackend extends FileBackendStore { $stat = $this->getContainerStat( $fullCont ); if ( is_array( $stat ) ) { return $status; // already there - } elseif ( $stat === null ) { + } elseif ( $stat === self::UNKNOWN ) { $status->fatal( 'backend-fail-internal', $this->name ); $this->logger->error( __METHOD__ . ': cannot get container stat' ); @@ -832,7 +832,7 @@ class SwiftFileBackend extends FileBackendStore { return ( count( $status->value ) ) > 0; } - return null; // error + return self::UNKNOWN; // error } /** @@ -1401,7 +1401,7 @@ class SwiftFileBackend extends FileBackendStore { if ( !$this->containerStatCache->hasField( $container, 'stat' ) ) { $auth = $this->getAuthentication(); if ( !$auth ) { - return null; + return self::UNKNOWN; } list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [ @@ -1427,7 +1427,7 @@ class SwiftFileBackend extends FileBackendStore { $this->onError( null, __METHOD__, [ 'cont' => $container ], $rerr, $rcode, $rdesc ); - return null; + return self::UNKNOWN; } } @@ -1599,7 +1599,7 @@ class SwiftFileBackend extends FileBackendStore { $stats[$path] = false; continue; // invalid storage path } elseif ( !$auth ) { - $stats[$path] = null; + $stats[$path] = self::UNKNOWN; continue; } @@ -1609,7 +1609,7 @@ class SwiftFileBackend extends FileBackendStore { $stats[$path] = false; continue; // ok, nothing to do } elseif ( !is_array( $cstat ) ) { - $stats[$path] = null; + $stats[$path] = self::UNKNOWN; continue; } @@ -1642,7 +1642,7 @@ class SwiftFileBackend extends FileBackendStore { } elseif ( $rcode === 404 ) { $stat = false; } else { - $stat = null; + $stat = self::UNKNOWN; $this->onError( null, __METHOD__, $params, $rerr, $rcode, $rdesc ); } $stats[$path] = $stat; diff --git a/includes/libs/mime/MSCompoundFileReader.php b/includes/libs/mime/MSCompoundFileReader.php index 8afaa38e02..0383def62c 100644 --- a/includes/libs/mime/MSCompoundFileReader.php +++ b/includes/libs/mime/MSCompoundFileReader.php @@ -182,11 +182,6 @@ class MSCompoundFileReader { return $this->unpack( $block, 0, $struct ); } - private function unpackSector( $sectorNumber, $struct ) { - $offset = $this->sectorOffset( $sectorNumber ); - return $this->unpackOffset( $offset, array_sum( $struct ) ); - } - private function unpack( $block, $offset, $struct ) { $data = []; foreach ( $struct as $key => $length ) { diff --git a/includes/logging/LogEventsList.php b/includes/logging/LogEventsList.php index 66be436da4..4179bd5882 100644 --- a/includes/logging/LogEventsList.php +++ b/includes/logging/LogEventsList.php @@ -220,21 +220,6 @@ class LogEventsList extends ContextSource { ]; } - private function getDefaultQuery() { - if ( !isset( $this->mDefaultQuery ) ) { - $this->mDefaultQuery = $this->getRequest()->getQueryValues(); - unset( $this->mDefaultQuery['title'] ); - unset( $this->mDefaultQuery['dir'] ); - unset( $this->mDefaultQuery['offset'] ); - unset( $this->mDefaultQuery['limit'] ); - unset( $this->mDefaultQuery['order'] ); - unset( $this->mDefaultQuery['month'] ); - unset( $this->mDefaultQuery['year'] ); - } - - return $this->mDefaultQuery; - } - /** * @param array $queryTypes * @return array Form descriptor diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php index ad0f67e590..8ffe824f79 100644 --- a/includes/objectcache/ObjectCache.php +++ b/includes/objectcache/ObjectCache.php @@ -204,9 +204,6 @@ class ObjectCache { if ( !isset( $params['servers'] ) ) { $params['servers'] = $GLOBALS['wgMemCachedServers']; } - if ( !isset( $params['debug'] ) ) { - $params['debug'] = $GLOBALS['wgMemCachedDebug']; - } if ( !isset( $params['persistent'] ) ) { $params['persistent'] = $GLOBALS['wgMemCachedPersistent']; } @@ -393,12 +390,19 @@ class ObjectCache { */ public static function detectLocalServerCache() { if ( function_exists( 'apcu_fetch' ) ) { - return 'apcu'; + // Make sure the APCu methods actually store anything + if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) { + return 'apcu'; + } } elseif ( function_exists( 'apc_fetch' ) ) { - return 'apc'; + // Make sure the APC methods actually store anything + if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) { + return 'apc'; + } } elseif ( function_exists( 'wincache_ucache_get' ) ) { return 'wincache'; } + return CACHE_NONE; } } diff --git a/includes/page/Article.php b/includes/page/Article.php index fcfb83de69..d8cd1c5bc8 100644 --- a/includes/page/Article.php +++ b/includes/page/Article.php @@ -635,8 +635,9 @@ class Article implements Page { if ( $outputPage->isPrintable() ) { $parserOptions->setIsPrintable( true ); $poOptions['enableSectionEditLinks'] = false; - } elseif ( $this->viewIsRenderAction - || !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) + } elseif ( $this->viewIsRenderAction || !$this->isCurrent() || + !MediaWikiServices::getInstance()->getPermissionManager() + ->quickUserCan( 'edit', $user, $this->getTitle() ) ) { $poOptions['enableSectionEditLinks'] = false; } @@ -1181,7 +1182,8 @@ class Article implements Page { $title = $this->getTitle(); $rc = false; - if ( !$title->quickUserCan( 'patrol', $user ) + if ( !MediaWikiServices::getInstance()->getPermissionManager() + ->quickUserCan( 'patrol', $user, $title ) || !( $wgUseRCPatrol || $wgUseNPPatrol || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) ) ) { @@ -1450,6 +1452,7 @@ class Article implements Page { # Show error message $oldid = $this->getOldID(); + $pm = MediaWikiServices::getInstance()->getPermissionManager(); if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) { // use fake Content object for system message $parserOptions = ParserOptions::newCanonical( 'canonical' ); @@ -1457,8 +1460,8 @@ class Article implements Page { } else { if ( $oldid ) { $text = wfMessage( 'missing-revision', $oldid )->plain(); - } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() ) - && $title->quickUserCan( 'edit', $this->getContext()->getUser() ) + } elseif ( $pm->quickUserCan( 'create', $this->getContext()->getUser(), $title ) && + $pm->quickUserCan( 'edit', $this->getContext()->getUser(), $title ) ) { $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon'; $text = wfMessage( $message )->plain(); diff --git a/includes/page/ImageHistoryList.php b/includes/page/ImageHistoryList.php index 2de82bf760..dc755410e1 100644 --- a/includes/page/ImageHistoryList.php +++ b/includes/page/ImageHistoryList.php @@ -118,6 +118,7 @@ class ImageHistoryList extends ContextSource { public function imageHistoryLine( $iscur, $file ) { $user = $this->getUser(); $lang = $this->getLanguage(); + $pm = MediaWikiServices::getInstance()->getPermissionManager(); $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() ); $img = $iscur ? $file->getName() : $file->getArchiveName(); $userId = $file->getUser( 'id' ); @@ -128,9 +129,7 @@ class ImageHistoryList extends ContextSource { $row = $selected = ''; // Deletion link - if ( $local && ( MediaWikiServices::getInstance() - ->getPermissionManager() - ->userHasAnyRight( $user, 'delete', 'deletedhistory' ) ) + if ( $local && ( $pm->userHasAnyRight( $user, 'delete', 'deletedhistory' ) ) ) { $row .= ''; # Link to remove from history @@ -173,8 +172,8 @@ class ImageHistoryList extends ContextSource { $row .= ''; if ( $iscur ) { $row .= $this->msg( 'filehist-current' )->escaped(); - } elseif ( $local && $this->title->quickUserCan( 'edit', $user ) - && $this->title->quickUserCan( 'upload', $user ) + } elseif ( $local && $pm->quickUserCan( 'edit', $user, $this->title ) + && $pm->quickUserCan( 'upload', $user, $this->title ) ) { if ( $file->isDeleted( File::DELETED_FILE ) ) { $row .= $this->msg( 'filehist-revert' )->escaped(); diff --git a/includes/page/ImagePage.php b/includes/page/ImagePage.php index 4f08995212..2e43e8c53b 100644 --- a/includes/page/ImagePage.php +++ b/includes/page/ImagePage.php @@ -748,7 +748,8 @@ EOT return; } - $canUpload = $this->getTitle()->quickUserCan( 'upload', $this->getContext()->getUser() ); + $canUpload = MediaWikiServices::getInstance()->getPermissionManager() + ->quickUserCan( 'upload', $this->getContext()->getUser(), $this->getTitle() ); if ( $canUpload && UploadBase::userCanReUpload( $this->getContext()->getUser(), $this->mPage->getFile() ) diff --git a/includes/skins/Skin.php b/includes/skins/Skin.php index 212ac47157..b0d067820a 100644 --- a/includes/skins/Skin.php +++ b/includes/skins/Skin.php @@ -461,7 +461,9 @@ abstract class Skin extends ContextSource { $type = 'ns-subject'; } // T208315: add HTML class when the user can edit the page - if ( $title->quickUserCan( 'edit', $user ) ) { + if ( MediaWikiServices::getInstance()->getPermissionManager() + ->quickUserCan( 'edit', $user, $title ) + ) { $type .= ' mw-editable'; } } @@ -725,14 +727,17 @@ abstract class Skin extends ContextSource { $action = $this->getRequest()->getVal( 'action', 'view' ); $title = $this->getTitle(); $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); + $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); if ( ( !$title->exists() || $action == 'history' ) && - $title->quickUserCan( 'deletedhistory', $this->getUser() ) + $permissionManager->quickUserCan( 'deletedhistory', $this->getUser(), $title ) ) { $n = $title->isDeleted(); if ( $n ) { - if ( $this->getTitle()->quickUserCan( 'undelete', $this->getUser() ) ) { + if ( $permissionManager->quickUserCan( 'undelete', + $this->getUser(), $this->getTitle() ) + ) { $msg = 'thisisdeleted'; } else { $msg = 'viewdeleted'; diff --git a/includes/skins/SkinTemplate.php b/includes/skins/SkinTemplate.php index aeca0162c1..3e8972c0c0 100644 --- a/includes/skins/SkinTemplate.php +++ b/includes/skins/SkinTemplate.php @@ -884,6 +884,7 @@ class SkinTemplate extends Skin { $out = $this->getOutput(); $request = $this->getRequest(); $user = $this->getUser(); + $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); $content_navigation = [ 'namespaces' => [], @@ -895,7 +896,7 @@ class SkinTemplate extends Skin { // parameters $action = $request->getVal( 'action', 'view' ); - $userCanRead = $title->quickUserCan( 'read', $user ); + $userCanRead = $permissionManager->quickUserCan( 'read', $user, $title ); // Avoid PHP 7.1 warning of passing $this by reference $skinTemplate = $this; @@ -965,8 +966,9 @@ class SkinTemplate extends Skin { } // Checks if user can edit the current page if it exists or create it otherwise - if ( $title->quickUserCan( 'edit', $user ) - && ( $title->exists() || $title->quickUserCan( 'create', $user ) ) + if ( $permissionManager->quickUserCan( 'edit', $user, $title ) && + ( $title->exists() || + $permissionManager->quickUserCan( 'create', $user, $title ) ) ) { // Builds CSS class for talk page links $isTalkClass = $isTalk ? ' istalk' : ''; @@ -1031,7 +1033,7 @@ class SkinTemplate extends Skin { 'href' => $title->getLocalURL( 'action=history' ), ]; - if ( $title->quickUserCan( 'delete', $user ) ) { + if ( $permissionManager->quickUserCan( 'delete', $user, $title ) ) { $content_navigation['actions']['delete'] = [ 'class' => ( $onPage && $action == 'delete' ) ? 'selected' : false, 'text' => wfMessageFallback( "$skname-action-delete", 'delete' ) @@ -1040,7 +1042,7 @@ class SkinTemplate extends Skin { ]; } - if ( $title->quickUserCan( 'move', $user ) ) { + if ( $permissionManager->quickUserCan( 'move', $user, $title ) ) { $moveTitle = SpecialPage::getTitleFor( 'Movepage', $title->getPrefixedDBkey() ); $content_navigation['actions']['move'] = [ 'class' => $this->getTitle()->isSpecial( 'Movepage' ) ? 'selected' : false, @@ -1051,13 +1053,14 @@ class SkinTemplate extends Skin { } } else { // article doesn't exist or is deleted - if ( $title->quickUserCan( 'deletedhistory', $user ) ) { + if ( $permissionManager->quickUserCan( 'deletedhistory', $user, $title ) ) { $n = $title->isDeleted(); if ( $n ) { $undelTitle = SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() ); // If the user can't undelete but can view deleted // history show them a "View .. deleted" tab instead. - $msgKey = $title->quickUserCan( 'undelete', $user ) ? 'undelete' : 'viewdeleted'; + $msgKey = $permissionManager->quickUserCan( 'undelete', + $user, $title ) ? 'undelete' : 'viewdeleted'; $content_navigation['actions']['undelete'] = [ 'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false, 'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" ) @@ -1068,9 +1071,10 @@ class SkinTemplate extends Skin { } } - if ( $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() && - MediaWikiServices::getInstance()->getPermissionManager() - ->getNamespaceRestrictionLevels( $title->getNamespace(), $user ) !== [ '' ] + if ( $permissionManager->quickUserCan( 'protect', $user, $title ) && + $title->getRestrictionTypes() && + $permissionManager->getNamespaceRestrictionLevels( $title->getNamespace(), + $user ) !== [ '' ] ) { $mode = $title->isProtected() ? 'unprotect' : 'protect'; $content_navigation['actions'][$mode] = [ @@ -1082,9 +1086,8 @@ class SkinTemplate extends Skin { } // Checks if the user is logged in - if ( $this->loggedin && MediaWikiServices::getInstance() - ->getPermissionManager() - ->userHasAllRights( $user, 'viewmywatchlist', 'editmywatchlist' ) + if ( $this->loggedin && $permissionManager->userHasAllRights( $user, + 'viewmywatchlist', 'editmywatchlist' ) ) { /** * The following actions use messages which, if made particular to diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php index da34d81af9..85f65bbe5c 100644 --- a/includes/specials/SpecialMovepage.php +++ b/includes/specials/SpecialMovepage.php @@ -197,7 +197,8 @@ class MovePageForm extends UnlistedSpecialPage { } if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'articleexists' - && $newTitle->quickUserCan( 'delete', $user ) + && MediaWikiServices::getInstance()->getPermissionManager() + ->quickUserCan( 'delete', $user, $newTitle ) ) { $out->wrapWikiMsg( "
\n$1\n
\n", diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index ad045e43f3..2ae4afcb2e 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -518,7 +518,8 @@ class SpecialSearch extends SpecialPage { $messageName = 'searchmenu-exists'; $linkClass = 'mw-search-exists'; } elseif ( ContentHandler::getForTitle( $title )->supportsDirectEditing() - && $title->quickUserCan( 'create', $this->getUser() ) + && MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan( 'create', + $this->getUser(), $title ) ) { $messageName = 'searchmenu-new'; } diff --git a/includes/specials/pagers/ContribsPager.php b/includes/specials/pagers/ContribsPager.php index d62951cb7e..b80a584fd2 100644 --- a/includes/specials/pagers/ContribsPager.php +++ b/includes/specials/pagers/ContribsPager.php @@ -439,6 +439,16 @@ class ContribsPager extends RangeChronologicalPager { return $this->tagFilter; } + /** + * @deprecated since 1.34, redundant. + * + * @return string "users" + */ + public function getContribs() { + // Brought back for backwards compatibility, see T231540. + return 'users'; + } + /** * @return string */ @@ -573,6 +583,7 @@ class ContribsPager extends RangeChronologicalPager { $attribs = []; $linkRenderer = $this->getLinkRenderer(); + $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); $page = null; // Create a title for the revision if possible @@ -598,8 +609,9 @@ class ContribsPager extends RangeChronologicalPager { $topmarktext .= '' . $this->messages['uctop'] . ''; $classes[] = 'mw-contributions-current'; # Add rollback link - if ( !$row->page_is_new && $page->quickUserCan( 'rollback', $user ) - && $page->quickUserCan( 'edit', $user ) + if ( !$row->page_is_new && + $permissionManager->quickUserCan( 'rollback', $user, $page ) && + $permissionManager->quickUserCan( 'edit', $user, $page ) ) { $this->preventClickjacking(); $topmarktext .= ' ' . Linker::generateRollback( $rev, $this->getContext(), diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 106d6a7dc6..3cb9c66628 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -759,6 +759,8 @@ "nocreate-loggedin": "You do not have permission to create new pages.", "sectioneditnotsupported-title": "Section editing not supported", "sectioneditnotsupported-text": "Section editing is not supported in this page.", + "modeleditnotsupported-title": "Editing not supported", + "modeleditnotsupported-text": "Editing is not supported for content model $1.", "permissionserrors": "Permission error", "permissionserrorstext": "You do not have permission to do that, for the following {{PLURAL:$1|reason|reasons}}:", "permissionserrorstext-withaction": "You do not have permission to $2, for the following {{PLURAL:$1|reason|reasons}}:", @@ -798,6 +800,9 @@ "content-model-json": "JSON", "content-json-empty-object": "Empty object", "content-json-empty-array": "Empty array", + "unsupported-content-model": "Warning: Content model $1 is not supported on this wiki.", + "unsupported-content-diff": "Diffs are not supported for content model $1.", + "unsupported-content-diff2": "Diffs between the content models $1 and $2 are not supported on this wiki.", "deprecated-self-close-category": "Pages using invalid self-closed HTML tags", "deprecated-self-close-category-desc": "The page contains invalid self-closed HTML tags, such as <b/> or <span/>. The behavior of these will change soon to be consistent with the HTML5 specification, so their use in wikitext is deprecated.", "duplicate-args-warning": "Warning: [[:$1]] is calling [[:$2]] with more than one value for the \"$3\" parameter. Only the last value provided will be used.", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 5fdcb7dc37..03c1da458b 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -968,6 +968,8 @@ "nocreate-loggedin": "Used as error message.\n\nSee also:\n* {{msg-mw|Nocreatetext}}", "sectioneditnotsupported-title": "Page title of special page, which presumably appears when someone tries to edit a section, and section editing is disabled. Explanation of section editing on [[meta:Help:Section_editing#Section_editing|meta]].", "sectioneditnotsupported-text": "I think this is the text of an error message, which presumably appears when someone tries to edit a section, and section editing is disabled. Explanation of section editing on [[meta:Help:Section_editing#Section_editing|meta]].", + "modeleditnotsupported-title": "Page title used on the edit page when editing is not supported for the page's content model.", + "modeleditnotsupported-text": "Error message show on the edit page when editing is not supported for the page's content model..\n\nParameters:\n* $1 - the name of the content model.", "permissionserrors": "Used as title of error message.\n\nSee also:\n* {{msg-mw|loginreqtitle}}\n{{Identical|Permission error}}", "permissionserrorstext": "This message is \"without action\" version of {{msg-mw|Permissionserrorstext-withaction}}.\n\nParameters:\n* $1 - the number of reasons that were found why ''the action'' cannot be performed", "permissionserrorstext-withaction": "This message is \"with action\" version of {{msg-mw|Permissionserrorstext}}.\n\nParameters:\n* $1 - the number of reasons that were found why the action cannot be performed\n* $2 - one of the action-* messages (for example {{msg-mw|action-edit}}) or other such messages tagged with {{tl|doc-action}} in their documentation\n\nPlease report at [[Support]] if you are unable to properly translate this message. Also see [[phab:T16246]] (now closed) for background.", @@ -1007,6 +1009,9 @@ "content-model-json": "{{optional}}\nName for the JSON content model, used when decribing what type of content a page contains.\n\nThis message is substituted in:\n*{{msg-mw|Bad-target-model}}\n*{{msg-mw|Content-not-allowed-here}}\n{{identical|JSON}}", "content-json-empty-object": "Used to represent an object with no properties on a JSON content model page.", "content-json-empty-array": "Used to represent an array with no values on a JSON content model page.", + "unsupported-content-model": "Warning shown when trying to display content with an unknown model.\n\nParameters:\n* $1 - the technical name of the content model.", + "unsupported-content-diff": "Warning shown when trying to display a diff between content with a model that does not support diffing (perhaps because it's an unknown model).\n\nParameters:\n* $1 - the technical name of the model of the content", + "unsupported-content-diff2": "Warning shown when trying to display a diff between content that uses models that do not support diffing with each other.\n\nParameters:\n* $1 - the technical name of the model of the old content\n* $2 - the technical name of the model of the new content.", "deprecated-self-close-category": "This message is used as a category name for a [[mw:Special:MyLanguage/Help:Tracking categories|tracking category]] where pages are placed automatically if they contain invalid self-closed HTML tags, such as <b/> or <span/>. The behavior of these will change soon to be consistent with the HTML5 specification, so their use in wikitext is deprecated.", "deprecated-self-close-category-desc": "Invalid self-closed HTML tag category description. Shown on [[Special:TrackingCategories]].\n\nSee also:\n* {{msg-mw|deprecated-self-close-category}}", "duplicate-args-warning": "If a page calls a template and specifies the same argument more than once, such as {{foo|bar=1|bar=2}} or {{foo|bar|1=baz}}, this warning is displayed when previewing.\n\nParameters:\n* $1 - The calling page\n* $2 - The called template\n* $3 - The name of the duplicated argument", diff --git a/package-lock.json b/package-lock.json index 1def4bd2fa..0b49359043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2105,10 +2105,13 @@ } }, "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", - "dev": true + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", + "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.0.0" + } }, "eslint-visitor-keys": { "version": "1.0.0", diff --git a/tests/common/TestsAutoLoader.php b/tests/common/TestsAutoLoader.php index 9f1d67be16..6cd7811ce0 100644 --- a/tests/common/TestsAutoLoader.php +++ b/tests/common/TestsAutoLoader.php @@ -224,6 +224,9 @@ $wgAutoloadClasses += [ # tests/phpunit/unit/includes/filebackend 'FileBackendGroupTestTrait' => "$testDir/phpunit/unit/includes/filebackend/FileBackendGroupTestTrait.php", + # tests/phpunit/unit/includes/language + 'LanguageFallbackTestTrait' => "$testDir/phpunit/unit/includes/language/LanguageFallbackTestTrait.php", + # tests/phpunit/unit/includes/libs/filebackend/fsfile 'TempFSFileTestTrait' => "$testDir/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTestTrait.php", diff --git a/tests/phpunit/MediaWikiIntegrationTestCase.php b/tests/phpunit/MediaWikiIntegrationTestCase.php index 33518ef929..83f27e847d 100644 --- a/tests/phpunit/MediaWikiIntegrationTestCase.php +++ b/tests/phpunit/MediaWikiIntegrationTestCase.php @@ -584,24 +584,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { $this->tmpFiles = array_merge( $this->tmpFiles, (array)$files ); } - // @todo Make const when we no longer support HHVM (T192166) - private static $namespaceAffectingSettings = [ - 'wgAllowImageMoving', - 'wgCanonicalNamespaceNames', - 'wgCapitalLinkOverrides', - 'wgCapitalLinks', - 'wgContentNamespaces', - 'wgExtensionMessagesFiles', - 'wgExtensionNamespaces', - 'wgExtraNamespaces', - 'wgExtraSignatureNamespaces', - 'wgNamespaceContentModels', - 'wgNamespaceProtection', - 'wgNamespacesWithSubpages', - 'wgNonincludableNamespaces', - 'wgRestrictionLevels', - ]; - protected function tearDown() { global $wgRequest, $wgSQLMode; @@ -648,12 +630,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { foreach ( $this->iniSettings as $name => $value ) { ini_set( $name, $value ); } - if ( - array_intersect( self::$namespaceAffectingSettings, array_keys( $this->mwGlobals ) ) || - array_intersect( self::$namespaceAffectingSettings, $this->mwGlobalsToUnset ) - ) { - $this->resetNamespaces(); - } $this->mwGlobals = []; $this->mwGlobalsToUnset = []; $this->restoreLoggers(); @@ -742,7 +718,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { ); if ( $name === 'ContentLanguage' ) { - $this->doSetMwGlobals( [ 'wgContLang' => $this->localServices->getContentLanguage() ] ); + $this->setMwGlobals( [ 'wgContLang' => $this->localServices->getContentLanguage() ] ); } } @@ -753,9 +729,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { * The key is added to the array of globals that will be reset afterwards * in the tearDown(). * - * It may be necessary to call resetServices() to allow any changed configuration variables - * to take effect on services that get initialized based on these variables. - * * @par Example * @code * protected function setUp() { @@ -779,8 +752,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { * @param mixed|null $value Value to set the global to (ignored * if an array is given as first argument). * - * @note To allow changes to global variables to take effect on global service instances, - * call resetServices(). + * @note This will call resetServices(). * * @since 1.21 */ @@ -789,29 +761,13 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { $pairs = [ $pairs => $value ]; } - if ( isset( $pairs['wgContLang'] ) ) { - throw new MWException( - 'No setting $wgContLang, use setContentLang() or setService( \'ContentLanguage\' )' - ); - } - - $this->doSetMwGlobals( $pairs, $value ); - } - - /** - * An internal method that allows setService() to set globals that tests are not supposed to - * touch. - */ - private function doSetMwGlobals( $pairs, $value = null ) { $this->doStashMwGlobals( array_keys( $pairs ) ); foreach ( $pairs as $key => $value ) { $GLOBALS[$key] = $value; } - if ( array_intersect( self::$namespaceAffectingSettings, array_keys( $pairs ) ) ) { - $this->resetNamespaces(); - } + $this->resetServices(); } /** @@ -826,23 +782,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { ini_set( $name, $value ); } - /** - * Must be called whenever namespaces are changed, e.g., $wgExtraNamespaces is altered. - * Otherwise old namespace data will lurk and cause bugs. - */ - private function resetNamespaces() { - if ( !$this->localServices ) { - throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' ); - } - - if ( $this->localServices !== MediaWikiServices::getInstance() ) { - throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices ' - . 'instance has been replaced by test code.' ); - } - - Language::clearCaches(); - } - /** * Check if we can back up a value by performing a shallow copy. * Values which fail this test are copied recursively. @@ -939,16 +878,12 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { * Useful for setting some entries in a configuration array, instead of * setting the entire array. * - * It may be necessary to call resetServices() to allow any changed configuration variables - * to take effect on services that get initialized based on these variables. - * * @param string $name The name of the global, as in wgFooBar * @param array $values The array containing the entries to set in that global * * @throws MWException If the designated global is not an array. * - * @note To allow changes to global variables to take effect on global service instances, - * call resetServices(). + * @note This will call resetServices(). * * @since 1.21 */ @@ -998,6 +933,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { } self::resetGlobalParser(); + Language::clearCaches(); } /** @@ -1182,16 +1118,11 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { */ public function setContentLang( $lang ) { if ( $lang instanceof Language ) { - $this->setMwGlobals( 'wgLanguageCode', $lang->getCode() ); // Set to the exact object requested $this->setService( 'ContentLanguage', $lang ); + $this->setMwGlobals( 'wgLanguageCode', $lang->getCode() ); } else { $this->setMwGlobals( 'wgLanguageCode', $lang ); - // Let the service handler make up the object. Avoid calling setService(), because if - // we do, overrideMwServices() will complain if it's called later on. - $services = MediaWikiServices::getInstance(); - $services->resetServiceForTesting( 'ContentLanguage' ); - $this->doSetMwGlobals( [ 'wgContLang' => $services->getContentLanguage() ] ); } } @@ -1202,6 +1133,8 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { * or three values to set a single permission, like * $this->setGroupPermissions( '*', 'read', false ); * + * @note This will call resetServices(). + * * @since 1.31 * @param array|string $newPerms Either an array of permissions to change, * in which case the next two parameters are ignored; or a single string @@ -1224,11 +1157,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { } $this->setMwGlobals( 'wgGroupPermissions', $newPermissions ); - - // Reset services so they pick up the new permissions. - // Resetting just PermissionManager is not sufficient, since other services may - // have the old instance of PermissionManager injected. - $this->resetServices(); } /** @@ -2422,6 +2350,9 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { /** * Create a temporary hook handler which will be reset by tearDown. * This replaces other handlers for the same hook. + * + * @note This will call resetServices(). + * * @param string $hookName Hook name * @param mixed $handler Value suitable for a hook handler * @since 1.28 diff --git a/tests/phpunit/MediaWikiTestCaseTrait.php b/tests/phpunit/MediaWikiTestCaseTrait.php index 77d7c04482..f047d826b5 100644 --- a/tests/phpunit/MediaWikiTestCaseTrait.php +++ b/tests/phpunit/MediaWikiTestCaseTrait.php @@ -17,4 +17,16 @@ trait MediaWikiTestCaseTrait { ...array_map( [ $this, 'matches' ], $values ) ) ); } + + /** + * Return a PHPUnit mock that is expected to never have any methods called on it. + * + * @param string $type + * @return object + */ + protected function createNoOpMock( $type ) { + $mock = $this->createMock( $type ); + $mock->expects( $this->never() )->method( $this->anything() ); + return $mock; + } } diff --git a/tests/phpunit/MediaWikiUnitTestCase.php b/tests/phpunit/MediaWikiUnitTestCase.php index ccf3357f63..edd8195991 100644 --- a/tests/phpunit/MediaWikiUnitTestCase.php +++ b/tests/phpunit/MediaWikiUnitTestCase.php @@ -72,4 +72,34 @@ abstract class MediaWikiUnitTestCase extends TestCase { global $wgHooks; $wgHooks[$hookName] = [ $handler ]; } + + protected function getMockMessage( $text, ...$params ) { + if ( isset( $params[0] ) && is_array( $params[0] ) ) { + $params = $params[0]; + } + + $msg = $this->getMockBuilder( Message::class ) + ->disableOriginalConstructor() + ->setMethods( [] ) + ->getMock(); + + $msg->method( 'toString' )->willReturn( $text ); + $msg->method( '__toString' )->willReturn( $text ); + $msg->method( 'text' )->willReturn( $text ); + $msg->method( 'parse' )->willReturn( $text ); + $msg->method( 'plain' )->willReturn( $text ); + $msg->method( 'parseAsBlock' )->willReturn( $text ); + $msg->method( 'escaped' )->willReturn( $text ); + + $msg->method( 'title' )->willReturn( $msg ); + $msg->method( 'inLanguage' )->willReturn( $msg ); + $msg->method( 'inContentLanguage' )->willReturn( $msg ); + $msg->method( 'useDatabase' )->willReturn( $msg ); + $msg->method( 'setContext' )->willReturn( $msg ); + + $msg->method( 'exists' )->willReturn( true ); + $msg->method( 'content' )->willReturn( new MessageContent( $msg ) ); + + return $msg; + } } diff --git a/tests/phpunit/includes/ActorMigrationTest.php b/tests/phpunit/includes/ActorMigrationTest.php index 40c45dc683..46c0c42a48 100644 --- a/tests/phpunit/includes/ActorMigrationTest.php +++ b/tests/phpunit/includes/ActorMigrationTest.php @@ -658,7 +658,6 @@ class ActorMigrationTest extends MediaWikiLangTestCase { // for User::getActorId() 'wgActorTableSchemaMigrationStage' => $stage ] ); - $this->overrideMwServices(); $user = $this->getMutableTestUser()->getUser(); $userIdentity = $this->getMock( UserIdentity::class ); diff --git a/tests/phpunit/includes/ContentSecurityPolicyTest.php b/tests/phpunit/includes/ContentSecurityPolicyTest.php index 5d6c0677bd..beddfe8a1b 100644 --- a/tests/phpunit/includes/ContentSecurityPolicyTest.php +++ b/tests/phpunit/includes/ContentSecurityPolicyTest.php @@ -41,7 +41,6 @@ class ContentSecurityPolicyTest extends MediaWikiTestCase { // Note, there are some obscure globals which // could affect the results which aren't included above. - $this->overrideMwServices(); $context = RequestContext::getMain(); $resp = $context->getRequest()->response(); $conf = $context->getConfig(); diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php index 660734e7c4..6b650cde58 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php @@ -120,9 +120,6 @@ class GlobalTest extends MediaWikiTestCase { fwrite( $f, 'Message' ); fclose( $f ); - // Reset the service to avoid cached results - $this->overrideMwServices(); - $this->assertTrue( wfReadOnly() ); $this->assertTrue( wfReadOnly() ); # Check cached } @@ -137,7 +134,6 @@ class GlobalTest extends MediaWikiTestCase { $this->setMwGlobals( [ 'wgReadOnly' => 'reason' ] ); - $this->overrideMwServices(); $this->assertSame( 'reason', wfReadOnlyReason() ); } @@ -321,33 +317,35 @@ class GlobalTest extends MediaWikiTestCase { ] ); $this->setLogger( 'wfDebug', new LegacyLogger( 'wfDebug' ) ); + unlink( $debugLogFile ); wfDebug( "This is a normal string" ); $this->assertEquals( "This is a normal string\n", file_get_contents( $debugLogFile ) ); - unlink( $debugLogFile ); + unlink( $debugLogFile ); wfDebug( "This is nöt an ASCII string" ); $this->assertEquals( "This is nöt an ASCII string\n", file_get_contents( $debugLogFile ) ); - unlink( $debugLogFile ); + unlink( $debugLogFile ); wfDebug( "\00305This has böth UTF and control chars\003" ); $this->assertEquals( " 05This has böth UTF and control chars \n", file_get_contents( $debugLogFile ) ); - unlink( $debugLogFile ); + unlink( $debugLogFile ); wfDebugMem(); $this->assertGreaterThan( 1000, preg_replace( '/\D/', '', file_get_contents( $debugLogFile ) ) ); - unlink( $debugLogFile ); + unlink( $debugLogFile ); wfDebugMem( true ); $this->assertGreaterThan( 1000000, preg_replace( '/\D/', '', file_get_contents( $debugLogFile ) ) ); + unlink( $debugLogFile ); } diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php index 95571f23da..d8d5495707 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php @@ -18,9 +18,6 @@ class GlobalWithDBTest extends MediaWikiTestCase { // Don't try to fetch the files from Commons or anything, please $this->setMwGlobals( 'wgForeignFileRepos', [] ); - // We need to reset services immediately so that editPage() doesn't use the old RepoGroup - // and hit the network - $this->resetServices(); // XXX How do we get file redirects to work? $this->editPage( 'File:Redirect to bad.jpg', '#REDIRECT [[Bad.jpg]]' ); diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index fb32ef7fda..37d4e0c240 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -404,9 +404,6 @@ class MessageTest extends MediaWikiLangTestCase { */ public function testRawHtmlInMsg() { $this->setMwGlobals( 'wgRawHtml', true ); - // We have to reset the core hook registration. - // to register the html hook - $this->overrideMwServices(); $msg = new RawMessage( '' ); $txt = '<html> tags cannot be' . diff --git a/tests/phpunit/includes/MovePageTest.php b/tests/phpunit/includes/MovePageTest.php index 2895fa286c..5cf6fbe0db 100644 --- a/tests/phpunit/includes/MovePageTest.php +++ b/tests/phpunit/includes/MovePageTest.php @@ -11,16 +11,6 @@ use Wikimedia\Rdbms\LoadBalancer; * @group Database */ class MovePageTest extends MediaWikiTestCase { - /** - * @param string $class - * @return object A mock that throws on any method call - */ - private function getNoOpMock( $class ) { - $mock = $this->createMock( $class ); - $mock->expects( $this->never() )->method( $this->anythingBut( '__destruct' ) ); - return $mock; - } - /** * The only files that exist are 'File:Existent.jpg', 'File:Existent2.jpg', and * 'File:Existent-file-no-page.jpg'. Calling unexpected methods causes a test failure. @@ -72,7 +62,7 @@ class MovePageTest extends MediaWikiTestCase { private function newMovePage( $old, $new, array $params = [] ) : MovePage { $mockLB = $this->createMock( LoadBalancer::class ); $mockLB->method( 'getConnection' ) - ->willReturn( $params['db'] ?? $this->getNoOpMock( IDatabase::class ) ); + ->willReturn( $params['db'] ?? $this->createNoOpMock( IDatabase::class ) ); $mockLB->expects( $this->never() ) ->method( $this->anythingBut( 'getConnection', '__destruct' ) ); @@ -98,8 +88,8 @@ class MovePageTest extends MediaWikiTestCase { ), $mockLB, $params['nsInfo'] ?? $mockNsInfo, - $params['wiStore'] ?? $this->getNoOpMock( WatchedItemStore::class ), - $params['permMgr'] ?? $this->getNoOpMock( PermissionManager::class ), + $params['wiStore'] ?? $this->createNoOpMock( WatchedItemStore::class ), + $params['permMgr'] ?? $this->createNoOpMock( PermissionManager::class ), $params['repoGroup'] ?? $this->getMockRepoGroup() ); } @@ -197,7 +187,6 @@ class MovePageTest extends MediaWikiTestCase { foreach ( $extraOptions as $key => $val ) { $this->setMwGlobals( "wg$key", $val ); } - $this->overrideMwServices(); $this->setService( 'RepoGroup', $this->getMockRepoGroup() ); // This returns true instead of an array if there are no errors $this->hideDeprecated( 'Title::isValidMoveOperation' ); diff --git a/tests/phpunit/includes/Permissions/PermissionManagerTest.php b/tests/phpunit/includes/Permissions/PermissionManagerTest.php index 3c22a23ce2..0e788e7455 100644 --- a/tests/phpunit/includes/Permissions/PermissionManagerTest.php +++ b/tests/phpunit/includes/Permissions/PermissionManagerTest.php @@ -122,8 +122,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase { $this->user = $this->userUser; } - - $this->resetServices(); } public function tearDown() { @@ -143,7 +141,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase { } else { $this->user = $this->altUser; } - $this->resetServices(); } /** @@ -421,8 +418,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase { global $wgGroupPermissions; $old = $wgGroupPermissions; - $wgGroupPermissions = []; - $this->resetServices(); + $this->setMwGlobals( 'wgGroupPermissions', [] ); $this->assertEquals( $check[$action][1], MediaWikiServices::getInstance()->getPermissionManager() @@ -433,8 +429,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase { $this->assertEquals( $check[$action][1], MediaWikiServices::getInstance()->getPermissionManager() ->getPermissionErrors( $action, $this->user, $this->title, 'secure' ) ); - $wgGroupPermissions = $old; - $this->resetServices(); + $this->setMwGlobals( 'wgGroupPermissions', $old ); $this->overrideUserPermissions( $this->user, $action ); $this->assertEquals( $check[$action][2], @@ -453,46 +448,39 @@ class PermissionManagerTest extends MediaWikiLangTestCase { ->userCan( $action, $this->user, $this->title, true ) ); $this->assertEquals( $check[$action][3], MediaWikiServices::getInstance()->getPermissionManager() - ->userCan( $action, $this->user, $this->title, - PermissionManager::RIGOR_QUICK ) ); + ->quickUserCan( $action, $this->user, $this->title ) ); # count( User::getGroupsWithPermissions( $action ) ) < 1 } } protected function runGroupPermissions( $perm, $action, $result, $result2 = null ) { - global $wgGroupPermissions; - if ( $result2 === null ) { $result2 = $result; } - $wgGroupPermissions['autoconfirmed']['move'] = false; - $wgGroupPermissions['user']['move'] = false; - $this->resetServices(); + $this->setGroupPermissions( 'autoconfirmed', 'move', false ); + $this->setGroupPermissions( 'user', 'move', false ); $this->overrideUserPermissions( $this->user, $perm ); $res = MediaWikiServices::getInstance()->getPermissionManager() ->getPermissionErrors( $action, $this->user, $this->title ); $this->assertEquals( $result, $res ); - $wgGroupPermissions['autoconfirmed']['move'] = true; - $wgGroupPermissions['user']['move'] = false; - $this->resetServices(); + $this->setGroupPermissions( 'autoconfirmed', 'move', true ); + $this->setGroupPermissions( 'user', 'move', false ); $this->overrideUserPermissions( $this->user, $perm ); $res = MediaWikiServices::getInstance()->getPermissionManager() ->getPermissionErrors( $action, $this->user, $this->title ); $this->assertEquals( $result2, $res ); - $wgGroupPermissions['autoconfirmed']['move'] = true; - $wgGroupPermissions['user']['move'] = true; - $this->resetServices(); + $this->setGroupPermissions( 'autoconfirmed', 'move', true ); + $this->setGroupPermissions( 'user', 'move', true ); $this->overrideUserPermissions( $this->user, $perm ); $res = MediaWikiServices::getInstance()->getPermissionManager() ->getPermissionErrors( $action, $this->user, $this->title ); $this->assertEquals( $result2, $res ); - $wgGroupPermissions['autoconfirmed']['move'] = false; - $wgGroupPermissions['user']['move'] = true; - $this->resetServices(); + $this->setGroupPermissions( 'autoconfirmed', 'move', false ); + $this->setGroupPermissions( 'user', 'move', true ); $this->overrideUserPermissions( $this->user, $perm ); $res = MediaWikiServices::getInstance()->getPermissionManager() ->getPermissionErrors( $action, $this->user, $this->title ); @@ -899,7 +887,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase { $this->assertEquals( true, MediaWikiServices::getInstance()->getPermissionManager() - ->userCan( 'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) ); + ->quickUserCan( 'edit', $this->user, $this->title ) ); $this->title->mRestrictions = [ "edit" => [ 'bogus', "sysop", "protect", "" ], "bogus" => [ 'bogus', "sysop", "protect", "" ] ]; @@ -945,11 +933,11 @@ class PermissionManagerTest extends MediaWikiLangTestCase { $this->assertEquals( false, MediaWikiServices::getInstance()->getPermissionManager() - ->userCan( 'bogus', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) ); + ->quickUserCan( 'bogus', $this->user, $this->title ) ); $this->assertEquals( false, - MediaWikiServices::getInstance()->getPermissionManager()->userCan( - 'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) ); + MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan( + 'edit', $this->user, $this->title ) ); $this->assertEquals( [ [ 'badaccess-group0' ], [ 'protectedpagetext', 'bogus', 'bogus' ], @@ -965,12 +953,12 @@ class PermissionManagerTest extends MediaWikiLangTestCase { $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] ); $this->assertEquals( false, - MediaWikiServices::getInstance()->getPermissionManager()->userCan( - 'bogus', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) ); + MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan( + 'bogus', $this->user, $this->title ) ); $this->assertEquals( false, - MediaWikiServices::getInstance()->getPermissionManager()->userCan( - 'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) ); + MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan( + 'edit', $this->user, $this->title ) ); $this->assertEquals( [ [ 'badaccess-group0' ], [ 'protectedpagetext', 'bogus', 'bogus' ], @@ -1144,7 +1132,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase { ->getPermissionErrors( 'edit', $this->user, $this->title ) ); $this->setMwGlobals( 'wgEmailConfirmToEdit', false ); - $this->resetServices(); $this->overrideUserPermissions( $this->user, [ 'createpage', 'edit', @@ -1188,8 +1175,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase { ->userCan( 'move-target', $this->user, $this->title ) ); // quickUserCan should ignore user blocks $this->assertEquals( true, MediaWikiServices::getInstance()->getPermissionManager() - ->userCan( 'move-target', $this->user, $this->title, - PermissionManager::RIGOR_QUICK ) ); + ->quickUserCan( 'move-target', $this->user, $this->title ) ); global $wgLocalTZoffset; $wgLocalTZoffset = -60; @@ -1577,7 +1563,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase { $rights = array_diff( $rights, [ 'writetest' ] ); } ); - $this->resetServices(); $rights = MediaWikiServices::getInstance() ->getPermissionManager() ->getUserPermissions( $user ); @@ -1859,7 +1844,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase { 'wgRestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ], 'wgAutopromote' => [] ] ); - $this->resetServices(); $user = is_null( $userGroups ) ? null : $this->getTestUser( $userGroups )->getUser(); $this->assertSame( $expected, MediaWikiServices::getInstance() ->getPermissionManager() diff --git a/tests/phpunit/includes/PrefixSearchTest.php b/tests/phpunit/includes/PrefixSearchTest.php index 685cb4024e..4d9f8e171f 100644 --- a/tests/phpunit/includes/PrefixSearchTest.php +++ b/tests/phpunit/includes/PrefixSearchTest.php @@ -60,8 +60,6 @@ class PrefixSearchTest extends MediaWikiLangTestCase { $this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers; TestingAccessWrapper::newFromClass( Hooks::class )->handlers = []; - - $this->overrideMwServices(); } public function tearDown() { diff --git a/tests/phpunit/includes/Rest/BasicAccess/MWBasicRequestAuthorizerTest.php b/tests/phpunit/includes/Rest/BasicAccess/MWBasicRequestAuthorizerTest.php index 49dcf07798..3c6573ac1f 100644 --- a/tests/phpunit/includes/Rest/BasicAccess/MWBasicRequestAuthorizerTest.php +++ b/tests/phpunit/includes/Rest/BasicAccess/MWBasicRequestAuthorizerTest.php @@ -25,7 +25,6 @@ class MWBasicRequestAuthorizerTest extends MediaWikiTestCase { $user = User::newFromName( 'Test user' ); // Don't allow the rights to everybody so that user rights kick in. $this->mergeMwGlobalArrayValue( 'wgGroupPermissions', [ '*' => $userRights ] ); - $this->resetServices(); $this->overrideUserPermissions( $user, array_keys( array_filter( $userRights ), function ( $value ) { diff --git a/tests/phpunit/includes/Revision/RevisionQueryInfoTest.php b/tests/phpunit/includes/Revision/RevisionQueryInfoTest.php index 57619c55b6..5cc3646ca8 100644 --- a/tests/phpunit/includes/Revision/RevisionQueryInfoTest.php +++ b/tests/phpunit/includes/Revision/RevisionQueryInfoTest.php @@ -960,7 +960,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase { */ public function testRevisionSelectFields( $migrationStageSettings, $expected ) { $this->setMwGlobals( $migrationStageSettings ); - $this->overrideMwServices(); $this->hideDeprecated( 'Revision::selectFields' ); $this->assertArrayEqualsIgnoringIntKeyOrder( $expected, Revision::selectFields() ); @@ -972,7 +971,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase { */ public function testRevisionSelectArchiveFields( $migrationStageSettings, $expected ) { $this->setMwGlobals( $migrationStageSettings ); - $this->overrideMwServices(); $this->hideDeprecated( 'Revision::selectArchiveFields' ); $this->assertArrayEqualsIgnoringIntKeyOrder( $expected, Revision::selectArchiveFields() ); @@ -984,7 +982,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase { public function testRevisionUserJoinCond() { $this->hideDeprecated( 'Revision::userJoinCond' ); $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD ); - $this->overrideMwServices(); $this->assertEquals( [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ], Revision::userJoinCond() @@ -1041,7 +1038,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase { */ public function testRevisionGetArchiveQueryInfo( $migrationStageSettings, $expected ) { $this->setMwGlobals( $migrationStageSettings ); - $this->overrideMwServices(); $queryInfo = Revision::getArchiveQueryInfo(); $this->assertQueryInfoEquals( $expected, $queryInfo ); @@ -1053,7 +1049,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase { */ public function testRevisionGetQueryInfo( $migrationStageSettings, $options, $expected ) { $this->setMwGlobals( $migrationStageSettings ); - $this->overrideMwServices(); $queryInfo = Revision::getQueryInfo( $options ); $this->assertQueryInfoEquals( $expected, $queryInfo ); @@ -1065,7 +1060,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase { */ public function testRevisionStoreGetQueryInfo( $migrationStageSettings, $options, $expected ) { $this->setMwGlobals( $migrationStageSettings ); - $this->overrideMwServices(); $store = MediaWikiServices::getInstance()->getRevisionStore(); @@ -1083,7 +1077,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase { $expected ) { $this->setMwGlobals( $migrationStageSettings ); - $this->overrideMwServices(); $store = MediaWikiServices::getInstance()->getRevisionStore(); @@ -1097,7 +1090,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase { */ public function testRevisionStoreGetArchiveQueryInfo( $migrationStageSettings, $expected ) { $this->setMwGlobals( $migrationStageSettings ); - $this->overrideMwServices(); $store = MediaWikiServices::getInstance()->getRevisionStore(); diff --git a/tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php b/tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php index d4393dd3f6..55bfab7d81 100644 --- a/tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php +++ b/tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php @@ -83,8 +83,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { 'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(), 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW, ] ); - - $this->overrideMwServices(); } protected function addCoreDBData() { @@ -403,7 +401,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { $title = $this->getTestPageTitle(); $rev = $this->getRevisionRecordFromDetailsArray( $revDetails ); - $this->overrideMwServices(); $store = MediaWikiServices::getInstance()->getRevisionStore(); $return = $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) ); @@ -480,7 +477,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { 'user' => true, ]; - $this->overrideMwServices(); $store = MediaWikiServices::getInstance()->getRevisionStore(); // Insert the first revision @@ -594,8 +590,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { * @covers \MediaWiki\Revision\RevisionStore::findSlotContentId */ public function testNewNullRevision( Title $title, $revDetails, $comment, $minor = false ) { - $this->overrideMwServices(); - $user = TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser(); $page = WikiPage::factory( $title ); @@ -1021,7 +1015,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { */ public function testNewRevisionFromRow_anonEdit_legacyEncoding() { $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' ); - $this->overrideMwServices(); $page = $this->getTestPage(); $text = __METHOD__ . 'a-ä'; /** @var Revision $rev */ @@ -1101,7 +1094,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { */ public function testNewRevisionFromArchiveRow_legacyEncoding() { $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' ); - $this->overrideMwServices(); $store = MediaWikiServices::getInstance()->getRevisionStore(); $title = Title::newFromText( __METHOD__ ); $text = __METHOD__ . '-bä'; @@ -1797,7 +1789,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { public function testGetKnownCurrentRevision_userNameChange() { global $wgActorTableSchemaMigrationStage; - $this->overrideMwServices(); $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] ); $this->setService( 'MainWANObjectCache', $cache ); @@ -1838,7 +1829,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { * @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision */ public function testGetKnownCurrentRevision_revDelete() { - $this->overrideMwServices(); $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] ); $this->setService( 'MainWANObjectCache', $cache ); diff --git a/tests/phpunit/includes/RevisionDbTestBase.php b/tests/phpunit/includes/RevisionDbTestBase.php index 83e8d8506f..7b334ee409 100644 --- a/tests/phpunit/includes/RevisionDbTestBase.php +++ b/tests/phpunit/includes/RevisionDbTestBase.php @@ -95,8 +95,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase { 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW, ] ); - $this->overrideMwServices(); - if ( !$this->testPage ) { /** * We have to create a new page for each subclass as the page creation may result diff --git a/tests/phpunit/includes/RevisionTest.php b/tests/phpunit/includes/RevisionTest.php index d62e4c7402..865bd31bb1 100644 --- a/tests/phpunit/includes/RevisionTest.php +++ b/tests/phpunit/includes/RevisionTest.php @@ -281,7 +281,6 @@ class RevisionTest extends MediaWikiTestCase { * @covers \MediaWiki\Revision\RevisionStore::newMutableRevisionFromArray */ public function testConstructFromRowWithBadPageId() { - $this->overrideMwServices(); Wikimedia\suppressWarnings(); $rev = new Revision( (object)[ 'rev_page' => 77777777, @@ -603,7 +602,6 @@ class RevisionTest extends MediaWikiTestCase { */ public function testLoadFromTitle() { $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW ); - $this->overrideMwServices(); $title = $this->getMockTitle(); $conditions = [ diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php index 91c4a13873..6c17bf5bce 100644 --- a/tests/phpunit/includes/TitlePermissionTest.php +++ b/tests/phpunit/includes/TitlePermissionTest.php @@ -72,7 +72,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->user = $this->userUser; } - $this->resetServices(); } protected function setTitle( $ns, $title = "Main_Page" ) { @@ -329,9 +328,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { global $wgGroupPermissions; $old = $wgGroupPermissions; - $wgGroupPermissions = []; - - $this->resetServices(); + $this->setMwGlobals( 'wgGroupPermissions', [] ); $this->assertEquals( $check[$action][1], $this->title->getUserPermissionsErrors( $action, $this->user, true ) ); @@ -340,8 +337,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->assertEquals( $check[$action][1], $this->title->getUserPermissionsErrors( $action, $this->user, 'secure' ) ); - $wgGroupPermissions = $old; - $this->resetServices(); + $this->setMwGlobals( 'wgGroupPermissions', $old ); $this->overrideUserPermissions( $this->user, $action ); $this->assertEquals( $check[$action][2], @@ -361,8 +357,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase { } protected function runGroupPermissions( $action, $result, $result2 = null ) { - global $wgGroupPermissions; - if ( $result2 === null ) { $result2 = $result; } @@ -375,30 +369,26 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $userPermsOverrides = MediaWikiServices::getInstance()->getPermissionManager() ->getUserPermissions( $this->user ); - $wgGroupPermissions['autoconfirmed']['move'] = false; - $wgGroupPermissions['user']['move'] = false; - $this->resetServices(); + $this->setGroupPermissions( 'autoconfirmed', 'move', false ); + $this->setGroupPermissions( 'user', 'move', false ); $this->overrideUserPermissions( $this->user, $userPermsOverrides ); $res = $this->title->getUserPermissionsErrors( $action, $this->user ); $this->assertEquals( $result, $res ); - $wgGroupPermissions['autoconfirmed']['move'] = true; - $wgGroupPermissions['user']['move'] = false; - $this->resetServices(); + $this->setGroupPermissions( 'autoconfirmed', 'move', true ); + $this->setGroupPermissions( 'user', 'move', false ); $this->overrideUserPermissions( $this->user, $userPermsOverrides ); $res = $this->title->getUserPermissionsErrors( $action, $this->user ); $this->assertEquals( $result2, $res ); - $wgGroupPermissions['autoconfirmed']['move'] = true; - $wgGroupPermissions['user']['move'] = true; - $this->resetServices(); + $this->setGroupPermissions( 'autoconfirmed', 'move', true ); + $this->setGroupPermissions( 'user', 'move', true ); $this->overrideUserPermissions( $this->user, $userPermsOverrides ); $res = $this->title->getUserPermissionsErrors( $action, $this->user ); $this->assertEquals( $result2, $res ); - $wgGroupPermissions['autoconfirmed']['move'] = false; - $wgGroupPermissions['user']['move'] = true; - $this->resetServices(); + $this->setGroupPermissions( 'autoconfirmed', 'move', false ); + $this->setGroupPermissions( 'user', 'move', true ); $this->overrideUserPermissions( $this->user, $userPermsOverrides ); $res = $this->title->getUserPermissionsErrors( $action, $this->user ); $this->assertEquals( $result2, $res ); @@ -906,7 +896,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase { 'wgEmailAuthentication' => true, 'wgBlockDisablesLogin' => false, ] ); - $this->resetServices(); $this->overrideUserPermissions( $this->user, @@ -921,7 +910,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->title->getUserPermissionsErrors( 'edit', $this->user ) ); $this->setMwGlobals( 'wgEmailConfirmToEdit', false ); - $this->resetServices(); $this->overrideUserPermissions( $this->user, [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ] @@ -1088,7 +1076,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase { ], ], ] ); - $this->resetServices(); $now = time(); $this->user->mBlockedby = $this->user->getName(); diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index 6cfc3779fb..18faeea3fc 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -150,9 +150,6 @@ class TitleTest extends MediaWikiTestCase { ] ] ] ); - - // Reset services since we modified $wgLocalInterwikis - $this->overrideMwServices(); } /** diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index c2917b66b9..2d19d3817b 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -150,8 +150,6 @@ class ApiBlockTest extends ApiTestCase { $this->setMwGlobals( 'wgRevokePermissions', [ 'user' => [ 'applychangetags' => true ] ] ); - $this->resetServices(); - $this->doBlock( [ 'tags' => 'custom tag' ] ); } @@ -162,7 +160,6 @@ class ApiBlockTest extends ApiTestCase { $this->mergeMwGlobalArrayValue( 'wgGroupPermissions', [ 'sysop' => $newPermissions ] ); - $this->resetServices(); $res = $this->doBlock( [ 'hidename' => '' ] ); $dbw = wfGetDB( DB_MASTER ); @@ -212,8 +209,6 @@ class ApiBlockTest extends ApiTestCase { $this->setMwGlobals( 'wgRevokePermissions', [ 'sysop' => [ 'blockemail' => true ] ] ); - $this->resetServices(); - $this->doBlock( [ 'noemail' => '' ] ); } diff --git a/tests/phpunit/includes/api/ApiDeleteTest.php b/tests/phpunit/includes/api/ApiDeleteTest.php index cc5dadaa93..c68954c077 100644 --- a/tests/phpunit/includes/api/ApiDeleteTest.php +++ b/tests/phpunit/includes/api/ApiDeleteTest.php @@ -143,7 +143,6 @@ class ApiDeleteTest extends ApiTestCase { ChangeTags::defineTag( 'custom tag' ); $this->setMwGlobals( 'wgRevokePermissions', [ 'user' => [ 'applychangetags' => true ] ] ); - $this->resetServices(); $this->editPage( $name, 'Some text' ); diff --git a/tests/phpunit/includes/api/ApiEditPageTest.php b/tests/phpunit/includes/api/ApiEditPageTest.php index 5e5fea321f..aafb3a2d3a 100644 --- a/tests/phpunit/includes/api/ApiEditPageTest.php +++ b/tests/phpunit/includes/api/ApiEditPageTest.php @@ -39,7 +39,6 @@ class ApiEditPageTest extends ApiTestCase { $this->tablesUsed, [ 'change_tag', 'change_tag_def', 'logging' ] ); - $this->resetServices(); } public function testEdit() { @@ -1368,8 +1367,6 @@ class ApiEditPageTest extends ApiTestCase { ChangeTags::defineTag( 'custom tag' ); $this->setMwGlobals( 'wgRevokePermissions', [ 'user' => [ 'applychangetags' => true ] ] ); - // Supply services with updated globals - $this->resetServices(); try { $this->doApiRequestWithToken( [ @@ -1498,8 +1495,6 @@ class ApiEditPageTest extends ApiTestCase { $this->setMwGlobals( 'wgRevokePermissions', [ 'user' => [ 'upload' => true ] ] ); - // Supply services with updated globals - $this->resetServices(); $this->doApiRequestWithToken( [ 'action' => 'edit', @@ -1515,8 +1510,6 @@ class ApiEditPageTest extends ApiTestCase { 'The content you supplied exceeds the article size limit of 1 kilobyte.' ); $this->setMwGlobals( 'wgMaxArticleSize', 1 ); - // Supply services with updated globals - $this->resetServices(); $text = str_repeat( '!', 1025 ); @@ -1534,8 +1527,6 @@ class ApiEditPageTest extends ApiTestCase { 'The action you have requested is limited to users in the group: ' ); $this->setMwGlobals( 'wgRevokePermissions', [ '*' => [ 'edit' => true ] ] ); - // Supply services with updated globals - $this->resetServices(); $this->doApiRequestWithToken( [ 'action' => 'edit', @@ -1552,8 +1543,6 @@ class ApiEditPageTest extends ApiTestCase { $this->setMwGlobals( 'wgRevokePermissions', [ 'user' => [ 'editcontentmodel' => true ] ] ); - // Supply services with updated globals - $this->resetServices(); $this->doApiRequestWithToken( [ 'action' => 'edit', diff --git a/tests/phpunit/includes/api/ApiMainTest.php b/tests/phpunit/includes/api/ApiMainTest.php index 580efcd933..1e2135b836 100644 --- a/tests/phpunit/includes/api/ApiMainTest.php +++ b/tests/phpunit/includes/api/ApiMainTest.php @@ -141,7 +141,6 @@ class ApiMainTest extends ApiTestCase { public function testSetCacheModeUnrecognized() { $api = new ApiMain(); $api->setCacheMode( 'unrecognized' ); - $this->resetServices(); $this->assertSame( 'private', TestingAccessWrapper::newFromObject( $api )->mCacheMode, diff --git a/tests/phpunit/includes/api/ApiMoveTest.php b/tests/phpunit/includes/api/ApiMoveTest.php index c98308cc88..e09e6ad7bb 100644 --- a/tests/phpunit/includes/api/ApiMoveTest.php +++ b/tests/phpunit/includes/api/ApiMoveTest.php @@ -294,7 +294,6 @@ class ApiMoveTest extends ApiTestCase { $name = ucfirst( __FUNCTION__ ); $this->mergeMwGlobalArrayValue( 'wgNamespacesWithSubpages', [ NS_MAIN => true ] ); - $this->resetServices(); $pages = [ $name, "$name/1", "$name/2", "Talk:$name", "Talk:$name/1", "Talk:$name/3" ]; $ids = []; diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php index bdce70c6be..2d0ce26f48 100644 --- a/tests/phpunit/includes/api/ApiOptionsTest.php +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -42,6 +42,16 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->method( 'getOptions' ) ->willReturn( [] ); + // DefaultPreferencesFactory calls a ton of user methods, but we still want to list all of + // them in case bugs are caused by unexpected things returning null that shouldn't. + $this->mUserMock->expects( $this->never() )->method( $this->anythingBut( + 'getEffectiveGroups', 'getOptionKinds', 'getInstanceForUpdate', 'getOptions', 'getId', + 'isAnon', 'getRequest', 'isLoggedIn', 'getName', 'getGroupMemberships', 'getEditCount', + 'getRegistration', 'isAllowed', 'getRealName', 'getOption', 'getStubThreshold', + 'getBoolOption', 'getEmail', 'getDatePreference', 'useRCPatrol', 'useNPPatrol', + 'setOption', 'saveSettings', 'resetOptions', 'isRegistered' + ) ); + // Create a new context $this->mContext = new DerivativeContext( new RequestContext() ); $this->mContext->getContext()->setTitle( Title::newFromText( 'Test' ) ); @@ -153,6 +163,8 @@ class ApiOptionsTest extends MediaWikiLangTestCase { private function executeQuery( $request ) { $this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) ); + $this->mUserMock->method( 'getRequest' )->willReturn( $this->mContext->getRequest() ); + $this->mTested->execute(); return $this->mTested->getResult()->getResultData( null, [ 'Strip' => 'all' ] ); diff --git a/tests/phpunit/includes/api/ApiParseTest.php b/tests/phpunit/includes/api/ApiParseTest.php index a87160a1d8..06b4cf257a 100644 --- a/tests/phpunit/includes/api/ApiParseTest.php +++ b/tests/phpunit/includes/api/ApiParseTest.php @@ -121,7 +121,6 @@ class ApiParseTest extends ApiTestCase { $this->setMwGlobals( 'wgExtraInterlanguageLinkPrefixes', [ 'madeuplanguage' ] ); $this->tablesUsed[] = 'interwiki'; - $this->resetServices(); } /** diff --git a/tests/phpunit/includes/api/ApiQuerySiteinfoTest.php b/tests/phpunit/includes/api/ApiQuerySiteinfoTest.php index 282188d264..0a2558a227 100644 --- a/tests/phpunit/includes/api/ApiQuerySiteinfoTest.php +++ b/tests/phpunit/includes/api/ApiQuerySiteinfoTest.php @@ -210,15 +210,16 @@ class ApiQuerySiteinfoTest extends ApiTestCase { $this->setExpectedApiException( 'apierror-siteinfo-includealldenied' ); } - $mockLB = $this->getMockBuilder( LoadBalancer::class ) - ->disableOriginalConstructor() - ->setMethods( [ 'getMaxLag', 'getLagTimes', 'getServerName', '__destruct' ] ) - ->getMock(); + $mockLB = $this->createMock( LoadBalancer::class ); $mockLB->method( 'getMaxLag' )->willReturn( [ null, 7, 1 ] ); $mockLB->method( 'getLagTimes' )->willReturn( [ 5, 7 ] ); $mockLB->method( 'getServerName' )->will( $this->returnValueMap( [ [ 0, 'apple' ], [ 1, 'carrot' ] ] ) ); + $mockLB->method( 'getLocalDomainID' )->willReturn( 'testdomain' ); + $mockLB->expects( $this->never() )->method( $this->anythingBut( + 'getMaxLag', 'getLagTimes', 'getServerName', 'getLocalDomainID', '__destruct' + ) ); $this->setService( 'DBLoadBalancer', $mockLB ); $this->setMwGlobals( 'wgShowHostnames', $showHostnames ); diff --git a/tests/phpunit/includes/api/ApiUserrightsTest.php b/tests/phpunit/includes/api/ApiUserrightsTest.php index 0d7ad0c502..92804fdfaf 100644 --- a/tests/phpunit/includes/api/ApiUserrightsTest.php +++ b/tests/phpunit/includes/api/ApiUserrightsTest.php @@ -37,8 +37,6 @@ class ApiUserrightsTest extends ApiTestCase { if ( $remove ) { $this->mergeMwGlobalArrayValue( 'wgRemoveGroups', [ 'bureaucrat' => $remove ] ); } - - $this->resetServices(); } /** @@ -221,7 +219,6 @@ class ApiUserrightsTest extends ApiTestCase { ChangeTags::defineTag( 'custom tag' ); $this->setGroupPermissions( 'user', 'applychangetags', false ); - $this->resetServices(); $this->doFailedRightsChange( 'You do not have permission to apply change tags along with your changes.', diff --git a/tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php b/tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php index 92c71bd59d..301ed100b5 100644 --- a/tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php @@ -11,13 +11,11 @@ class ApiQueryUserContribsTest extends ApiTestCase { global $wgActorTableSchemaMigrationStage; $reset = new \Wikimedia\ScopedCallback( function ( $v ) { - global $wgActorTableSchemaMigrationStage; - $wgActorTableSchemaMigrationStage = $v; - $this->overrideMwServices(); + $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $v ); }, [ $wgActorTableSchemaMigrationStage ] ); // Needs to WRITE_BOTH so READ_OLD tests below work. READ mode here doesn't really matter. - $wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW; - $this->overrideMwServices(); + $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ); $users = [ User::newFromName( '192.168.2.2', false ), @@ -55,7 +53,6 @@ class ApiQueryUserContribsTest extends ApiTestCase { $this->markTestSkippedIfDbType( 'sqlite' ); $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage ); - $this->overrideMwServices(); if ( isset( $params['ucuserids'] ) ) { $params['ucuserids'] = implode( '|', array_map( 'User::idFromName', $params['ucuserids'] ) ); @@ -150,7 +147,6 @@ class ApiQueryUserContribsTest extends ApiTestCase { */ public function testInterwikiUser( $stage ) { $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage ); - $this->overrideMwServices(); $params = [ 'action' => 'query', diff --git a/tests/phpunit/includes/auth/AuthManagerTest.php b/tests/phpunit/includes/auth/AuthManagerTest.php index fc6f688091..f8be1d437f 100644 --- a/tests/phpunit/includes/auth/AuthManagerTest.php +++ b/tests/phpunit/includes/auth/AuthManagerTest.php @@ -1473,12 +1473,10 @@ class AuthManagerTest extends \MediaWikiTestCase { ], 'wgProxyWhitelist' => [], ] ); - $this->resetServices(); $status = $this->manager->checkAccountCreatePermissions( new \User ); $this->assertFalse( $status->isOK() ); $this->assertTrue( $status->hasMessage( 'sorbs_create_account_reason' ) ); $this->setMwGlobals( 'wgProxyWhitelist', [ '127.0.0.1' ] ); - $this->resetServices(); $status = $this->manager->checkAccountCreatePermissions( new \User ); $this->assertTrue( $status->isGood() ); } @@ -1610,9 +1608,9 @@ class AuthManagerTest extends \MediaWikiTestCase { $this->assertSame( AuthenticationResponse::FAIL, $ret->status ); $this->assertSame( 'noname', $ret->message->getKey() ); + $this->hook( 'LocalUserCreated', $this->never() ); $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode(); $readOnlyMode->setReason( 'Because' ); - $this->hook( 'LocalUserCreated', $this->never() ); $userReq->username = self::usernameForCreation(); $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' ); $this->unhook( 'LocalUserCreated' ); @@ -1782,11 +1780,11 @@ class AuthManagerTest extends \MediaWikiTestCase { $session, $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ) ); + $this->hook( 'LocalUserCreated', $this->never() ); $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', [ 'username' => $creator->getName() ] + $session ); $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode(); $readOnlyMode->setReason( 'Because' ); - $this->hook( 'LocalUserCreated', $this->never() ); $ret = $this->manager->continueAccountCreation( [] ); $this->unhook( 'LocalUserCreated' ); $this->assertSame( AuthenticationResponse::FAIL, $ret->status ); @@ -2366,8 +2364,6 @@ class AuthManagerTest extends \MediaWikiTestCase { $this->mergeMwGlobalArrayValue( 'wgObjectCaches', [ __METHOD__ => [ 'class' => 'HashBagOStuff' ] ] ); $this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] ); - // Supply services with updated globals - $this->resetServices(); // Set up lots of mocks... $mocks = []; @@ -2486,10 +2482,10 @@ class AuthManagerTest extends \MediaWikiTestCase { // Wiki is read-only $session->clear(); + $this->hook( 'LocalUserCreated', $this->never() ); $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode(); $readOnlyMode->setReason( 'Because' ); $user = \User::newFromName( $username ); - $this->hook( 'LocalUserCreated', $this->never() ); $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true ); $this->unhook( 'LocalUserCreated' ); $this->assertEquals( \Status::newFatal( wfMessage( 'readonlytext', 'Because' ) ), $ret ); diff --git a/tests/phpunit/includes/block/BlockManagerTest.php b/tests/phpunit/includes/block/BlockManagerTest.php index e085d88fa2..d4133b7f93 100644 --- a/tests/phpunit/includes/block/BlockManagerTest.php +++ b/tests/phpunit/includes/block/BlockManagerTest.php @@ -48,7 +48,6 @@ class BlockManagerTest extends MediaWikiTestCase { private function getBlockManagerConstructorArgs( $overrideConfig ) { $blockManagerConfig = array_merge( $this->blockManagerConfig, $overrideConfig ); $this->setMwGlobals( $blockManagerConfig ); - $this->overrideMwServices(); return [ new LoggedServiceOptions( self::$serviceOptionsAccessLog, diff --git a/tests/phpunit/includes/cache/MessageCacheTest.php b/tests/phpunit/includes/cache/MessageCacheTest.php index 74ad84ade6..7abddd44ad 100644 --- a/tests/phpunit/includes/cache/MessageCacheTest.php +++ b/tests/phpunit/includes/cache/MessageCacheTest.php @@ -24,7 +24,6 @@ class MessageCacheTest extends MediaWikiLangTestCase { // let's choose e.g. German (de) $this->setUserLang( 'de' ); $this->setContentLang( 'de' ); - $this->resetServices(); } function addDBDataOnce() { @@ -150,7 +149,6 @@ class MessageCacheTest extends MediaWikiLangTestCase { ] ] ] ); - $this->overrideMwServices(); $messageCache = MessageCache::singleton(); $messageCache->enable(); diff --git a/tests/phpunit/includes/content/UnknownContentHandlerTest.php b/tests/phpunit/includes/content/UnknownContentHandlerTest.php new file mode 100644 index 0000000000..bc1d3c6405 --- /dev/null +++ b/tests/phpunit/includes/content/UnknownContentHandlerTest.php @@ -0,0 +1,119 @@ +assertFalse( $handler->supportsDirectEditing(), 'direct editing supported' ); + } + + /** + * @covers UnknownContentHandler::serializeContent + */ + public function testSerializeContent() { + $handler = new UnknownContentHandler( 'horkyporky' ); + $content = new UnknownContent( 'hello world', 'horkyporky' ); + + $this->assertEquals( 'hello world', $handler->serializeContent( $content ) ); + $this->assertEquals( + 'hello world', + $handler->serializeContent( $content, 'application/horkyporky' ) + ); + } + + /** + * @covers UnknownContentHandler::unserializeContent + */ + public function testUnserializeContent() { + $handler = new UnknownContentHandler( 'horkyporky' ); + $content = $handler->unserializeContent( 'hello world' ); + $this->assertEquals( 'hello world', $content->getData() ); + + $content = $handler->unserializeContent( 'hello world', 'application/horkyporky' ); + $this->assertEquals( 'hello world', $content->getData() ); + } + + /** + * @covers UnknownContentHandler::makeEmptyContent + */ + public function testMakeEmptyContent() { + $handler = new UnknownContentHandler( 'horkyporky' ); + $content = $handler->makeEmptyContent(); + + $this->assertTrue( $content->isEmpty() ); + $this->assertEquals( '', $content->getData() ); + } + + public static function dataIsSupportedFormat() { + return [ + [ null, true ], + [ 'application/octet-stream', true ], + [ 'unknown/unknown', true ], + [ 'text/plain', false ], + [ 99887766, false ], + ]; + } + + /** + * @dataProvider dataIsSupportedFormat + * @covers UnknownContentHandler::isSupportedFormat + */ + public function testIsSupportedFormat( $format, $supported ) { + $handler = new UnknownContentHandler( 'horkyporky' ); + $this->assertEquals( $supported, $handler->isSupportedFormat( $format ) ); + } + + /** + * @covers ContentHandler::getSecondaryDataUpdates + */ + public function testGetSecondaryDataUpdates() { + $title = Title::newFromText( 'Somefile.jpg', NS_FILE ); + $content = new UnknownContent( '', 'horkyporky' ); + + /** @var SlotRenderingProvider $srp */ + $srp = $this->getMock( SlotRenderingProvider::class ); + + $handler = new UnknownContentHandler( 'horkyporky' ); + $updates = $handler->getSecondaryDataUpdates( $title, $content, SlotRecord::MAIN, $srp ); + + $this->assertEquals( [], $updates ); + } + + /** + * @covers ContentHandler::getDeletionUpdates + */ + public function testGetDeletionUpdates() { + $title = Title::newFromText( 'Somefile.jpg', NS_FILE ); + + $handler = new UnknownContentHandler( 'horkyporky' ); + $updates = $handler->getDeletionUpdates( $title, SlotRecord::MAIN ); + + $this->assertEquals( [], $updates ); + } + + /** + * @covers ContentHandler::getDeletionUpdates + */ + public function testGetSlotDiffRenderer() { + $context = new RequestContext(); + $context->setRequest( new FauxRequest() ); + + $handler = new UnknownContentHandler( 'horkyporky' ); + $slotDiffRenderer = $handler->getSlotDiffRenderer( $context ); + + $oldContent = $handler->unserializeContent( 'Foo' ); + $newContent = $handler->unserializeContent( 'Foo bar' ); + + $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent ); + $this->assertNotEmpty( $diff ); + } + +} diff --git a/tests/phpunit/includes/content/UnknownContentTest.php b/tests/phpunit/includes/content/UnknownContentTest.php new file mode 100644 index 0000000000..fd8e3ba585 --- /dev/null +++ b/tests/phpunit/includes/content/UnknownContentTest.php @@ -0,0 +1,259 @@ +setUserLang( 'en' ); + $this->setContentLang( 'qqx' ); + + $title = Title::newFromText( 'Test' ); + $content = $this->newContent( 'Horkyporky' ); + + $po = $content->getParserOutput( $title ); + $html = $po->getText(); + $html = preg_replace( '##sm', '', $html ); // strip comments + + $this->assertNotContains( 'Horkyporky', $html ); + $this->assertNotContains( '(unsupported-content-model)', $html ); + } + + /** + * @covers UnknownContent::preSaveTransform + */ + public function testPreSaveTransform() { + $title = Title::newFromText( 'Test' ); + $user = $this->getTestUser()->getUser(); + $content = $this->newContent( 'Horkyporky ~~~' ); + + $options = new ParserOptions(); + + $this->assertSame( $content, $content->preSaveTransform( $title, $user, $options ) ); + } + + /** + * @covers UnknownContent::preloadTransform + */ + public function testPreloadTransform() { + $title = Title::newFromText( 'Test' ); + $content = $this->newContent( 'Horkyporky ~~~' ); + + $options = new ParserOptions(); + + $this->assertSame( $content, $content->preloadTransform( $title, $options ) ); + } + + /** + * @covers UnknownContent::getRedirectTarget + */ + public function testGetRedirectTarget() { + $content = $this->newContent( '#REDIRECT [[Horkyporky]]' ); + $this->assertNull( $content->getRedirectTarget() ); + } + + /** + * @covers UnknownContent::isRedirect + */ + public function testIsRedirect() { + $content = $this->newContent( '#REDIRECT [[Horkyporky]]' ); + $this->assertFalse( $content->isRedirect() ); + } + + /** + * @covers UnknownContent::isCountable + */ + public function testIsCountable() { + $content = $this->newContent( '[[Horkyporky]]' ); + $this->assertFalse( $content->isCountable( true ) ); + } + + /** + * @covers UnknownContent::getTextForSummary + */ + public function testGetTextForSummary() { + $content = $this->newContent( 'Horkyporky' ); + $this->assertSame( '', $content->getTextForSummary() ); + } + + /** + * @covers UnknownContent::getTextForSearchIndex + */ + public function testGetTextForSearchIndex() { + $content = $this->newContent( 'Horkyporky' ); + $this->assertSame( '', $content->getTextForSearchIndex() ); + } + + /** + * @covers UnknownContent::copy + */ + public function testCopy() { + $content = $this->newContent( 'hello world.' ); + $copy = $content->copy(); + + $this->assertSame( $content, $copy ); + } + + /** + * @covers UnknownContent::getSize + */ + public function testGetSize() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 12, $content->getSize() ); + } + + /** + * @covers UnknownContent::getData + */ + public function testGetData() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 'hello world.', $content->getData() ); + } + + /** + * @covers UnknownContent::getNativeData + */ + public function testGetNativeData() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 'hello world.', $content->getNativeData() ); + } + + /** + * @covers UnknownContent::getWikitextForTransclusion + */ + public function testGetWikitextForTransclusion() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( '', $content->getWikitextForTransclusion() ); + } + + /** + * @covers UnknownContent::getModel + */ + public function testGetModel() { + $content = $this->newContent( "hello world.", 'horkyporky' ); + + $this->assertEquals( 'horkyporky', $content->getModel() ); + } + + /** + * @covers UnknownContent::getContentHandler + */ + public function testGetContentHandler() { + $this->mergeMwGlobalArrayValue( + 'wgContentHandlers', + [ 'horkyporky' => 'UnknownContentHandler' ] + ); + + $content = $this->newContent( "hello world.", 'horkyporky' ); + + $this->assertInstanceOf( UnknownContentHandler::class, $content->getContentHandler() ); + $this->assertEquals( 'horkyporky', $content->getContentHandler()->getModelID() ); + } + + public static function dataIsEmpty() { + return [ + [ '', true ], + [ ' ', false ], + [ '0', false ], + [ 'hallo welt.', false ], + ]; + } + + /** + * @dataProvider dataIsEmpty + * @covers UnknownContent::isEmpty + */ + public function testIsEmpty( $text, $empty ) { + $content = $this->newContent( $text ); + + $this->assertEquals( $empty, $content->isEmpty() ); + } + + public function provideEquals() { + return [ + [ new UnknownContent( "hallo", 'horky' ), null, false ], + [ new UnknownContent( "hallo", 'horky' ), new UnknownContent( "hallo", 'horky' ), true ], + [ new UnknownContent( "hallo", 'horky' ), new UnknownContent( "hallo", 'xyzzy' ), false ], + [ new UnknownContent( "hallo", 'horky' ), new JavaScriptContent( "hallo" ), false ], + [ new UnknownContent( "hallo", 'horky' ), new WikitextContent( "hallo" ), false ], + ]; + } + + /** + * @dataProvider provideEquals + * @covers UnknownContent::equals + */ + public function testEquals( Content $a, Content $b = null, $equal = false ) { + $this->assertEquals( $equal, $a->equals( $b ) ); + } + + public static function provideConvert() { + return [ + [ // #0 + 'Hallo Welt', + CONTENT_MODEL_WIKITEXT, + 'lossless', + 'Hallo Welt' + ], + [ // #1 + 'Hallo Welt', + CONTENT_MODEL_WIKITEXT, + 'lossless', + 'Hallo Welt' + ], + [ // #1 + 'Hallo Welt', + CONTENT_MODEL_CSS, + 'lossless', + 'Hallo Welt' + ], + [ // #1 + 'Hallo Welt', + CONTENT_MODEL_JAVASCRIPT, + 'lossless', + 'Hallo Welt' + ], + ]; + } + + /** + * @covers UnknownContent::convert + */ + public function testConvert() { + $content = $this->newContent( 'More horkyporky?' ); + + $this->assertFalse( $content->convert( CONTENT_MODEL_TEXT ) ); + } + + /** + * @covers UnknownContent::__construct + * @covers UnknownContentHandler::serializeContent + */ + public function testSerialize() { + $this->mergeMwGlobalArrayValue( + 'wgContentHandlers', + [ 'horkyporky' => 'UnknownContentHandler' ] + ); + + $content = $this->newContent( 'Hörkypörky', 'horkyporky' ); + + $this->assertSame( 'Hörkypörky', $content->serialize() ); + } + +} diff --git a/tests/phpunit/includes/diff/DifferenceEngineTest.php b/tests/phpunit/includes/diff/DifferenceEngineTest.php index ba31b4ffd5..c1bb1a0d38 100644 --- a/tests/phpunit/includes/diff/DifferenceEngineTest.php +++ b/tests/phpunit/includes/diff/DifferenceEngineTest.php @@ -157,8 +157,14 @@ class DifferenceEngineTest extends MediaWikiTestCase { * @dataProvider provideGenerateContentDiffBody */ public function testGenerateContentDiffBody( - Content $oldContent, Content $newContent, $expectedDiff + array $oldContentArgs, array $newContentArgs, $expectedDiff ) { + $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [ + 'testing-nontext' => DummyNonTextContentHandler::class, + ] ); + $oldContent = ContentHandler::makeContent( ...$oldContentArgs ); + $newContent = ContentHandler::makeContent( ...$newContentArgs ); + // Set $wgExternalDiffEngine to something bogus to try to force use of // the PHP engine rather than wikidiff2. $this->setMwGlobals( [ @@ -170,12 +176,9 @@ class DifferenceEngineTest extends MediaWikiTestCase { $this->assertSame( $expectedDiff, $this->getPlainDiff( $diff ) ); } - public function provideGenerateContentDiffBody() { - $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [ - 'testing-nontext' => DummyNonTextContentHandler::class, - ] ); - $content1 = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT ); - $content2 = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT ); + public static function provideGenerateContentDiffBody() { + $content1 = [ 'xxx', null, CONTENT_MODEL_TEXT ]; + $content2 = [ 'yyy', null, CONTENT_MODEL_TEXT ]; return [ 'self-diff' => [ $content1, $content1, '' ], diff --git a/tests/phpunit/includes/diff/TextSlotDiffRendererTest.php b/tests/phpunit/includes/diff/TextSlotDiffRendererTest.php index c523561827..3eb12011d2 100644 --- a/tests/phpunit/includes/diff/TextSlotDiffRendererTest.php +++ b/tests/phpunit/includes/diff/TextSlotDiffRendererTest.php @@ -9,14 +9,22 @@ class TextSlotDiffRendererTest extends MediaWikiTestCase { /** * @dataProvider provideGetDiff - * @param Content|null $oldContent - * @param Content|null $newContent + * @param array|null $oldContentArgs To pass to makeContent() (if not null) + * @param array|null $newContentArgs * @param string|Exception $expectedResult * @throws Exception */ public function testGetDiff( - Content $oldContent = null, Content $newContent = null, $expectedResult + array $oldContentArgs = null, array $newContentArgs = null, $expectedResult ) { + $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [ + 'testing' => DummyContentHandlerForTesting::class, + 'testing-nontext' => DummyNonTextContentHandler::class, + ] ); + + $oldContent = $oldContentArgs ? self::makeContent( ...$oldContentArgs ) : null; + $newContent = $newContentArgs ? self::makeContent( ...$newContentArgs ) : null; + if ( $expectedResult instanceof Exception ) { $this->setExpectedException( get_class( $expectedResult ), $expectedResult->getMessage() ); } @@ -30,31 +38,26 @@ class TextSlotDiffRendererTest extends MediaWikiTestCase { $this->assertSame( $expectedResult, $plainDiff ); } - public function provideGetDiff() { - $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [ - 'testing' => DummyContentHandlerForTesting::class, - 'testing-nontext' => DummyNonTextContentHandler::class, - ] ); - + public static function provideGetDiff() { return [ 'same text' => [ - $this->makeContent( "aaa\nbbb\nccc" ), - $this->makeContent( "aaa\nbbb\nccc" ), + [ "aaa\nbbb\nccc" ], + [ "aaa\nbbb\nccc" ], "", ], 'different text' => [ - $this->makeContent( "aaa\nbbb\nccc" ), - $this->makeContent( "aaa\nxxx\nccc" ), + [ "aaa\nbbb\nccc" ], + [ "aaa\nxxx\nccc" ], " aaa aaa\n-bbb+xxx\n ccc ccc", ], 'no right content' => [ - $this->makeContent( "aaa\nbbb\nccc" ), + [ "aaa\nbbb\nccc" ], null, "-aaa+ \n-bbb \n-ccc ", ], 'no left content' => [ null, - $this->makeContent( "aaa\nbbb\nccc" ), + [ "aaa\nbbb\nccc" ], "- +aaa\n +bbb\n +ccc", ], 'no content' => [ @@ -63,13 +66,13 @@ class TextSlotDiffRendererTest extends MediaWikiTestCase { new InvalidArgumentException( '$oldContent and $newContent cannot both be null' ), ], 'non-text left content' => [ - $this->makeContent( '', 'testing-nontext' ), - $this->makeContent( "aaa\nbbb\nccc" ), + [ '', 'testing-nontext' ], + [ "aaa\nbbb\nccc" ], new ParameterTypeException( '$oldContent', 'TextContent|null' ), ], 'non-text right content' => [ - $this->makeContent( "aaa\nbbb\nccc" ), - $this->makeContent( '', 'testing-nontext' ), + [ "aaa\nbbb\nccc" ], + [ '', 'testing-nontext' ], new ParameterTypeException( '$newContent', 'TextContent|null' ), ], ]; @@ -107,7 +110,7 @@ class TextSlotDiffRendererTest extends MediaWikiTestCase { * @param string $model * @return null|TextContent */ - private function makeContent( $str, $model = CONTENT_MODEL_TEXT ) { + private static function makeContent( $str, $model = CONTENT_MODEL_TEXT ) { return ContentHandler::makeContent( $str, null, $model ); } diff --git a/tests/phpunit/includes/diff/UnsupportedSlotDiffRendererTest.php b/tests/phpunit/includes/diff/UnsupportedSlotDiffRendererTest.php new file mode 100644 index 0000000000..e8f0bb4ef1 --- /dev/null +++ b/tests/phpunit/includes/diff/UnsupportedSlotDiffRendererTest.php @@ -0,0 +1,42 @@ +mergeMwGlobalArrayValue( + 'wgContentHandlers', + [ 'xyzzy' => 'UnknownContentHandler' ] + ); + + $localizer = $this->getMock( MessageLocalizer::class ); + + $localizer->method( 'msg' ) + ->willReturnCallback( function ( $key, ...$params ) { + return new RawMessage( "($key)", $params ); + } ); + + $sdr = new UnsupportedSlotDiffRenderer( $localizer ); + $this->assertContains( $expected, $sdr->getDiff( $oldContent, $newContent ) ); + } + +} diff --git a/tests/phpunit/includes/filebackend/FileBackendGroupIntegrationTest.php b/tests/phpunit/includes/filebackend/FileBackendGroupIntegrationTest.php index ee3262c4e8..00fa1bb4a5 100644 --- a/tests/phpunit/includes/filebackend/FileBackendGroupIntegrationTest.php +++ b/tests/phpunit/includes/filebackend/FileBackendGroupIntegrationTest.php @@ -49,7 +49,11 @@ class FileBackendGroupIntegrationTest extends MediaWikiIntegrationTestCase { $services = MediaWikiServices::getInstance(); foreach ( $serviceMembers as $key => $name ) { - $this->$key = $services->getService( $name ); + if ( $key === 'srvCache' ) { + $this->$key = ObjectCache::getLocalServerInstance( 'hash' ); + } else { + $this->$key = $services->getService( $name ); + } } return FileBackendGroup::singleton(); diff --git a/tests/phpunit/includes/filerepo/RepoGroupTest.php b/tests/phpunit/includes/filerepo/RepoGroupTest.php index 67de6982e6..04452f2259 100644 --- a/tests/phpunit/includes/filerepo/RepoGroupTest.php +++ b/tests/phpunit/includes/filerepo/RepoGroupTest.php @@ -7,7 +7,6 @@ class RepoGroupTest extends MediaWikiTestCase { function testHasForeignRepoNegative() { $this->setMwGlobals( 'wgForeignFileRepos', [] ); - $this->overrideMwServices(); FileBackendGroup::destroySingleton(); $this->assertFalse( RepoGroup::singleton()->hasForeignRepos() ); } @@ -27,7 +26,6 @@ class RepoGroupTest extends MediaWikiTestCase { function testForEachForeignRepoNone() { $this->setMwGlobals( 'wgForeignFileRepos', [] ); - $this->overrideMwServices(); FileBackendGroup::destroySingleton(); $fakeCallback = $this->createMock( RepoGroupTestHelper::class ); $fakeCallback->expects( $this->never() )->method( 'callback' ); @@ -48,7 +46,6 @@ class RepoGroupTest extends MediaWikiTestCase { 'apiThumbCacheExpiry' => 86400, 'directory' => $wgUploadDirectory ] ] ); - $this->overrideMwServices(); FileBackendGroup::destroySingleton(); } } diff --git a/tests/phpunit/includes/interwiki/InterwikiTest.php b/tests/phpunit/includes/interwiki/InterwikiTest.php index d2c9a327e0..88ac923796 100644 --- a/tests/phpunit/includes/interwiki/InterwikiTest.php +++ b/tests/phpunit/includes/interwiki/InterwikiTest.php @@ -51,7 +51,6 @@ class InterwikiTest extends MediaWikiTestCase { } private function setWgInterwikiCache( $interwikiCache ) { - $this->overrideMwServices(); MediaWikiServices::getInstance()->resetServiceForTesting( 'InterwikiLookup' ); $this->setMwGlobals( 'wgInterwikiCache', $interwikiCache ); } diff --git a/tests/phpunit/includes/objectcache/ObjectCacheTest.php b/tests/phpunit/includes/objectcache/ObjectCacheTest.php index 4318852867..7f52ca6039 100644 --- a/tests/phpunit/includes/objectcache/ObjectCacheTest.php +++ b/tests/phpunit/includes/objectcache/ObjectCacheTest.php @@ -82,9 +82,6 @@ class ObjectCacheTest extends MediaWikiTestCase { /** @covers ObjectCache::newAnything */ public function testNewAnythingNoAccelNoDb() { - $this->overrideMwServices(); // Ensures restore on tear down - MediaWiki\MediaWikiServices::disableStorageBackend(); - $this->setMwGlobals( [ 'wgMainCacheType' => CACHE_ACCEL ] ); @@ -94,6 +91,8 @@ class ObjectCacheTest extends MediaWikiTestCase { CACHE_ACCEL => [ 'class' => EmptyBagOStuff::class ] ] ); + MediaWiki\MediaWikiServices::disableStorageBackend(); + $this->assertInstanceOf( EmptyBagOStuff::class, ObjectCache::newAnything( [] ), @@ -103,7 +102,6 @@ class ObjectCacheTest extends MediaWikiTestCase { /** @covers ObjectCache::newAnything */ public function testNewAnythingNothingNoDb() { - $this->overrideMwServices(); MediaWiki\MediaWikiServices::disableStorageBackend(); $this->assertInstanceOf( diff --git a/tests/phpunit/includes/page/PageArchiveTestBase.php b/tests/phpunit/includes/page/PageArchiveTestBase.php index 42edf3803b..bcbc1eda19 100644 --- a/tests/phpunit/includes/page/PageArchiveTestBase.php +++ b/tests/phpunit/includes/page/PageArchiveTestBase.php @@ -83,13 +83,11 @@ abstract class PageArchiveTestBase extends MediaWikiTestCase { $this->tablesUsed += $this->getMcrTablesToReset(); - $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW ); - $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() ); - $this->setMwGlobals( - 'wgMultiContentRevisionSchemaMigrationStage', - $this->getMcrMigrationStage() - ); - $this->overrideMwServices(); + $this->setMwGlobals( [ + 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW, + 'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(), + 'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(), + ] ); // First create our dummy page $page = Title::newFromText( 'PageArchiveTest_thePage' ); diff --git a/tests/phpunit/includes/page/WikiPageDbTestBase.php b/tests/phpunit/includes/page/WikiPageDbTestBase.php index 30973c8f67..f071e4b198 100644 --- a/tests/phpunit/includes/page/WikiPageDbTestBase.php +++ b/tests/phpunit/includes/page/WikiPageDbTestBase.php @@ -65,8 +65,6 @@ abstract class WikiPageDbTestBase extends MediaWikiLangTestCase { $this->getMcrMigrationStage() ); $this->pagesToDelete = []; - - $this->overrideMwServices(); } protected function tearDown() { diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php index ac4a1ca1bb..428778eebc 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php @@ -18,8 +18,6 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { * @coversNothing */ public function testServiceWiring() { - $this->overrideMwServices(); - $ranHook = 0; $this->setMwGlobals( 'wgHooks', [ 'ResourceLoaderRegisterModules' => [ diff --git a/tests/phpunit/includes/search/SearchEnginePrefixTest.php b/tests/phpunit/includes/search/SearchEnginePrefixTest.php index 372cb33cbb..ed750766d4 100644 --- a/tests/phpunit/includes/search/SearchEnginePrefixTest.php +++ b/tests/phpunit/includes/search/SearchEnginePrefixTest.php @@ -68,8 +68,6 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase { $this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers; TestingAccessWrapper::newFromClass( Hooks::class )->handlers = []; - - $this->overrideMwServices(); } public function tearDown() { diff --git a/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php b/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php index 9d58cef71e..68433c61ed 100644 --- a/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php +++ b/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php @@ -225,7 +225,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase public function testRcHidemyselfFilter() { $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW ); - $this->overrideMwServices(); $user = $this->getTestUser()->getUser(); $user->getActorId( wfGetDB( DB_MASTER ) ); @@ -258,7 +257,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ); - $this->overrideMwServices(); $user = $this->getTestUser()->getUser(); $user->getActorId( wfGetDB( DB_MASTER ) ); @@ -289,7 +287,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase public function testRcHidebyothersFilter() { $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW ); - $this->overrideMwServices(); $user = $this->getTestUser()->getUser(); $user->getActorId( wfGetDB( DB_MASTER ) ); @@ -322,7 +319,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ); - $this->overrideMwServices(); $user = $this->getTestUser()->getUser(); $user->getActorId( wfGetDB( DB_MASTER ) ); @@ -555,7 +551,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase public function testFilterUserExpLevelAllExperienceLevels() { $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW ); - $this->overrideMwServices(); $this->assertConditions( [ @@ -573,7 +568,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ); - $this->overrideMwServices(); $this->assertConditions( [ @@ -589,7 +583,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase public function testFilterUserExpLevelRegistrered() { $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW ); - $this->overrideMwServices(); $this->assertConditions( [ @@ -607,7 +600,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ); - $this->overrideMwServices(); $this->assertConditions( [ @@ -623,7 +615,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase public function testFilterUserExpLevelUnregistrered() { $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW ); - $this->overrideMwServices(); $this->assertConditions( [ @@ -641,7 +632,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ); - $this->overrideMwServices(); $this->assertConditions( [ @@ -657,7 +647,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase public function testFilterUserExpLevelRegistreredOrLearner() { $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW ); - $this->overrideMwServices(); $this->assertConditions( [ @@ -675,7 +664,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ); - $this->overrideMwServices(); $this->assertConditions( [ @@ -691,7 +679,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase public function testFilterUserExpLevelUnregistreredOrExperienced() { $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW ); - $this->overrideMwServices(); $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] ); @@ -707,7 +694,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ); - $this->overrideMwServices(); $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] ); diff --git a/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php index 04a89d2f15..c0376ad05e 100644 --- a/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php +++ b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php @@ -33,7 +33,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { $count++; } ] ] ); - $this->overrideMwServices(); $spf = MediaWikiServices::getInstance()->getSpecialPageFactory(); $spf->getNames(); $spf->getNames(); @@ -71,7 +70,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testGetPage( $spec, $shouldReuseInstance ) { $this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => $spec ] ); - $this->overrideMwServices(); $page = SpecialPageFactory::getPage( 'testdummy' ); $this->assertInstanceOf( SpecialPage::class, $page ); @@ -85,7 +83,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testGetNames() { $this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => SpecialAllPages::class ] ); - $this->overrideMwServices(); $names = SpecialPageFactory::getNames(); $this->assertInternalType( 'array', $names ); @@ -97,7 +94,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testResolveAlias() { $this->setContentLang( 'de' ); - $this->overrideMwServices(); list( $name, $param ) = SpecialPageFactory::resolveAlias( 'Spezialseiten/Foo' ); $this->assertEquals( 'Specialpages', $name ); @@ -109,7 +105,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testGetLocalNameFor() { $this->setContentLang( 'de' ); - $this->overrideMwServices(); $name = SpecialPageFactory::getLocalNameFor( 'Specialpages', 'Foo' ); $this->assertEquals( 'Spezialseiten/Foo', $name ); @@ -120,7 +115,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { */ public function testGetTitleForAlias() { $this->setContentLang( 'de' ); - $this->overrideMwServices(); $title = SpecialPageFactory::getTitleForAlias( 'Specialpages/Foo' ); $this->assertEquals( 'Spezialseiten/Foo', $title->getText() ); @@ -138,7 +132,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { $this->setMwGlobals( 'wgSpecialPages', array_combine( array_keys( $aliasesList ), array_keys( $aliasesList ) ) ); - $this->overrideMwServices(); $this->setContentLang( $lang ); // Catch the warnings we expect to be raised @@ -267,7 +260,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { } ], ] ); - $this->overrideMwServices(); SpecialPageFactory::getLocalNameFor( 'Specialpages' ); $this->assertTrue( $called, 'Recursive call succeeded' ); } diff --git a/tests/phpunit/includes/title/NamespaceInfoTest.php b/tests/phpunit/includes/title/NamespaceInfoTest.php index 7f97a16dfa..03ba960cbe 100644 --- a/tests/phpunit/includes/title/NamespaceInfoTest.php +++ b/tests/phpunit/includes/title/NamespaceInfoTest.php @@ -1275,7 +1275,6 @@ class NamespaceInfoTest extends MediaWikiTestCase { 'wgRestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ], 'wgAutopromote' => [] ] ); - $this->resetServices(); $obj = $this->newObj(); $user = is_null( $groups ) ? null : $this->getTestUser( $groups )->getUser(); $this->assertSame( $expected, $obj->getRestrictionLevels( $ns, $user ) ); diff --git a/tests/phpunit/includes/user/UserGroupMembershipTest.php b/tests/phpunit/includes/user/UserGroupMembershipTest.php index 340a4c3106..4862747b4f 100644 --- a/tests/phpunit/includes/user/UserGroupMembershipTest.php +++ b/tests/phpunit/includes/user/UserGroupMembershipTest.php @@ -46,8 +46,6 @@ class UserGroupMembershipTest extends MediaWikiTestCase { $this->userTester->addGroup( 'unittesters' ); $this->expiryTime = wfTimestamp( TS_MW, time() + 100500 ); $this->userTester->addGroup( 'testwriters', $this->expiryTime ); - - $this->resetServices(); } /** diff --git a/tests/phpunit/includes/user/UserTest.php b/tests/phpunit/includes/user/UserTest.php index 9ff052142f..2d87c78cdc 100644 --- a/tests/phpunit/includes/user/UserTest.php +++ b/tests/phpunit/includes/user/UserTest.php @@ -33,7 +33,6 @@ class UserTest extends MediaWikiTestCase { 'wgRevokePermissions' => [], 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW, ] ); - $this->overrideMwServices(); $this->setUpPermissionGlobals(); @@ -73,7 +72,6 @@ class UserTest extends MediaWikiTestCase { RequestContext::getMain()->setRequest( $request ); TestingAccessWrapper::newFromObject( $user )->mRequest = $request; $request->getSession()->setUser( $user ); - $this->overrideMwServices(); } /** @@ -136,7 +134,6 @@ class UserTest extends MediaWikiTestCase { $rights = array_diff( $rights, [ 'writetest' ] ); } ); - $this->resetServices(); $rights = $user->getRights(); $this->assertContains( 'test', $rights ); $this->assertContains( 'runtest', $rights ); @@ -1091,7 +1088,6 @@ class UserTest extends MediaWikiTestCase { $this->setMwGlobals( [ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, ] ); - $this->overrideMwServices(); $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID(); $this->hideDeprecated( 'User::selectFields' ); diff --git a/tests/phpunit/includes/watcheditem/WatchedItemStoreUnitTest.php b/tests/phpunit/includes/watcheditem/WatchedItemStoreUnitTest.php index 9616672bbd..fbb893e204 100644 --- a/tests/phpunit/includes/watcheditem/WatchedItemStoreUnitTest.php +++ b/tests/phpunit/includes/watcheditem/WatchedItemStoreUnitTest.php @@ -1837,8 +1837,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase { new WatchedItem( $user, $targets[0], '20151212010101' ), new WatchedItem( $user, $targets[1], null ), ]; - $mockDb = $this->getMockDb(); - $mockDb->expects( $this->never() )->method( $this->anything() ); + $mockDb = $this->createNoOpMock( IDatabase::class ); $mockCache = $this->getMockCache(); $mockCache->expects( $this->at( 1 ) ) @@ -1864,16 +1863,18 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase { } public function testGetNotificationTimestampsBatch_anonymousUser() { + if ( defined( 'HHVM_VERSION' ) ) { + $this->markTestSkipped( 'HHVM Reflection buggy' ); + } + $targets = [ new TitleValue( 0, 'SomeDbKey' ), new TitleValue( 1, 'AnotherDbKey' ), ]; - $mockDb = $this->getMockDb(); - $mockDb->expects( $this->never() )->method( $this->anything() ); + $mockDb = $this->createNoOpMock( IDatabase::class ); - $mockCache = $this->getMockCache(); - $mockCache->expects( $this->never() )->method( $this->anything() ); + $mockCache = $this->createNoOpMock( HashBagOStuff::class ); $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] ); @@ -2086,8 +2087,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase { $mockQueueGroup = $this->getMockJobQueueGroup(); - $mockRevisionRecord = $this->createMock( RevisionRecord::class ); - $mockRevisionRecord->expects( $this->never() )->method( $this->anything() ); + $mockRevisionRecord = $this->createNoOpMock( RevisionRecord::class ); $mockRevisionLookup = $this->getMockRevisionLookup( [ 'getTimestampFromId' => function () { @@ -2144,11 +2144,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase { $oldid = 22; $title = new TitleValue( 0, 'SomeDbKey' ); - $mockRevision = $this->createMock( RevisionRecord::class ); - $mockRevision->expects( $this->never() )->method( $this->anything() ); - - $mockNextRevision = $this->createMock( RevisionRecord::class ); - $mockNextRevision->expects( $this->never() )->method( $this->anything() ); + $mockRevision = $this->createNoOpMock( RevisionRecord::class ); + $mockNextRevision = $this->createNoOpMock( RevisionRecord::class ); $mockDb = $this->getMockDb(); $mockDb->expects( $this->once() ) @@ -2258,11 +2255,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase { $mockQueueGroup = $this->getMockJobQueueGroup(); - $mockRevision = $this->createMock( RevisionRecord::class ); - $mockRevision->expects( $this->never() )->method( $this->anything() ); - - $mockNextRevision = $this->createMock( RevisionRecord::class ); - $mockNextRevision->expects( $this->never() )->method( $this->anything() ); + $mockRevision = $this->createNoOpMock( RevisionRecord::class ); + $mockNextRevision = $this->createNoOpMock( RevisionRecord::class ); $mockRevisionLookup = $this->getMockRevisionLookup( [ @@ -2350,11 +2344,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase { $mockQueueGroup = $this->getMockJobQueueGroup(); - $mockRevision = $this->createMock( RevisionRecord::class ); - $mockRevision->expects( $this->never() )->method( $this->anything() ); - - $mockNextRevision = $this->createMock( RevisionRecord::class ); - $mockNextRevision->expects( $this->never() )->method( $this->anything() ); + $mockRevision = $this->createNoOpMock( RevisionRecord::class ); + $mockNextRevision = $this->createNoOpMock( RevisionRecord::class ); $mockRevisionLookup = $this->getMockRevisionLookup( [ @@ -2442,11 +2433,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase { $mockQueueGroup = $this->getMockJobQueueGroup(); - $mockRevision = $this->createMock( RevisionRecord::class ); - $mockRevision->expects( $this->never() )->method( $this->anything() ); - - $mockNextRevision = $this->createMock( RevisionRecord::class ); - $mockNextRevision->expects( $this->never() )->method( $this->anything() ); + $mockRevision = $this->createNoOpMock( RevisionRecord::class ); + $mockNextRevision = $this->createNoOpMock( RevisionRecord::class ); $mockRevisionLookup = $this->getMockRevisionLookup( [ diff --git a/tests/phpunit/languages/LanguageFallbackStaticMethodsTest.php b/tests/phpunit/languages/LanguageFallbackStaticMethodsTest.php new file mode 100644 index 0000000000..a683c9a9b2 --- /dev/null +++ b/tests/phpunit/languages/LanguageFallbackStaticMethodsTest.php @@ -0,0 +1,24 @@ +setMwGlobals( 'wgLanguageCode', $options['siteLangCode'] ); + $this->resetServices(); + } + return Language::class; + } + + private function getMessagesKey() { + return Language::MESSAGES_FALLBACKS; + } + + private function getStrictKey() { + return Language::STRICT_FALLBACKS; + } +} diff --git a/tests/phpunit/tests/MediaWikiTestCaseTest.php b/tests/phpunit/tests/MediaWikiTestCaseTest.php index 37fa03092d..a46f25d512 100644 --- a/tests/phpunit/tests/MediaWikiTestCaseTest.php +++ b/tests/phpunit/tests/MediaWikiTestCaseTest.php @@ -212,7 +212,6 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase { // the actual test: change config, reset services. $this->setMwGlobals( 'wgLanguageCode', 'qqx' ); - $this->resetServices(); // the overridden service instance should still be there $this->assertSame( $myReadOnlyMode, $services->getService( 'ReadOnlyMode' ) ); diff --git a/tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupFactoryTest.php b/tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupFactoryTest.php index 38fcf29363..ca39341032 100644 --- a/tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupFactoryTest.php +++ b/tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupFactoryTest.php @@ -10,8 +10,7 @@ use Wikimedia\Rdbms\LBFactory; */ class LockManagerGroupFactoryTest extends MediaWikiUnitTestCase { public function testGetLockManagerGroup() { - $mockLbFactory = $this->createMock( LBFactory::class ); - $mockLbFactory->expects( $this->never() )->method( $this->anything() ); + $mockLbFactory = $this->createNoOpMock( LBFactory::class ); $factory = new LockManagerGroupFactory( 'defaultDomain', [], $mockLbFactory ); $lbmUnspecified = $factory->getLockManagerGroup(); diff --git a/tests/phpunit/unit/includes/language/LanguageFallbackTestTrait.php b/tests/phpunit/unit/includes/language/LanguageFallbackTestTrait.php new file mode 100644 index 0000000000..b500b21ce4 --- /dev/null +++ b/tests/phpunit/unit/includes/language/LanguageFallbackTestTrait.php @@ -0,0 +1,184 @@ +getCallee( $options ); + // One behavior difference between the old static methods and the new instance methods: + // returning null instead of false. + $defaultExpected = is_object( $callee ) ? null : false; + $this->assertSame( $expected[0] ?? $defaultExpected, + $this->callMethod( $callee, 'getFallbackFor', $code ) ); + } + + /** + * @param string $code + * @param array $expected + * @param array $options + * @dataProvider provideGetFallbacksFor + * @covers ::getFallbacksFor + * @covers Language::getFallbacksFor + */ + public function testGetFallbacksFor( $code, array $expected, array $options = [] ) { + $this->assertSame( $expected, + $this->callMethod( $this->getCallee( $options ), 'getFallbacksFor', $code ) ); + } + + /** + * @param string $code + * @param array $expected + * @param array $options + * @dataProvider provideGetFallbacksFor + * @covers ::getFallbacksFor + * @covers Language::getFallbacksFor + */ + public function testGetFallbacksFor_messages( $code, array $expected, array $options = [] ) { + $this->assertSame( $expected, + $this->callMethod( $this->getCallee( $options ), 'getFallbacksFor', + $code, $this->getMessagesKey() ) ); + } + + public static function provideGetFallbacksFor() { + return [ + 'en' => [ 'en', [], [ 'expectedGets' => 0 ] ], + 'fr' => [ 'fr', [ 'en' ] ], + 'sco' => [ 'sco', [ 'en' ] ], + 'yi' => [ 'yi', [ 'he', 'en' ] ], + 'ruq' => [ 'ruq', [ 'ruq-latn', 'ro', 'en' ] ], + 'sh' => [ 'sh', [ 'bs', 'sr-el', 'hr', 'en' ] ], + ]; + } + + /** + * @param string $code + * @param array $expected + * @param array $options + * @dataProvider provideGetFallbacksFor_strict + * @covers ::getFallbacksFor + * @covers Language::getFallbacksFor + */ + public function testGetFallbacksFor_strict( $code, array $expected, array $options = [] ) { + $this->assertSame( $expected, + $this->callMethod( $this->getCallee( $options ), 'getFallbacksFor', + $code, $this->getStrictKey() ) ); + } + + public static function provideGetFallbacksFor_strict() { + return [ + 'en' => [ 'en', [], [ 'expectedGets' => 0 ] ], + 'fr' => [ 'fr', [] ], + 'sco' => [ 'sco', [ 'en' ] ], + 'yi' => [ 'yi', [ 'he' ] ], + 'ruq' => [ 'ruq', [ 'ruq-latn', 'ro' ] ], + 'sh' => [ 'sh', [ 'bs', 'sr-el', 'hr' ] ], + ]; + } + + /** + * @covers ::getFallbacksFor + * @covers Language::getFallbacksFor + */ + public function testGetFallbacksFor_exception() { + $this->setExpectedException( MWException::class, 'Invalid fallback mode "7"' ); + + $callee = $this->getCallee( [ 'expectedGets' => 0 ] ); + + // These should not throw, because of short-circuiting. If they do, it will fail the test, + // because we pass 5 and 6 instead of 7. + $this->callMethod( $callee, 'getFallbacksFor', 'en', 5 ); + $this->callMethod( $callee, 'getFallbacksFor', '!!!', 6 ); + + // This is the one that should throw. + $this->callMethod( $callee, 'getFallbacksFor', 'fr', 7 ); + } + + /** + * @param string $code + * @param string $siteLangCode + * @param array $expected + * @param int $expectedGets + * @dataProvider provideGetFallbacksIncludingSiteLanguage + * @covers ::getFallbacksIncludingSiteLanguage + * @covers Language::getFallbacksIncludingSiteLanguage + */ + public function testGetFallbacksIncludingSiteLanguage( + $code, $siteLangCode, array $expected, $expectedGets = 1 + ) { + $callee = $this->getCallee( + [ 'siteLangCode' => $siteLangCode, 'expectedGets' => $expectedGets ] ); + $this->assertSame( $expected, + $this->callMethod( $callee, 'getFallbacksIncludingSiteLanguage', $code ) ); + + // Call again to make sure we don't call LocalisationCache again + $this->callMethod( $callee, 'getFallbacksIncludingSiteLanguage', $code ); + } + + public static function provideGetFallbacksIncludingSiteLanguage() { + return [ + 'en on en' => [ 'en', 'en', [ [], [ 'en' ] ], 0 ], + 'fr on en' => [ 'fr', 'en', [ [ 'en' ], [] ] ], + 'en on fr' => [ 'en', 'fr', [ [], [ 'fr', 'en' ] ] ], + 'fr on fr' => [ 'fr', 'fr', [ [ 'en' ], [ 'fr' ] ] ], + + 'sco on en' => [ 'sco', 'en', [ [ 'en' ], [] ] ], + 'en on sco' => [ 'en', 'sco', [ [], [ 'sco', 'en' ] ] ], + 'sco on sco' => [ 'sco', 'sco', [ [ 'en' ], [ 'sco' ] ] ], + + 'fr on sco' => [ 'fr', 'sco', [ [ 'en' ], [ 'sco' ] ], 2 ], + 'sco on fr' => [ 'sco', 'fr', [ [ 'en' ], [ 'fr' ] ], 2 ], + + 'fr on yi' => [ 'fr', 'yi', [ [ 'en' ], [ 'yi', 'he' ] ], 2 ], + 'yi on fr' => [ 'yi', 'fr', [ [ 'he', 'en' ], [ 'fr' ] ], 2 ], + 'yi on yi' => [ 'yi', 'yi', [ [ 'he', 'en' ], [ 'yi' ] ] ], + + 'sh on ruq' => [ 'sh', 'ruq', + [ [ 'bs', 'sr-el', 'hr', 'en' ], [ 'ruq', 'ruq-latn', 'ro' ] ], 2 ], + 'ruq on sh' => [ 'ruq', 'sh', + [ [ 'ruq-latn', 'ro', 'en' ], [ 'sh', 'bs', 'sr-el', 'hr' ] ], 2 ], + ]; + } +}