Merge "Add clearUserWatchedItems methods to WatchedItemStoreInterface"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 13 Feb 2018 07:21:17 +0000 (07:21 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 13 Feb 2018 07:21:17 +0000 (07:21 +0000)
24 files changed:
autoload.php
includes/SiteStats.php
includes/SiteStatsInit.php [new file with mode: 0644]
includes/deferred/SiteStatsUpdate.php
includes/installer/Installer.php
includes/page/WikiPage.php
includes/registration/ExtensionJsonValidator.php
includes/registration/ExtensionRegistry.php
includes/registration/VersionChecker.php
includes/specials/SpecialLog.php
resources/src/mediawiki.special/mediawiki.special.upload.js
tests/phpunit/ResourceLoaderTestCase.php
tests/phpunit/data/registration/bad_spdx.json [new file with mode: 0644]
tests/phpunit/data/registration/good.json [new file with mode: 0644]
tests/phpunit/data/registration/invalid.json [new file with mode: 0644]
tests/phpunit/data/registration/newer_manifest_version.json [new file with mode: 0644]
tests/phpunit/data/registration/no_manifest_version.json [new file with mode: 0644]
tests/phpunit/data/registration/notjson.txt [new file with mode: 0644]
tests/phpunit/data/registration/old_manifest_version.json [new file with mode: 0644]
tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php [new file with mode: 0644]
tests/phpunit/includes/registration/ExtensionRegistryTest.php
tests/phpunit/includes/registration/VersionCheckerTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php
tests/qunit/QUnitTestResources.php

index 9618a8f..f8d1f9f 100644 (file)
@@ -1361,7 +1361,7 @@ $wgAutoloadLocalClasses = [
        'SiteLookup' => __DIR__ . '/includes/site/SiteLookup.php',
        'SiteSQLStore' => __DIR__ . '/includes/site/SiteSQLStore.php',
        'SiteStats' => __DIR__ . '/includes/SiteStats.php',
-       'SiteStatsInit' => __DIR__ . '/includes/SiteStats.php',
+       'SiteStatsInit' => __DIR__ . '/includes/SiteStatsInit.php',
        'SiteStatsUpdate' => __DIR__ . '/includes/deferred/SiteStatsUpdate.php',
        'SiteStore' => __DIR__ . '/includes/site/SiteStore.php',
        'SitesCacheFileBuilder' => __DIR__ . '/includes/site/SitesCacheFileBuilder.php',
index f10e6a2..7b2b8d3 100644 (file)
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
 use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\LoadBalancer;
 
 /**
  * Static accessor class for site_stats and related things
  */
 class SiteStats {
-       /** @var bool|stdClass */
+       /** @var stdClass */
        private static $row;
 
-       /** @var bool */
-       private static $loaded = false;
-       /** @var int[] */
-       private static $pageCount = [];
-
-       static function unload() {
-               self::$loaded = false;
-       }
-
-       static function recache() {
-               self::load( true );
-       }
-
        /**
-        * @param bool $recache
+        * Trigger a reload next time a field is accessed
         */
-       static function load( $recache = false ) {
-               if ( self::$loaded && !$recache ) {
-                       return;
-               }
-
-               self::$row = self::loadAndLazyInit();
+       public static function unload() {
+               self::$row = null;
+       }
 
-               self::$loaded = true;
+       protected static function load() {
+               if ( self::$row === null ) {
+                       self::$row = self::loadAndLazyInit();
+               }
        }
 
        /**
-        * @return bool|stdClass
+        * @return stdClass
         */
-       static function loadAndLazyInit() {
-               global $wgMiserMode;
+       protected static function loadAndLazyInit() {
+               $config = MediaWikiServices::getInstance()->getMainConfig();
 
+               $lb = self::getLB();
+               $dbr = $lb->getConnection( DB_REPLICA );
                wfDebug( __METHOD__ . ": reading site_stats from replica DB\n" );
-               $row = self::doLoad( wfGetDB( DB_REPLICA ) );
+               $row = self::doLoadFromDB( $dbr );
 
-               if ( !self::isSane( $row ) ) {
-                       $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
-                       if ( $lb->hasOrMadeRecentMasterChanges() ) {
-                               // Might have just been initialized during this request? Underflow?
-                               wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB\n" );
-                               $row = self::doLoad( wfGetDB( DB_MASTER ) );
-                       }
+               if ( !self::isSane( $row ) && $lb->hasOrMadeRecentMasterChanges() ) {
+                       // Might have just been initialized during this request? Underflow?
+                       wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB\n" );
+                       $row = self::doLoadFromDB( $lb->getConnection( DB_MASTER ) );
                }
 
                if ( !self::isSane( $row ) ) {
-                       if ( $wgMiserMode ) {
+                       if ( $config->get( 'MiserMode' ) ) {
                                // Start off with all zeroes, assuming that this is a new wiki or any
                                // repopulations where done manually via script.
                                SiteStatsInit::doPlaceholderInit();
@@ -86,15 +73,15 @@ class SiteStats {
                                // broken, however, for instance when importing from a dump into a
                                // clean schema with mwdumper.
                                wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" );
-                               SiteStatsInit::doAllAndCommit( wfGetDB( DB_REPLICA ) );
+                               SiteStatsInit::doAllAndCommit( $dbr );
                        }
 
-                       $row = self::doLoad( wfGetDB( DB_MASTER ) );
+                       $row = self::doLoadFromDB( $lb->getConnection( DB_MASTER ) );
                }
 
                if ( !self::isSane( $row ) ) {
                        wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" );
-
+                       // Always return a row-like object
                        $row = (object)array_fill_keys( self::selectFields(), 0 );
                }
 
@@ -103,70 +90,68 @@ class SiteStats {
 
        /**
         * @param IDatabase $db
-        * @return bool|stdClass
-        */
-       static function doLoad( $db ) {
-               return $db->selectRow( 'site_stats', self::selectFields(), [], __METHOD__ );
-       }
-
-       /**
-        * Return the total number of page views. Except we don't track those anymore.
-        * Stop calling this function, it will be removed some time in the future. It's
-        * kept here simply to prevent fatal errors.
-        *
-        * @deprecated since 1.25
-        * @return int
+        * @return stdClass|bool
         */
-       static function views() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return 0;
+       private static function doLoadFromDB( IDatabase $db ) {
+               return $db->selectRow(
+                       'site_stats',
+                       self::selectFields(),
+                       [ 'ss_row_id' => 1 ],
+                       __METHOD__
+               );
        }
 
        /**
         * @return int
         */
-       static function edits() {
+       public static function edits() {
                self::load();
+
                return self::$row->ss_total_edits;
        }
 
        /**
         * @return int
         */
-       static function articles() {
+       public static function articles() {
                self::load();
+
                return self::$row->ss_good_articles;
        }
 
        /**
         * @return int
         */
-       static function pages() {
+       public static function pages() {
                self::load();
+
                return self::$row->ss_total_pages;
        }
 
        /**
         * @return int
         */
-       static function users() {
+       public static function users() {
                self::load();
+
                return self::$row->ss_users;
        }
 
        /**
         * @return int
         */
-       static function activeUsers() {
+       public static function activeUsers() {
                self::load();
+
                return self::$row->ss_active_users;
        }
 
        /**
         * @return int
         */
-       static function images() {
+       public static function images() {
                self::load();
+
                return self::$row->ss_images;
        }
 
@@ -175,17 +160,17 @@ class SiteStats {
         * @param string $group Name of group
         * @return int
         */
-       static function numberingroup( $group ) {
+       public static function numberingroup( $group ) {
                $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+
                return $cache->getWithSetCallback(
                        $cache->makeKey( 'SiteStats', 'groupcounts', $group ),
                        $cache::TTL_HOUR,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $group ) {
-                               $dbr = wfGetDB( DB_REPLICA );
-
+                               $dbr = self::getLB()->getConnection( DB_REPLICA );
                                $setOpts += Database::getCacheSetOptions( $dbr );
 
-                               return $dbr->selectField(
+                               return (int)$dbr->selectField(
                                        'user_groups',
                                        'COUNT(*)',
                                        [
@@ -203,8 +188,9 @@ class SiteStats {
         * Total number of jobs in the job queue.
         * @return int
         */
-       static function jobs() {
+       public static function jobs() {
                $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+
                return $cache->getWithSetCallback(
                        $cache->makeKey( 'SiteStats', 'jobscount' ),
                        $cache::TTL_MINUTE,
@@ -222,20 +208,27 @@ class SiteStats {
 
        /**
         * @param int $ns
-        *
         * @return int
         */
-       static function pagesInNs( $ns ) {
-               if ( !isset( self::$pageCount[$ns] ) ) {
-                       $dbr = wfGetDB( DB_REPLICA );
-                       self::$pageCount[$ns] = (int)$dbr->selectField(
-                               'page',
-                               'COUNT(*)',
-                               [ 'page_namespace' => $ns ],
-                               __METHOD__
-                       );
-               }
-               return self::$pageCount[$ns];
+       public static function pagesInNs( $ns ) {
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+
+               return $cache->getWithSetCallback(
+                       $cache->makeKey( 'SiteStats', 'page-in-namespace', $ns ),
+                       $cache::TTL_HOUR,
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $ns ) {
+                               $dbr = self::getLB()->getConnection( DB_REPLICA );
+                               $setOpts += Database::getCacheSetOptions( $dbr );
+
+                               return (int)$dbr->selectField(
+                                       'page',
+                                       'COUNT(*)',
+                                       [ 'page_namespace' => $ns ],
+                                       __METHOD__
+                               );
+                       },
+                       [ 'pcTTL' => $cache::TTL_PROC_LONG ]
+               );
        }
 
        /**
@@ -243,7 +236,6 @@ class SiteStats {
         */
        public static function selectFields() {
                return [
-                       'ss_row_id',
                        'ss_total_edits',
                        'ss_good_articles',
                        'ss_total_pages',
@@ -259,7 +251,6 @@ class SiteStats {
         * Checks only fields which are filled by SiteStatsInit::refresh.
         *
         * @param bool|object $row
-        *
         * @return bool
         */
        private static function isSane( $row ) {
@@ -281,163 +272,14 @@ class SiteStats {
                                return false;
                        }
                }
-               return true;
-       }
-}
-
-/**
- * Class designed for counting of stats.
- */
-class SiteStatsInit {
-
-       // Database connection
-       private $db;
-
-       // Various stats
-       private $mEdits = null, $mArticles = null, $mPages = null;
-       private $mUsers = null, $mFiles = null;
-
-       /**
-        * @param bool|IDatabase $database
-        * - bool: Whether to use the master DB
-        * - IDatabase: Database connection to use
-        */
-       public function __construct( $database = false ) {
-               if ( $database instanceof IDatabase ) {
-                       $this->db = $database;
-               } elseif ( $database ) {
-                       $this->db = wfGetDB( DB_MASTER );
-               } else {
-                       $this->db = wfGetDB( DB_REPLICA, 'vslow' );
-               }
-       }
-
-       /**
-        * Count the total number of edits
-        * @return int
-        */
-       public function edits() {
-               $this->mEdits = $this->db->selectField( 'revision', 'COUNT(*)', '', __METHOD__ );
-               $this->mEdits += $this->db->selectField( 'archive', 'COUNT(*)', '', __METHOD__ );
-               return $this->mEdits;
-       }
-
-       /**
-        * Count pages in article space(s)
-        * @return int
-        */
-       public function articles() {
-               global $wgArticleCountMethod;
-
-               $tables = [ 'page' ];
-               $conds = [
-                       'page_namespace' => MWNamespace::getContentNamespaces(),
-                       'page_is_redirect' => 0,
-               ];
-
-               if ( $wgArticleCountMethod == 'link' ) {
-                       $tables[] = 'pagelinks';
-                       $conds[] = 'pl_from=page_id';
-               } elseif ( $wgArticleCountMethod == 'comma' ) {
-                       // To make a correct check for this, we would need, for each page,
-                       // to load the text, maybe uncompress it, maybe decode it and then
-                       // check if there's one comma.
-                       // But one thing we are sure is that if the page is empty, it can't
-                       // contain a comma :)
-                       $conds[] = 'page_len > 0';
-               }
-
-               $this->mArticles = $this->db->selectField( $tables, 'COUNT(DISTINCT page_id)',
-                       $conds, __METHOD__ );
-               return $this->mArticles;
-       }
 
-       /**
-        * Count total pages
-        * @return int
-        */
-       public function pages() {
-               $this->mPages = $this->db->selectField( 'page', 'COUNT(*)', '', __METHOD__ );
-               return $this->mPages;
-       }
-
-       /**
-        * Count total users
-        * @return int
-        */
-       public function users() {
-               $this->mUsers = $this->db->selectField( 'user', 'COUNT(*)', '', __METHOD__ );
-               return $this->mUsers;
-       }
-
-       /**
-        * Count total files
-        * @return int
-        */
-       public function files() {
-               $this->mFiles = $this->db->selectField( 'image', 'COUNT(*)', '', __METHOD__ );
-               return $this->mFiles;
-       }
-
-       /**
-        * Do all updates and commit them. More or less a replacement
-        * for the original initStats, but without output.
-        *
-        * @param IDatabase|bool $database
-        * - bool: Whether to use the master DB
-        * - IDatabase: Database connection to use
-        * @param array $options Array of options, may contain the following values
-        * - activeUsers bool: Whether to update the number of active users (default: false)
-        */
-       public static function doAllAndCommit( $database, array $options = [] ) {
-               $options += [ 'update' => false, 'activeUsers' => false ];
-
-               // Grab the object and count everything
-               $counter = new SiteStatsInit( $database );
-
-               $counter->edits();
-               $counter->articles();
-               $counter->pages();
-               $counter->users();
-               $counter->files();
-
-               $counter->refresh();
-
-               // Count active users if need be
-               if ( $options['activeUsers'] ) {
-                       SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) );
-               }
-       }
-
-       /**
-        * Insert a dummy row with all zeroes if no row is present
-        */
-       public static function doPlaceholderInit() {
-               $dbw = wfGetDB( DB_MASTER );
-               if ( $dbw->selectRow( 'site_stats', '1', [], __METHOD__ ) === false ) {
-                       $dbw->insert(
-                               'site_stats',
-                               array_fill_keys( SiteStats::selectFields(), 0 ),
-                               __METHOD__,
-                               [ 'IGNORE' ]
-                       );
-               }
+               return true;
        }
 
        /**
-        * Refresh site_stats
+        * @return LoadBalancer
         */
-       public function refresh() {
-               $values = [
-                       'ss_row_id' => 1,
-                       'ss_total_edits' => ( $this->mEdits === null ? $this->edits() : $this->mEdits ),
-                       'ss_good_articles' => ( $this->mArticles === null ? $this->articles() : $this->mArticles ),
-                       'ss_total_pages' => ( $this->mPages === null ? $this->pages() : $this->mPages ),
-                       'ss_users' => ( $this->mUsers === null ? $this->users() : $this->mUsers ),
-                       'ss_images' => ( $this->mFiles === null ? $this->files() : $this->mFiles ),
-               ];
-
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->upsert( 'site_stats', $values, [ 'ss_row_id' ], $values, __METHOD__ );
+       private static function getLB() {
+               return MediaWikiServices::getInstance()->getDBLoadBalancer();
        }
 }
diff --git a/includes/SiteStatsInit.php b/includes/SiteStatsInit.php
new file mode 100644 (file)
index 0000000..f888690
--- /dev/null
@@ -0,0 +1,206 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+use Wikimedia\Rdbms\IDatabase;
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Class designed for counting of stats.
+ */
+class SiteStatsInit {
+       /* @var IDatabase */
+       private $dbr;
+       /** @var int */
+       private $edits;
+       /** @var int */
+       private $articles;
+       /** @var int */
+       private $pages;
+       /** @var int */
+       private $users;
+       /** @var int */
+       private $files;
+
+       /**
+        * @param bool|IDatabase $database
+        * - bool: Whether to use the master DB
+        * - IDatabase: Database connection to use
+        */
+       public function __construct( $database = false ) {
+               if ( $database instanceof IDatabase ) {
+                       $this->dbr = $database;
+               } elseif ( $database ) {
+                       $this->dbr = self::getDB( DB_MASTER );
+               } else {
+                       $this->dbr = self::getDB( DB_REPLICA, 'vslow' );
+               }
+       }
+
+       /**
+        * Count the total number of edits
+        * @return int
+        */
+       public function edits() {
+               $this->edits = $this->dbr->selectField( 'revision', 'COUNT(*)', '', __METHOD__ );
+               $this->edits += $this->dbr->selectField( 'archive', 'COUNT(*)', '', __METHOD__ );
+
+               return $this->edits;
+       }
+
+       /**
+        * Count pages in article space(s)
+        * @return int
+        */
+       public function articles() {
+               $config = MediaWikiServices::getInstance()->getMainConfig();
+
+               $tables = [ 'page' ];
+               $conds = [
+                       'page_namespace' => MWNamespace::getContentNamespaces(),
+                       'page_is_redirect' => 0,
+               ];
+
+               if ( $config->get( 'ArticleCountMethod' ) == 'link' ) {
+                       $tables[] = 'pagelinks';
+                       $conds[] = 'pl_from=page_id';
+               } elseif ( $config->get( 'ArticleCountMethod' ) == 'comma' ) {
+                       // To make a correct check for this, we would need, for each page,
+                       // to load the text, maybe uncompress it, maybe decode it and then
+                       // check if there's one comma.
+                       // But one thing we are sure is that if the page is empty, it can't
+                       // contain a comma :)
+                       $conds[] = 'page_len > 0';
+               }
+
+               $this->articles = $this->dbr->selectField(
+                       $tables,
+                       'COUNT(DISTINCT page_id)',
+                       $conds,
+                       __METHOD__
+               );
+
+               return $this->articles;
+       }
+
+       /**
+        * Count total pages
+        * @return int
+        */
+       public function pages() {
+               $this->pages = $this->dbr->selectField( 'page', 'COUNT(*)', '', __METHOD__ );
+
+               return $this->pages;
+       }
+
+       /**
+        * Count total users
+        * @return int
+        */
+       public function users() {
+               $this->users = $this->dbr->selectField( 'user', 'COUNT(*)', '', __METHOD__ );
+
+               return $this->users;
+       }
+
+       /**
+        * Count total files
+        * @return int
+        */
+       public function files() {
+               $this->files = $this->dbr->selectField( 'image', 'COUNT(*)', '', __METHOD__ );
+
+               return $this->files;
+       }
+
+       /**
+        * Do all updates and commit them. More or less a replacement
+        * for the original initStats, but without output.
+        *
+        * @param IDatabase|bool $database
+        * - bool: Whether to use the master DB
+        * - IDatabase: Database connection to use
+        * @param array $options Array of options, may contain the following values
+        * - activeUsers bool: Whether to update the number of active users (default: false)
+        */
+       public static function doAllAndCommit( $database, array $options = [] ) {
+               $options += [ 'update' => false, 'activeUsers' => false ];
+
+               // Grab the object and count everything
+               $counter = new self( $database );
+
+               $counter->edits();
+               $counter->articles();
+               $counter->pages();
+               $counter->users();
+               $counter->files();
+
+               $counter->refresh();
+
+               // Count active users if need be
+               if ( $options['activeUsers'] ) {
+                       SiteStatsUpdate::cacheUpdate( self::getDB( DB_MASTER ) );
+               }
+       }
+
+       /**
+        * Insert a dummy row with all zeroes if no row is present
+        */
+       public static function doPlaceholderInit() {
+               $dbw = self::getDB( DB_MASTER );
+               $exists = $dbw->selectField( 'site_stats', '1', [ 'ss_row_id' => 1 ],  __METHOD__ );
+               if ( $exists === false ) {
+                       $dbw->insert(
+                               'site_stats',
+                               [ 'ss_row_id' => 1 ] + array_fill_keys( SiteStats::selectFields(), 0 ),
+                               __METHOD__,
+                               [ 'IGNORE' ]
+                       );
+               }
+       }
+
+       /**
+        * Refresh site_stats
+        */
+       public function refresh() {
+               $values = [
+                       'ss_row_id' => 1,
+                       'ss_total_edits' => $this->edits === null ? $this->edits() : $this->edits,
+                       'ss_good_articles' => $this->articles === null ? $this->articles() : $this->articles,
+                       'ss_total_pages' => $this->pages === null ? $this->pages() : $this->pages,
+                       'ss_users' => $this->users === null ? $this->users() : $this->users,
+                       'ss_images' => $this->files === null ? $this->files() : $this->files,
+               ];
+
+               self::getDB( DB_MASTER )->upsert(
+                       'site_stats',
+                       $values,
+                       [ 'ss_row_id' ],
+                       $values,
+                       __METHOD__
+               );
+       }
+
+       /**
+        * @param int $index
+        * @return IDatabase
+        */
+       private static function getDB( $index ) {
+               return MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( $index );
+       }
+}
index 44876a6..ad1f172 100644 (file)
@@ -25,6 +25,8 @@ use Wikimedia\Rdbms\IDatabase;
  * Class for handling updates to the site_stats table
  */
 class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
+       /** @var BagOStuff */
+       protected $stash;
        /** @var int */
        protected $edits = 0;
        /** @var int */
@@ -44,6 +46,8 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
                $this->articles = $good;
                $this->pages = $pages;
                $this->users = $users;
+
+               $this->stash = MediaWikiServices::getInstance()->getMainObjectStash();
        }
 
        public function merge( MergeableUpdate $update ) {
@@ -72,11 +76,9 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
        }
 
        public function doUpdate() {
-               global $wgSiteStatsAsyncFactor;
-
                $this->doUpdateContextStats();
 
-               $rate = $wgSiteStatsAsyncFactor; // convenience
+               $rate = MediaWikiServices::getInstance()->getMainConfig()->get( 'SiteStatsAsyncFactor' );
                // If set to do so, only do actual DB updates 1 every $rate times.
                // The other times, just update "pending delta" values in memcached.
                if ( $rate && ( $rate < 0 || mt_rand( 0, $rate - 1 ) != 0 ) ) {
@@ -91,12 +93,13 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
         * Do not call this outside of SiteStatsUpdate
         */
        public function tryDBUpdateInternal() {
-               global $wgSiteStatsAsyncFactor;
+               $services = MediaWikiServices::getInstance();
+               $config = $services->getMainConfig();
 
-               $dbw = wfGetDB( DB_MASTER );
-               $lockKey = wfWikiID() . ':site_stats'; // prepend wiki ID
+               $dbw = $services->getDBLoadBalancer()->getConnection( DB_MASTER );
+               $lockKey = $dbw->getDomainID() . ':site_stats'; // prepend wiki ID
                $pd = [];
-               if ( $wgSiteStatsAsyncFactor ) {
+               if ( $config->get( 'SiteStatsAsyncFactor' ) ) {
                        // Lock the table so we don't have double DB/memcached updates
                        if ( !$dbw->lockIsFree( $lockKey, __METHOD__ )
                                || !$dbw->lock( $lockKey, __METHOD__, 1 ) // 1 sec timeout
@@ -125,7 +128,7 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
                        $dbw->update( 'site_stats', [ $updates ], [], __METHOD__ );
                }
 
-               if ( $wgSiteStatsAsyncFactor ) {
+               if ( $config->get( 'SiteStatsAsyncFactor' ) ) {
                        // Decrement the async deltas now that we applied them
                        $this->removePendingDeltas( $pd );
                        // Commit the updates and unlock the table
@@ -140,9 +143,11 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
         * @param IDatabase $dbw
         * @return bool|mixed
         */
-       public static function cacheUpdate( $dbw ) {
-               global $wgActiveUserDays;
-               $dbr = wfGetDB( DB_REPLICA, 'vslow' );
+       public static function cacheUpdate( IDatabase $dbw ) {
+               $services = MediaWikiServices::getInstance();
+               $config = $services->getMainConfig();
+
+               $dbr = $services->getDBLoadBalancer()->getConnection( DB_REPLICA, 'vslow' );
                # Get non-bot users than did some recent action other than making accounts.
                # If account creation is included, the number gets inflated ~20+ fold on enwiki.
                $activeUsers = $dbr->selectField(
@@ -153,8 +158,8 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
                                'rc_user != 0',
                                'rc_bot' => 0,
                                'rc_log_type != ' . $dbr->addQuotes( 'newusers' ) . ' OR rc_log_type IS NULL',
-                               'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( wfTimestamp( TS_UNIX )
-                                       - $wgActiveUserDays * 24 * 3600 ) ),
+                               'rc_timestamp >= ' . $dbr->addQuotes(
+                                       $dbr->timestamp( time() - $config->get( 'ActiveUserDays' ) * 24 * 3600 ) ),
                        ],
                        __METHOD__
                );
@@ -208,13 +213,13 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
        }
 
        /**
-        * @param BagOStuff $cache
+        * @param BagOStuff $stash
         * @param string $type
         * @param string $sign ('+' or '-')
         * @return string
         */
-       private function getTypeCacheKey( BagOStuff $cache, $type, $sign ) {
-               return $cache->makeKey( 'sitestatsupdate', 'pendingdelta', $type, $sign );
+       private function getTypeCacheKey( BagOStuff $stash, $type, $sign ) {
+               return $stash->makeKey( 'sitestatsupdate', 'pendingdelta', $type, $sign );
        }
 
        /**
@@ -224,15 +229,14 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
         * @param int $delta Delta (positive or negative)
         */
        protected function adjustPending( $type, $delta ) {
-               $cache = MediaWikiServices::getInstance()->getMainObjectStash();
                if ( $delta < 0 ) { // decrement
-                       $key = $this->getTypeCacheKey( $cache, $type, '-' );
+                       $key = $this->getTypeCacheKey( $this->stash, $type, '-' );
                } else { // increment
-                       $key = $this->getTypeCacheKey( $cache, $type, '+' );
+                       $key = $this->getTypeCacheKey( $this->stash, $type, '+' );
                }
 
                $magnitude = abs( $delta );
-               $cache->incrWithInit( $key, 0, $magnitude, $magnitude );
+               $this->stash->incrWithInit( $key, 0, $magnitude, $magnitude );
        }
 
        /**
@@ -240,16 +244,20 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
         * @return array Positive and negative deltas for each type
         */
        protected function getPendingDeltas() {
-               $cache = MediaWikiServices::getInstance()->getMainObjectStash();
-
                $pending = [];
                foreach ( [ 'ss_total_edits',
                        'ss_good_articles', 'ss_total_pages', 'ss_users', 'ss_images' ] as $type
                ) {
                        // Get pending increments and pending decrements
                        $flg = BagOStuff::READ_LATEST;
-                       $pending[$type]['+'] = (int)$cache->get( $this->getTypeCacheKey( $cache, $type, '+' ), $flg );
-                       $pending[$type]['-'] = (int)$cache->get( $this->getTypeCacheKey( $cache, $type, '-' ), $flg );
+                       $pending[$type]['+'] = (int)$this->stash->get(
+                               $this->getTypeCacheKey( $this->stash, $type, '+' ),
+                               $flg
+                       );
+                       $pending[$type]['-'] = (int)$this->stash->get(
+                               $this->getTypeCacheKey( $this->stash, $type, '-' ),
+                               $flg
+                       );
                }
 
                return $pending;
@@ -260,12 +268,11 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
         * @param array $pd Result of getPendingDeltas(), used for DB update
         */
        protected function removePendingDeltas( array $pd ) {
-               $cache = MediaWikiServices::getInstance()->getMainObjectStash();
-
                foreach ( $pd as $type => $deltas ) {
                        foreach ( $deltas as $sign => $magnitude ) {
                                // Lower the pending counter now that we applied these changes
-                               $cache->decr( $this->getTypeCacheKey( $cache, $type, $sign ), $magnitude );
+                               $key = $this->getTypeCacheKey( $this->stash, $type, $sign );
+                               $this->stash->decr( $key, $magnitude );
                        }
                }
        }
index f22b63d..dbd143c 100644 (file)
@@ -1564,7 +1564,7 @@ abstract class Installer {
                        $user->saveSettings();
 
                        // Update user count
-                       $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
+                       $ssUpdate = SiteStatsUpdate::factory( [ 'users' => 1 ] );
                        $ssUpdate->doUpdate();
                }
                $status = Status::newGood();
index a7eed5a..c7bb8ec 100644 (file)
@@ -2271,7 +2271,9 @@ class WikiPage implements Page, IDBAccessObject {
                $edits = $options['changed'] ? 1 : 0;
                $total = $options['created'] ? 1 : 0;
 
-               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, $edits, $good, $total ) );
+               DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
+                       [ 'edits' => $edits, 'articles' => $good, 'total' => $total ]
+               ) );
                DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) );
 
                // If this is another user's talk page, update newtalk.
@@ -3018,7 +3020,9 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                // Update site status
-               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$countable, -1 ) );
+               DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
+                       [ 'edits' => 1, 'articles' => -$countable, 'pages' => -1 ]
+               ) );
 
                // Delete pagelinks, update secondary indexes, etc
                $updates = $this->getDeletionUpdates( $content );
index c8e5e19..7e3afaa 100644 (file)
@@ -40,6 +40,7 @@ class ExtensionJsonValidator {
        }
 
        /**
+        * @codeCoverageIgnore
         * @return bool
         */
        public function checkDependencies() {
index bb4c7fd..1876645 100644 (file)
@@ -82,6 +82,7 @@ class ExtensionRegistry {
        private static $instance;
 
        /**
+        * @codeCoverageIgnore
         * @return ExtensionRegistry
         */
        public static function getInstance() {
@@ -105,9 +106,11 @@ class ExtensionRegistry {
                        } else {
                                throw new Exception( "$path does not exist!" );
                        }
+                       // @codeCoverageIgnoreStart
                        if ( !$mtime ) {
                                $err = error_get_last();
                                throw new Exception( "Couldn't stat $path: {$err['message']}" );
+                               // @codeCoverageIgnoreEnd
                        }
                }
                $this->queued[$path] = $mtime;
@@ -406,16 +409,6 @@ class ExtensionRegistry {
                return $this->loaded;
        }
 
-       /**
-        * Mark a thing as loaded
-        *
-        * @param string $name
-        * @param array $credits
-        */
-       protected function markLoaded( $name, array $credits ) {
-               $this->loaded[$name] = $credits;
-       }
-
        /**
         * Fully expand autoloader paths
         *
index a31551c..02e3a7c 100644 (file)
@@ -175,13 +175,13 @@ class VersionChecker {
                if ( !isset( $this->loaded[$dependencyName]['version'] ) ) {
                        // If we depend upon any version, and none is set, that's fine.
                        if ( $constraint === '*' ) {
-                               wfDebug( "{$dependencyName} does not expose it's version, but {$checkedExt}
-                                       mentions it with constraint '*'. Assume it's ok so." );
+                               wfDebug( "{$dependencyName} does not expose its version, but {$checkedExt}"
+                                       . " mentions it with constraint '*'. Assume it's ok so." );
                                return false;
                        } else {
                                // Otherwise, mark it as incompatible.
-                               return "{$dependencyName} does not expose it's version, but {$checkedExt}
-                                       requires: {$constraint}.";
+                               return "{$dependencyName} does not expose its version, but {$checkedExt}"
+                                       . " requires: {$constraint}.";
                        }
                } else {
                        // Try to get a constraint for the dependency version
index 10f4864..cc43580 100644 (file)
@@ -82,7 +82,7 @@ class SpecialLog extends SpecialPage {
                        if ( $offender ) {
                                if ( $offender->getId() > 0 ) {
                                        $qc = [ 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() ];
-                               } elseif ( empty( $opts->getValue( 'offender' ) ) === false ) {
+                               } elseif ( !empty( $opts->getValue( 'offender' ) ) ) {
                                        $qc = [ 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() ];
                                }
                        }
index 9af7ab2..57578a6 100644 (file)
@@ -6,7 +6,6 @@
  * @singleton
  */
 
-/* eslint-disable no-use-before-define */
 /* global Uint8Array */
 
 ( function ( mw, $ ) {
                        return mw.msg( sizeMsgs[ 0 ], Math.round( s ) );
                }
 
+               /**
+                * Start loading a file into memory; when complete, pass it as a
+                * data URL to the callback function. If the callbackBinary is set it will
+                * first be read as binary and afterwards as data URL. Useful if you want
+                * to do preprocessing on the binary data first.
+                *
+                * @param {File} file
+                * @param {Function} callback
+                * @param {Function} callbackBinary
+                */
+               function fetchPreview( file, callback, callbackBinary ) {
+                       var reader = new FileReader();
+                       if ( callbackBinary && 'readAsBinaryString' in reader ) {
+                               // To fetch JPEG metadata we need a binary string; start there.
+                               // TODO
+                               reader.onload = function () {
+                                       callbackBinary( reader.result );
+
+                                       // Now run back through the regular code path.
+                                       fetchPreview( file, callback );
+                               };
+                               reader.readAsBinaryString( file );
+                       } else if ( callbackBinary && 'readAsArrayBuffer' in reader ) {
+                               // readAsArrayBuffer replaces readAsBinaryString
+                               // However, our JPEG metadata library wants a string.
+                               // So, this is going to be an ugly conversion.
+                               reader.onload = function () {
+                                       var i,
+                                               buffer = new Uint8Array( reader.result ),
+                                               string = '';
+                                       for ( i = 0; i < buffer.byteLength; i++ ) {
+                                               string += String.fromCharCode( buffer[ i ] );
+                                       }
+                                       callbackBinary( string );
+
+                                       // Now run back through the regular code path.
+                                       fetchPreview( file, callback );
+                               };
+                               reader.readAsArrayBuffer( file );
+                       } else if ( 'URL' in window && 'createObjectURL' in window.URL ) {
+                               // Supported in Firefox 4.0 and above <https://developer.mozilla.org/en/DOM/window.URL.createObjectURL>
+                               // WebKit has it in a namespace for now but that's ok. ;)
+                               //
+                               // Lifetime of this URL is until document close, which is fine
+                               // for Special:Upload -- if this code gets used on longer-running
+                               // pages, add a revokeObjectURL() when it's no longer needed.
+                               //
+                               // Prefer this over readAsDataURL for Firefox 7 due to bug reading
+                               // some SVG files from data URIs <https://bugzilla.mozilla.org/show_bug.cgi?id=694165>
+                               callback( window.URL.createObjectURL( file ) );
+                       } else {
+                               // This ends up decoding the file to base-64 and back again, which
+                               // feels horribly inefficient.
+                               reader.onload = function () {
+                                       callback( reader.result );
+                               };
+                               reader.readAsDataURL( file );
+                       }
+               }
+
+               /**
+                * Clear the file upload preview area.
+                */
+               function clearPreview() {
+                       $( '#mw-upload-thumbnail' ).remove();
+               }
+
                /**
                 * Show a thumbnail preview of PNG, JPEG, GIF, and SVG files prior to upload
                 * in browsers supporting HTML5 FileAPI.
                        } : null );
                }
 
-               /**
-                * Start loading a file into memory; when complete, pass it as a
-                * data URL to the callback function. If the callbackBinary is set it will
-                * first be read as binary and afterwards as data URL. Useful if you want
-                * to do preprocessing on the binary data first.
-                *
-                * @param {File} file
-                * @param {Function} callback
-                * @param {Function} callbackBinary
-                */
-               function fetchPreview( file, callback, callbackBinary ) {
-                       var reader = new FileReader();
-                       if ( callbackBinary && 'readAsBinaryString' in reader ) {
-                               // To fetch JPEG metadata we need a binary string; start there.
-                               // TODO
-                               reader.onload = function () {
-                                       callbackBinary( reader.result );
-
-                                       // Now run back through the regular code path.
-                                       fetchPreview( file, callback );
-                               };
-                               reader.readAsBinaryString( file );
-                       } else if ( callbackBinary && 'readAsArrayBuffer' in reader ) {
-                               // readAsArrayBuffer replaces readAsBinaryString
-                               // However, our JPEG metadata library wants a string.
-                               // So, this is going to be an ugly conversion.
-                               reader.onload = function () {
-                                       var i,
-                                               buffer = new Uint8Array( reader.result ),
-                                               string = '';
-                                       for ( i = 0; i < buffer.byteLength; i++ ) {
-                                               string += String.fromCharCode( buffer[ i ] );
-                                       }
-                                       callbackBinary( string );
-
-                                       // Now run back through the regular code path.
-                                       fetchPreview( file, callback );
-                               };
-                               reader.readAsArrayBuffer( file );
-                       } else if ( 'URL' in window && 'createObjectURL' in window.URL ) {
-                               // Supported in Firefox 4.0 and above <https://developer.mozilla.org/en/DOM/window.URL.createObjectURL>
-                               // WebKit has it in a namespace for now but that's ok. ;)
-                               //
-                               // Lifetime of this URL is until document close, which is fine
-                               // for Special:Upload -- if this code gets used on longer-running
-                               // pages, add a revokeObjectURL() when it's no longer needed.
-                               //
-                               // Prefer this over readAsDataURL for Firefox 7 due to bug reading
-                               // some SVG files from data URIs <https://bugzilla.mozilla.org/show_bug.cgi?id=694165>
-                               callback( window.URL.createObjectURL( file ) );
-                       } else {
-                               // This ends up decoding the file to base-64 and back again, which
-                               // feels horribly inefficient.
-                               reader.onload = function () {
-                                       callback( reader.result );
-                               };
-                               reader.readAsDataURL( file );
-                       }
-               }
-
-               /**
-                * Clear the file upload preview area.
-                */
-               function clearPreview() {
-                       $( '#mw-upload-thumbnail' ).remove();
-               }
-
                /**
                 * Check if the file does not exceed the maximum size
                 *
index 9719862..514150c 100644 (file)
@@ -87,7 +87,6 @@ class ResourceLoaderTestModule extends ResourceLoaderModule {
        protected $dependencies = [];
        protected $group = null;
        protected $source = 'local';
-       protected $position = 'bottom';
        protected $script = '';
        protected $styles = '';
        protected $skipFunction = null;
@@ -126,9 +125,6 @@ class ResourceLoaderTestModule extends ResourceLoaderModule {
        public function getSource() {
                return $this->source;
        }
-       public function getPosition() {
-               return $this->position;
-       }
 
        public function getType() {
                return $this->type;
diff --git a/tests/phpunit/data/registration/bad_spdx.json b/tests/phpunit/data/registration/bad_spdx.json
new file mode 100644 (file)
index 0000000..383ab04
--- /dev/null
@@ -0,0 +1,6 @@
+{
+       "name": "FooBar",
+       "license-name": "not a license identifier",
+       "manifest_version": 1
+}
+
diff --git a/tests/phpunit/data/registration/good.json b/tests/phpunit/data/registration/good.json
new file mode 100644 (file)
index 0000000..ad16c5e
--- /dev/null
@@ -0,0 +1,4 @@
+{
+       "name": "FooBar",
+       "manifest_version": 1
+}
diff --git a/tests/phpunit/data/registration/invalid.json b/tests/phpunit/data/registration/invalid.json
new file mode 100644 (file)
index 0000000..4d1fa58
--- /dev/null
@@ -0,0 +1,5 @@
+{
+       "name": "FooBar",
+       "license-name": [ "array" ],
+       "manifest_version": 1
+}
diff --git a/tests/phpunit/data/registration/newer_manifest_version.json b/tests/phpunit/data/registration/newer_manifest_version.json
new file mode 100644 (file)
index 0000000..29c668e
--- /dev/null
@@ -0,0 +1,4 @@
+{
+       "name": "FooBar",
+       "manifest_version": 999999
+}
diff --git a/tests/phpunit/data/registration/no_manifest_version.json b/tests/phpunit/data/registration/no_manifest_version.json
new file mode 100644 (file)
index 0000000..1a6119f
--- /dev/null
@@ -0,0 +1,3 @@
+{
+       "name": "FooBar"
+}
diff --git a/tests/phpunit/data/registration/notjson.txt b/tests/phpunit/data/registration/notjson.txt
new file mode 100644 (file)
index 0000000..d47b460
--- /dev/null
@@ -0,0 +1 @@
+This is definitely not JSON.
diff --git a/tests/phpunit/data/registration/old_manifest_version.json b/tests/phpunit/data/registration/old_manifest_version.json
new file mode 100644 (file)
index 0000000..f50faa1
--- /dev/null
@@ -0,0 +1,4 @@
+{
+       "name": "FooBar",
+       "manifest_version": -2
+}
diff --git a/tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php b/tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php
new file mode 100644 (file)
index 0000000..d69ad59
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Copyright (C) 2018 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/**
+ * @covers ExtensionJsonValidator
+ */
+class ExtensionJsonValidatorTest extends MediaWikiTestCase {
+
+       /**
+        * @dataProvider provideValidate
+        */
+       public function testValidate( $file, $expected ) {
+               // If a dependency is missing, skip this test.
+               $validator = new ExtensionJsonValidator( function ( $msg ) {
+                       $this->markTestSkipped( $msg );
+               } );
+
+               if ( is_string( $expected ) ) {
+                       $this->setExpectedException(
+                               ExtensionJsonValidationError::class,
+                               $expected
+                       );
+               }
+
+               $dir = __DIR__ . '/../../data/registration/';
+               $this->assertSame(
+                       $expected,
+                       $validator->validate( $dir . $file )
+               );
+       }
+
+       public function provideValidate() {
+               return [
+                       [
+                               'notjson.txt',
+                               'notjson.txt is not valid JSON'
+                       ],
+                       [
+                               'no_manifest_version.json',
+                               'no_manifest_version.json does not have manifest_version set.'
+                       ],
+                       [
+                               'old_manifest_version.json',
+                               'old_manifest_version.json is using a non-supported schema version'
+                       ],
+                       [
+                               'newer_manifest_version.json',
+                               'newer_manifest_version.json is using a non-supported schema version'
+                       ],
+                       [
+                               'bad_spdx.json',
+                               "bad_spdx.json did not pass validation.
+[license-name] Invalid SPDX license identifier, see <https://spdx.org/licenses/>"
+                       ],
+                       [
+                               'invalid.json',
+                               "invalid.json did not pass validation.
+[license-name] Array value found, but a string is required"
+                       ],
+                       [
+                               'good.json',
+                               true
+                       ],
+               ];
+       }
+
+}
index a6f69b6..67bc088 100644 (file)
@@ -1,9 +1,57 @@
 <?php
 
+/**
+ * @covers ExtensionRegistry
+ */
 class ExtensionRegistryTest extends MediaWikiTestCase {
 
+       private $dataDir;
+
+       public function setUp() {
+               parent::setUp();
+               $this->dataDir = __DIR__ . '/../../data/registration';
+       }
+
+       public function testQueue_invalid() {
+               $registry = new ExtensionRegistry();
+               $path = __DIR__ . '/doesnotexist.json';
+               $this->setExpectedException(
+                       Exception::class,
+                       "$path does not exist!"
+               );
+               $registry->queue( $path );
+       }
+
+       public function testQueue() {
+               $registry = new ExtensionRegistry();
+               $path = "{$this->dataDir}/good.json";
+               $registry->queue( $path );
+               $this->assertArrayHasKey(
+                       $path,
+                       $registry->getQueue()
+               );
+               $registry->clearQueue();
+               $this->assertEmpty( $registry->getQueue() );
+       }
+
+       public function testLoadFromQueue_empty() {
+               $registry = new ExtensionRegistry();
+               $registry->loadFromQueue();
+               $this->assertEmpty( $registry->getAllThings() );
+       }
+
+       public function testLoadFromQueue_late() {
+               $registry = new ExtensionRegistry();
+               $registry->finish();
+               $registry->queue( "{$this->dataDir}/good.json" );
+               $this->setExpectedException(
+                       MWException::class,
+                       "The following paths tried to load late: {$this->dataDir}/good.json"
+               );
+               $registry->loadFromQueue();
+       }
+
        /**
-        * @covers ExtensionRegistry::exportExtractedData
         * @dataProvider provideExportExtractedDataGlobals
         */
        public function testExportExtractedDataGlobals( $desc, $before, $globals, $expected ) {
index 6f0de16..4a2810b 100644 (file)
@@ -54,6 +54,7 @@ class VersionCheckerTest extends PHPUnit_Framework_TestCase {
                                'FakeDependency' => [
                                        'version' => '1.0.0',
                                ],
+                               'NoVersionGiven' => [],
                        ] );
                $this->assertEquals( $expected, $checker->checkArray( [
                        'FakeExtension' => $given,
@@ -78,6 +79,39 @@ class VersionCheckerTest extends PHPUnit_Framework_TestCase {
                                ],
                                []
                        ],
+                       [
+                               [
+                                       'extensions' => [
+                                               'NoVersionGiven' => '*'
+                                       ]
+                               ],
+                               [],
+                       ],
+                       [
+                               [
+                                       'extensions' => [
+                                               'NoVersionGiven' => '1.0',
+                                       ]
+                               ],
+                               [ 'NoVersionGiven does not expose its version, but FakeExtension requires: 1.0.' ],
+                       ],
+                       [
+                               [
+                                       'extensions' => [
+                                               'Missing' => '*',
+                                       ]
+                               ],
+                               [ 'FakeExtension requires Missing to be installed.' ],
+                       ],
+                       [
+                               [
+                                       'extensions' => [
+                                               'FakeDependency' => '2.0.0',
+                                       ]
+                               ],
+                               // phpcs:ignore Generic.Files.LineLength.TooLong
+                               [ 'FakeExtension is not compatible with the current installed version of FakeDependency (1.0.0), it requires: 2.0.0.' ],
+                       ]
                ];
        }
 
index c83f500..e51d0d6 100644 (file)
@@ -42,9 +42,7 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
        protected static function makeSampleModules() {
                $modules = [
                        'test' => [],
-                       'test.top' => [ 'position' => 'top' ],
-                       'test.private.top' => [ 'group' => 'private', 'position' => 'top' ],
-                       'test.private.bottom' => [ 'group' => 'private', 'position' => 'bottom' ],
+                       'test.private' => [ 'group' => 'private' ],
                        'test.shouldembed.empty' => [ 'shouldEmbed' => true, 'isKnownEmpty' => true ],
                        'test.shouldembed' => [ 'shouldEmbed' => true ],
 
@@ -75,7 +73,6 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                        ],
 
                        'test.scripts' => [],
-                       'test.scripts.top' => [ 'position' => 'top' ],
                        'test.scripts.user' => [ 'group' => 'user' ],
                        'test.scripts.user.empty' => [ 'group' => 'user', 'isKnownEmpty' => true ],
                        'test.scripts.raw' => [ 'isRaw' => true ],
@@ -115,9 +112,7 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                $client = new ResourceLoaderClientHtml( $context );
                $client->setModules( [
                        'test',
-                       'test.private.bottom',
-                       'test.private.top',
-                       'test.top',
+                       'test.private',
                        'test.shouldembed.empty',
                        'test.shouldembed',
                        'test.unregistered',
@@ -133,15 +128,13 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                $client->setModuleScripts( [
                        'test.scripts',
                        'test.scripts.user.empty',
-                       'test.scripts.top',
                        'test.scripts.shouldembed',
                        'test.unregistered.scripts',
                ] );
 
                $expected = [
                        'states' => [
-                               'test.private.top' => 'loading',
-                               'test.private.bottom' => 'loading',
+                               'test.private' => 'loading',
                                'test.shouldembed.empty' => 'ready',
                                'test.shouldembed' => 'loading',
                                'test.styles.pure' => 'ready',
@@ -149,27 +142,23 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                                'test.styles.private' => 'ready',
                                'test.styles.shouldembed' => 'ready',
                                'test.scripts' => 'loading',
-                               'test.scripts.top' => 'loading',
                                'test.scripts.user.empty' => 'ready',
                                'test.scripts.shouldembed' => 'loading',
                        ],
                        'general' => [
                                'test',
-                               'test.top',
                        ],
                        'styles' => [
                                'test.styles.pure',
                        ],
                        'scripts' => [
                                'test.scripts',
-                               'test.scripts.top',
                                'test.scripts.shouldembed',
                        ],
                        'embed' => [
                                'styles' => [ 'test.styles.private', 'test.styles.shouldembed' ],
                                'general' => [
-                                       'test.private.bottom',
-                                       'test.private.top',
+                                       'test.private',
                                        'test.shouldembed',
                                ],
                        ],
@@ -193,15 +182,15 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                $client = new ResourceLoaderClientHtml( $context );
                $client->setConfig( [ 'key' => 'value' ] );
                $client->setModules( [
-                       'test.top',
-                       'test.private.top',
+                       'test',
+                       'test.private',
                ] );
                $client->setModuleStyles( [
                        'test.styles.pure',
                        'test.styles.private',
                ] );
                $client->setModuleScripts( [
-                       'test.scripts.top',
+                       'test.scripts',
                ] );
                $client->setExemptStates( [
                        'test.exempt' => 'ready',
@@ -211,10 +200,10 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                $expected = '<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>' . "\n"
                        . '<script>(window.RLQ=window.RLQ||[]).push(function(){'
                        . 'mw.config.set({"key":"value"});'
-                       . 'mw.loader.state({"test.exempt":"ready","test.private.top":"loading","test.styles.pure":"ready","test.styles.private":"ready","test.scripts.top":"loading"});'
-                       . 'mw.loader.implement("test.private.top@{blankVer}",function($,jQuery,require,module){},{"css":[]});'
-                       . 'mw.loader.load(["test.top"]);'
-                       . 'mw.loader.load("/w/load.php?debug=false\u0026lang=nl\u0026modules=test.scripts.top\u0026only=scripts\u0026skin=fallback");'
+                       . 'mw.loader.state({"test.exempt":"ready","test.private":"loading","test.styles.pure":"ready","test.styles.private":"ready","test.scripts":"loading"});'
+                       . 'mw.loader.implement("test.private@{blankVer}",function($,jQuery,require,module){},{"css":[]});'
+                       . 'mw.loader.load(["test"]);'
+                       . 'mw.loader.load("/w/load.php?debug=false\u0026lang=nl\u0026modules=test.scripts\u0026only=scripts\u0026skin=fallback");'
                        . '});</script>' . "\n"
                        . '<link rel="stylesheet" href="/w/load.php?debug=false&amp;lang=nl&amp;modules=test.styles.pure&amp;only=styles&amp;skin=fallback"/>' . "\n"
                        . '<style>.private{}</style>' . "\n"
@@ -266,9 +255,9 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                        ],
                        [
                                'context' => [],
-                               'modules' => [ 'test.private.top' ],
+                               'modules' => [ 'test.private' ],
                                'only' => ResourceLoaderModule::TYPE_COMBINED,
-                               'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.private.top@{blankVer}",function($,jQuery,require,module){},{"css":[]});});</script>',
+                               'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.private@{blankVer}",function($,jQuery,require,module){},{"css":[]});});</script>',
                        ],
                        [
                                'context' => [],
index b168754..8390ab3 100644 (file)
@@ -33,7 +33,6 @@ return [
                        'mediawiki.page.startup',
                        'test.sinonjs',
                ],
-               'position' => 'top',
                'targets' => [ 'desktop', 'mobile' ],
        ],