Merge "ParserTests: fix `bits` column in uploaded images for test cases."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 4 Jun 2014 16:25:35 +0000 (16:25 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 4 Jun 2014 16:25:35 +0000 (16:25 +0000)
14 files changed:
includes/changes/RecentChange.php
includes/installer/MysqlUpdater.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteUpdater.php
includes/parser/ParserOutput.php
includes/poolcounter/PoolCounter.php
includes/rcfeed/MachineReadableRCFeedFormatter.php
includes/specialpage/SpecialPage.php
languages/messages/MessagesAr.php
maintenance/archives/patch-watchlist-user-notificationtimestamp-index.sql [new file with mode: 0644]
maintenance/postgres/tables.sql
maintenance/tables.sql
tests/frontend/Gruntfile.js
tests/phpunit/includes/poolcounter/PoolCounterTest.php [new file with mode: 0644]

index 370e109..60aba7e 100644 (file)
@@ -147,7 +147,7 @@ class RecentChange {
                        case RC_NEW:
                                $type = 'new';
                                break;
-                       case RC_MOVE:
+                       case RC_MOVE: // obsolete
                                $type = 'move';
                                break;
                        case RC_LOG:
@@ -156,7 +156,7 @@ class RecentChange {
                        case RC_EXTERNAL:
                                $type = 'external';
                                break;
-                       case RC_MOVE_OVER_REDIRECT:
+                       case RC_MOVE_OVER_REDIRECT: // obsolete
                                $type = 'move over redirect';
                                break;
                        default:
index c538921..2f77021 100644 (file)
@@ -254,6 +254,7 @@ class MysqlUpdater extends DatabaseUpdater {
                        // 1.24
                        array( 'addField', 'page_props', 'pp_sortkey', 'patch-pp_sortkey.sql' ),
                        array( 'dropField', 'recentchanges', 'rc_cur_time', 'patch-drop-rc_cur_time.sql' ),
+                       array( 'addIndex', 'watchlist', 'wl_user_notificationtimestamp', 'patch-watchlist-user-notificationtimestamp-index.sql' ),
                );
        }
 
index 8c81080..e8de7de 100644 (file)
@@ -250,6 +250,7 @@ class PostgresUpdater extends DatabaseUpdater {
                        array( 'addPgIndex', 'recentchanges', 'rc_timestamp_bot', '(rc_timestamp) WHERE rc_bot = 0' ),
                        array( 'addPgIndex', 'templatelinks', 'templatelinks_from', '(tl_from)' ),
                        array( 'addPgIndex', 'watchlist', 'wl_user', '(wl_user)' ),
+                       array( 'addPgIndex', 'watchlist', 'wl_user_notificationtimestamp', '(wl_user, wl_notificationtimestamp)' ),
                        array( 'addPgIndex', 'logging', 'logging_user_type_time',
                                '(log_user, log_type, log_timestamp)' ),
                        array( 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ),
index 7813115..111d654 100644 (file)
@@ -132,6 +132,7 @@ class SqliteUpdater extends DatabaseUpdater {
                        // 1.24
                        array( 'addField', 'page_props', 'pp_sortkey', 'patch-pp_sortkey.sql' ),
                        array( 'dropField', 'recentchanges', 'rc_cur_time', 'patch-drop-rc_cur_time.sql' ),
+                       array( 'addIndex', 'watchlist', 'wl_user_notificationtimestamp', 'patch-watchlist-user-notificationtimestamp-index.sql' ),
                );
        }
 
index 931c088..b4a1c5d 100644 (file)
@@ -573,6 +573,14 @@ class ParserOutput extends CacheTime {
                $this->mProperties[$name] = $value;
        }
 
+       /**
+        * @param string $name The property name to look up.
+        *
+        * @return mixed|false The value previously set using setProperty(). False if null or no value
+        * was set for the given property name.
+        *
+        * @note You need to use getProperties() to check for boolean and null properties.
+        */
        public function getProperty( $name ) {
                return isset( $this->mProperties[$name] ) ? $this->mProperties[$name] : false;
        }
@@ -706,7 +714,7 @@ class ParserOutput extends CacheTime {
         *
         * @param string $key The key to look up.
         *
-        * @return mixed The value previously set for the given key using setExtensionData( $key ),
+        * @return mixed|null The value previously set for the given key using setExtensionData()
         *         or null if no value was set for this key.
         */
        public function getExtensionData( $key ) {
index 34953c0..f8d48cc 100644 (file)
@@ -53,8 +53,15 @@ abstract class PoolCounter {
 
        /** @var string All workers with the same key share the lock */
        protected $key;
-       /** @var int Maximum number of workers doing the task simultaneously */
+       /** @var int Maximum number of workers working on tasks with the same key simultaneously */
        protected $workers;
+       /**
+        * Maximum number of workers working on this task type, regardless of key.
+        * 0 means unlimited. Max allowed value is 65536.
+        * The way the slot limit is enforced is overzealous - this option should be used with caution.
+        * @var int
+        */
+       protected $slots = 0;
        /** @var int If this number of workers are already working/waiting, fail instead of wait */
        protected $maxqueue;
        /** @var float Maximum time in seconds to wait for the lock */
@@ -66,10 +73,17 @@ abstract class PoolCounter {
         * @param string $key
         */
        protected function __construct( $conf, $type, $key ) {
-               $this->key = $key;
                $this->workers = $conf['workers'];
                $this->maxqueue = $conf['maxqueue'];
                $this->timeout = $conf['timeout'];
+               if ( isset( $conf['slots'] ) ) {
+                       $this->slots = $conf['slots'];
+               }
+
+               if ( $this->slots ) {
+                       $key = $this->hashKeyIntoSlots( $key, $this->slots );
+               }
+               $this->key = $key;
        }
 
        /**
@@ -121,6 +135,20 @@ abstract class PoolCounter {
         * @return Status value is one of Released/NotLocked/Error
         */
        abstract public function release();
+
+       /**
+        * Given a key (any string) and the number of lots, returns a slot number (an integer from the [0..($slots-1)] range).
+        * This is used for a global limit on the number of instances  of a given type that can acquire a lock.
+        * The hashing is deterministic so that PoolCounter::$workers is always an upper limit of how many instances with
+        * the same key can acquire a lock.
+        *
+        * @param string $key PoolCounter instance key (any string)
+        * @param int $slots the number of slots (max allowed value is 65536)
+        * @return int
+        */
+       protected function hashKeyIntoSlots( $key, $slots ) {
+               return hexdec( substr( sha1( $key ), 0, 4 ) ) % $slots;
+       }
 }
 
 // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
index 3f17bcd..d61b6c0 100644 (file)
@@ -46,7 +46,7 @@ abstract class MachineReadableRCFeedFormatter implements RCFeedFormatter {
                        // but there is no real reason not to expose it in other cases,
                        // and I can see how this may be potentially useful for clients.
                        'id' => $attrib['rc_id'],
-                       'type' => $attrib['rc_type'],
+                       'type' => RecentChange::parseFromRCType( $attrib['rc_type'] ),
                        'namespace' => $rc->getTitle()->getNamespace(),
                        'title' => $rc->getTitle()->getPrefixedText(),
                        'comment' => $attrib['rc_comment'],
index 82d6177..7ec9f4a 100644 (file)
@@ -580,6 +580,15 @@ class SpecialPage {
                return $this->getContext()->getLanguage();
        }
 
+       /**
+        * Shortcut to get main config object
+        * @return Config
+        * @since 1.24
+        */
+       public function getConfig() {
+               return $this->getContext()->getConfig();
+       }
+
        /**
         * Return the full title, including $par
         *
index 6109535..b0ff762 100644 (file)
@@ -30,6 +30,7 @@ $datePreferences = array(
        'ymd',
        'hijri',
        'ISO 8601',
+       'jMY',
 );
 
 /**
diff --git a/maintenance/archives/patch-watchlist-user-notificationtimestamp-index.sql b/maintenance/archives/patch-watchlist-user-notificationtimestamp-index.sql
new file mode 100644 (file)
index 0000000..22ae44f
--- /dev/null
@@ -0,0 +1,4 @@
+--
+-- Creates the wl_user_notificationtimestamp index for the watchlist table
+--
+CREATE INDEX /*i*/wl_user_notificationtimestamp ON /*_*/watchlist (wl_user, wl_notificationtimestamp);
index abbfd3a..be8cbdb 100644 (file)
@@ -448,6 +448,7 @@ CREATE TABLE watchlist (
 );
 CREATE UNIQUE INDEX wl_user_namespace_title ON watchlist (wl_namespace, wl_title, wl_user);
 CREATE INDEX wl_user ON watchlist (wl_user);
+CREATE INDEX wl_user_notificationtimestamp ON watchlist (wl_user, wl_notificationtimestamp);
 
 
 CREATE TABLE interwiki (
index 1b8d618..67696f2 100644 (file)
@@ -1129,6 +1129,7 @@ CREATE TABLE /*_*/watchlist (
 
 CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
 CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE INDEX /*i*/wl_user_notificationtimestamp ON /*_*/watchlist (wl_user, wl_notificationtimestamp);
 
 
 --
index f18903b..63c3d01 100644 (file)
@@ -5,8 +5,8 @@
 /*jshint node:true */
 module.exports = function ( grunt ) {
        grunt.loadNpmTasks( 'grunt-contrib-jshint' );
-       grunt.loadNpmTasks( 'grunt-banana-checker' );
        grunt.loadNpmTasks( 'grunt-contrib-watch' );
+       grunt.loadNpmTasks( 'grunt-banana-checker' );
        grunt.loadNpmTasks( 'grunt-jscs-checker' );
        grunt.loadNpmTasks( 'grunt-jsonlint' );
 
@@ -16,14 +16,14 @@ module.exports = function ( grunt ) {
                pkg: grunt.file.readJSON( __dirname + '/package.json' ),
                jshint: {
                        options: {
-                               jshintrc: '.jshintrc'
+                               jshintrc: true
                        },
-                       all: [ '*.js', '{includes,languages,resources,skins,tests}/**/*.js' ]
+                       all: [
+                               '*.js',
+                               '{includes,languages,resources,skins,tests}/**/*.js'
+                       ]
                },
                jscs: {
-                       // Known issues:
-                       // - https://github.com/mdevils/node-jscs/issues/277
-                       // - https://github.com/mdevils/node-jscs/issues/278
                        all: [
                                '<%= jshint.all %>',
                                // Auto-generated file with JSON (double quotes)
@@ -56,7 +56,7 @@ module.exports = function ( grunt ) {
                                '.jshintignore',
                                '.jshintrc'
                        ],
-                       tasks: ['test']
+                       tasks: 'test'
                }
        } );
 
diff --git a/tests/phpunit/includes/poolcounter/PoolCounterTest.php b/tests/phpunit/includes/poolcounter/PoolCounterTest.php
new file mode 100644 (file)
index 0000000..2d31d08
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+// We will use this class with getMockForAbstractClass to create a concrete mock class. That call will die if the
+// contructor is not public, unless we use disableOriginalConstructor(), in which case we could not test the constructor.
+abstract class PoolCounterAbstractMock extends PoolCounter {
+       public function __construct() {
+               call_user_func_array( 'parent::__construct', func_get_args() );
+       }
+}
+
+class PoolCounterTest extends MediaWikiTestCase {
+       public function testConstruct() {
+               $poolCounterConfig = array(
+                       'class' => 'PoolCounterMock',
+                       'timeout' => 10,
+                       'workers' => 10,
+                       'maxqueue' => 100,
+               );
+
+               $poolCounter = $this->getMockBuilder( 'PoolCounterAbstractMock' )
+                       ->setConstructorArgs( array( $poolCounterConfig, 'testCounter', 'someKey' ) )
+                       // don't mock anything - the proper syntax would be setMethods(null), but due to a PHPUnit bug that
+                       // does not work with getMockForAbstractClass()
+                       ->setMethods( array( 'idontexist' ) )
+                       ->getMockForAbstractClass();
+               $this->assertInstanceOf( 'PoolCounter', $poolCounter );
+       }
+
+       public function testConstructWithSlots() {
+               $poolCounterConfig = array(
+                       'class' => 'PoolCounterMock',
+                       'timeout' => 10,
+                       'workers' => 10,
+                       'slots' => 2,
+                       'maxqueue' => 100,
+               );
+
+               $poolCounter = $this->getMockBuilder( 'PoolCounterAbstractMock' )
+                       ->setConstructorArgs( array( $poolCounterConfig, 'testCounter', 'key' ) )
+                       ->setMethods( array( 'idontexist' ) ) // don't mock anything
+                       ->getMockForAbstractClass();
+               $this->assertInstanceOf( 'PoolCounter', $poolCounter );
+       }
+
+       public function testHashKeyIntoSlots() {
+               $poolCounter = $this->getMockBuilder( 'PoolCounterAbstractMock' )
+                       // don't mock anything - the proper syntax would be setMethods(null), but due to a PHPUnit bug that
+                       // does not work with getMockForAbstractClass()
+                       ->setMethods( array( 'idontexist' ) )
+                       ->disableOriginalConstructor()
+                       ->getMockForAbstractClass();
+
+               $hashKeyIntoSlots = new ReflectionMethod($poolCounter, 'hashKeyIntoSlots' );
+               $hashKeyIntoSlots->setAccessible( true );
+
+
+               $keysWithTwoSlots = $keysWithFiveSlots = array();
+               foreach ( range( 1, 100 ) as $i ) {
+                       $keysWithTwoSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'key ' . $i, 2 );
+                       $keysWithFiveSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'key ' . $i, 5 );
+               }
+
+               $this->assertArrayEquals( range( 0, 1 ), array_unique( $keysWithTwoSlots ) );
+               $this->assertArrayEquals( range( 0, 4 ), array_unique( $keysWithFiveSlots ) );
+
+               // make sure it is deterministic
+               $this->assertEquals(
+                       $hashKeyIntoSlots->invoke( $poolCounter, 'asdfgh', 1000 ),
+                       $hashKeyIntoSlots->invoke( $poolCounter, 'asdfgh', 1000 )
+               );
+       }
+}