Merge "Avoid fatal when finding no base revision for a null revision."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 4 Oct 2018 18:11:58 +0000 (18:11 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 4 Oct 2018 18:11:58 +0000 (18:11 +0000)
22 files changed:
docs/extension.schema.v1.json
docs/extension.schema.v2.json
includes/parser/CacheTime.php
includes/registration/ExtensionDependencyError.php
includes/registration/ExtensionRegistry.php
includes/registration/VersionChecker.php
includes/utils/UIDGenerator.php
languages/i18n/skr.json [deleted file]
tests/common/TestsAutoLoader.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/Storage/DerivedPageDataUpdaterTest.php
tests/phpunit/includes/TestUserRegistry.php
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/api/ApiBlockTest.php
tests/phpunit/includes/content/TextContentTest.php
tests/phpunit/includes/parser/ParserIntegrationTest.php [deleted file]
tests/phpunit/includes/parser/ParserOutputTest.php
tests/phpunit/includes/registration/VersionCheckerTest.php
tests/phpunit/includes/utils/UIDGeneratorTest.php
tests/phpunit/suite.xml
tests/phpunit/suites/ParserIntegrationTest.php [new file with mode: 0644]

index e6ec971..f6f3b21 100644 (file)
                                                        "type": "string",
                                                        "description": "Version constraint string against PHP."
                                                }
+                                       },
+                                       "patternProprties": {
+                                               "^ext-": {
+                                                       "type": "string",
+                                                       "description": "Required PHP extension.",
+                                                       "const": "*"
+                                               }
                                        }
                                },
                                "extensions": {
index 93bf0d9..8ade991 100644 (file)
                                                        "type": "string",
                                                        "description": "Version constraint string against PHP."
                                                }
+                                       },
+                                       "patternProprties": {
+                                               "^ext-": {
+                                                       "type": "string",
+                                                       "description": "Required PHP extension.",
+                                                       "const": "*"
+                                               }
                                        }
                                },
                                "extensions": {
index 26d5bdd..e9840a4 100644 (file)
@@ -38,13 +38,13 @@ class CacheTime {
        public $mVersion = Parser::VERSION;
 
        /**
-        * @var string|int TS_MW timestamp when this object was generated, or -1 for uncacheable. Used
+        * @var string|int TS_MW timestamp when this object was generated, or -1 for not cacheable. Used
         * in ParserCache.
         */
        public $mCacheTime = '';
 
        /**
-        * @var int|null Seconds after which the object should expire, use 0 for uncacheable. Used in
+        * @var int|null Seconds after which the object should expire, use 0 for not cacheable. Used in
         * ParserCache.
         */
        public $mCacheExpiry = null;
@@ -58,7 +58,11 @@ class CacheTime {
         * @return string TS_MW timestamp
         */
        public function getCacheTime() {
-               return wfTimestamp( TS_MW, $this->mCacheTime );
+               // NOTE: keep support for undocumented used of -1 to mean "not cacheable".
+               if ( $this->mCacheTime === '' ) {
+                       $this->mCacheTime = MWTimestamp::now();
+               }
+               return $this->mCacheTime;
        }
 
        /**
@@ -68,6 +72,11 @@ class CacheTime {
         * @return string
         */
        public function setCacheTime( $t ) {
+               // NOTE: keep support for undocumented used of -1 to mean "not cacheable".
+               if ( is_string( $t ) && $t !== '-1' ) {
+                       $t = MWTimestamp::convert( TS_MW, $t );
+               }
+
                return wfSetVar( $this->mCacheTime, $t );
        }
 
@@ -120,9 +129,10 @@ class CacheTime {
        public function getCacheExpiry() {
                global $wgParserCacheExpireTime;
 
+               // NOTE: keep support for undocumented used of -1 to mean "not cacheable".
                if ( $this->mCacheTime < 0 ) {
                        return 0;
-               } // old-style marker for "not cacheable"
+               }
 
                $expire = $this->mCacheExpiry;
 
@@ -157,11 +167,12 @@ class CacheTime {
        public function expired( $touched ) {
                global $wgCacheEpoch;
 
-               return !$this->isCacheable() // parser says it's uncacheable
+               $expiry = MWTimestamp::convert( TS_MW, MWTimestamp::time() - $this->getCacheExpiry() );
+
+               return !$this->isCacheable() // parser says it's not cacheable
                        || $this->getCacheTime() < $touched
                        || $this->getCacheTime() <= $wgCacheEpoch
-                       || $this->getCacheTime() <
-                               wfTimestamp( TS_MW, time() - $this->getCacheExpiry() ) // expiry period has passed
+                       || $this->getCacheTime() < $expiry // expiry period has passed
                        || !isset( $this->mVersion )
                        || version_compare( $this->mVersion, Parser::VERSION, "lt" );
        }
index dfd5985..c27cd2c 100644 (file)
@@ -53,6 +53,11 @@ class ExtensionDependencyError extends Exception {
         */
        public $incompatiblePhp = false;
 
+       /**
+        * @var string[]
+        */
+       public $missingPhpExtensions = [];
+
        /**
         * @param array $errors Each error has a 'msg' and 'type' key at minimum
         */
@@ -67,6 +72,9 @@ class ExtensionDependencyError extends Exception {
                                case 'incompatible-php':
                                        $this->incompatiblePhp = true;
                                        break;
+                               case 'missing-phpExtension':
+                                       $this->missingPhpExtensions[] = $info['missing'];
+                                       break;
                                case 'missing-skins':
                                        $this->missingSkins[] = $info['missing'];
                                        break;
index 3138b37..e462a0b 100644 (file)
@@ -213,8 +213,11 @@ class ExtensionRegistry {
                $autoloadNamespaces = [];
                $autoloaderPaths = [];
                $processor = new ExtensionProcessor();
-               $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
-               $versionChecker = new VersionChecker( $wgVersion, $phpVersion );
+               $versionChecker = new VersionChecker(
+                       $wgVersion,
+                       PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION,
+                       get_loaded_extensions()
+               );
                $extDependencies = [];
                $incompatible = [];
                $warnings = false;
index 93b4a14..586729d 100644 (file)
@@ -40,6 +40,11 @@ class VersionChecker {
         */
        private $phpVersion = false;
 
+       /**
+        * @var string[] List of installed PHP extensions
+        */
+       private $phpExtensions = [];
+
        /**
         * @var array Loaded extensions
         */
@@ -52,11 +57,14 @@ class VersionChecker {
 
        /**
         * @param string $coreVersion Current version of core
+        * @param string $phpVersion Current PHP version
+        * @param string[] $phpExtensions List of installed PHP extensions
         */
-       public function __construct( $coreVersion, $phpVersion ) {
+       public function __construct( $coreVersion, $phpVersion, array $phpExtensions ) {
                $this->versionParser = new VersionParser();
                $this->setCoreVersion( $coreVersion );
                $this->setPhpVersion( $phpVersion );
+               $this->phpExtensions = $phpExtensions;
        }
 
        /**
@@ -112,7 +120,8 @@ class VersionChecker {
         *       'FooBar' => {
         *         'MediaWiki' => '>= 1.25.0',
         *         'platform': {
-        *           'php': '>= 7.0.0'
+        *           'php': '>= 7.0.0',
+        *           'ext-foo': '*'
         *         },
         *         'extensions' => {
         *           'FooBaz' => '>= 1.25.0'
@@ -151,6 +160,7 @@ class VersionChecker {
                                        case 'platform':
                                                foreach ( $values as $dependency => $constraint ) {
                                                        if ( $dependency === 'php' ) {
+                                                               // PHP version
                                                                $phpError = $this->handleDependency(
                                                                        $this->phpVersion,
                                                                        $constraint,
@@ -166,6 +176,23 @@ class VersionChecker {
                                                                                'type' => 'incompatible-php',
                                                                        ];
                                                                }
+                                                       } elseif ( substr( $dependency, 0, 4 ) === 'ext-' ) {
+                                                               // PHP extensions
+                                                               $phpExtension = substr( $dependency, 4 );
+                                                               if ( $constraint !== '*' ) {
+                                                                       throw new UnexpectedValueException( 'Version constraints for '
+                                                                               . 'PHP extensions are not supported in ' . $extension );
+                                                               }
+                                                               if ( !in_array( $phpExtension, $this->phpExtensions, true ) ) {
+                                                                       $errors[] = [
+                                                                               'msg' =>
+                                                                                       "{$extension} requires {$phpExtension} PHP extension "
+                                                                                       . "to be installed."
+                                                                               ,
+                                                                               'type' => 'missing-phpExtension',
+                                                                               'missing' => $phpExtension,
+                                                                       ];
+                                                               }
                                                        } else {
                                                                // add other platform dependencies here
                                                                throw new UnexpectedValueException( 'Dependency type ' . $dependency .
index 10095e9..913fbdf 100644 (file)
@@ -122,8 +122,8 @@ class UIDGenerator {
        }
 
        /**
-        * @param array $info The result of UIDGenerator::getTimeAndDelay() or
-        *  a plain (UIDGenerator::millitime(), counter, clock sequence) array.
+        * @param array $info result of UIDGenerator::getTimeAndDelay(), or
+        *  for sub classes, a seqencial array like (time, offsetCounter).
         * @return string 88 bits
         * @throws RuntimeException
         */
@@ -176,8 +176,8 @@ class UIDGenerator {
        }
 
        /**
-        * @param array $info The result of UIDGenerator::getTimeAndDelay() or
-        *  a plain (UIDGenerator::millitime(), counter, clock sequence) array.
+        * @param array $info The result of UIDGenerator::getTimeAndDelay(),
+        *  for sub classes, a seqencial array like (time, offsetCounter, clkSeq).
         * @return string 128 bits
         * @throws RuntimeException
         */
@@ -358,7 +358,8 @@ class UIDGenerator {
        protected function getSequentialPerNodeIDs( $bucket, $bits, $count, $flags ) {
                if ( $count <= 0 ) {
                        return []; // nothing to do
-               } elseif ( $bits < 16 || $bits > 48 ) {
+               }
+               if ( $bits < 16 || $bits > 48 ) {
                        throw new RuntimeException( "Requested bit size ($bits) is out of range." );
                }
 
@@ -390,7 +391,8 @@ class UIDGenerator {
                        // Acquire the UID lock file
                        if ( $handle === false ) {
                                throw new RuntimeException( "Could not open '{$path}'." );
-                       } elseif ( !flock( $handle, LOCK_EX ) ) {
+                       }
+                       if ( !flock( $handle, LOCK_EX ) ) {
                                fclose( $handle );
                                throw new RuntimeException( "Could not acquire '{$path}'." );
                        }
@@ -425,7 +427,12 @@ class UIDGenerator {
         * @param int $clockSeqSize The number of possible clock sequence values
         * @param int $counterSize The number of possible counter values
         * @param int $offsetSize The number of possible offset values
-        * @return array (result of UIDGenerator::millitime(), counter, clock sequence)
+        * @return array Array with the following keys:
+        *  - array 'time': array of seconds int and milliseconds int.
+        *  - int 'counter'.
+        *  - int 'clkSeq'.
+        *  - int 'offset': .
+        *  - int 'offsetCounter'.
         * @throws RuntimeException
         */
        protected function getTimeAndDelay( $lockFile, $clockSeqSize, $counterSize, $offsetSize ) {
@@ -439,38 +446,64 @@ class UIDGenerator {
                // Acquire the UID lock file
                if ( $handle === false ) {
                        throw new RuntimeException( "Could not open '{$this->$lockFile}'." );
-               } elseif ( !flock( $handle, LOCK_EX ) ) {
+               }
+               if ( !flock( $handle, LOCK_EX ) ) {
                        fclose( $handle );
                        throw new RuntimeException( "Could not acquire '{$this->$lockFile}'." );
                }
-               // Get the current timestamp, clock sequence number, last time, and counter
+
+               // The formatters that use this method expect a timestamp with millisecond
+               // precision and a counter upto a certain size. When more IDs than the counter
+               // size are generated during the same timestamp, an exception is thrown as we
+               // cannot increment further, because the formatted ID would not have enough
+               // bits to fit the counter.
+               //
+               // To orchestrate this between independant PHP processes on the same hosts,
+               // we must have a common sense of time so that we only have to maintain
+               // a single counter in a single lock file.
+
                rewind( $handle );
-               $data = explode( ' ', fgets( $handle ) ); // "<clk seq> <sec> <msec> <counter> <offset>"
-               $clockChanged = false; // clock set back significantly?
-               if ( count( $data ) == 5 ) { // last UID info already initialized
+               // Format of lock file contents:
+               // "<clk seq> <sec> <msec> <counter> <rand offset>"
+               $data = explode( ' ', fgets( $handle ) );
+
+               // Did the clock get moved back significantly?
+               $clockChanged = false;
+
+               if ( count( $data ) === 5 ) {
+                       // The UID lock file was already initialized
                        $clkSeq = (int)$data[0] % $clockSeqSize;
                        $prevTime = [ (int)$data[1], (int)$data[2] ];
-                       $offset = (int)$data[4] % $counterSize; // random counter offset
-                       $counter = 0; // counter for UIDs with the same timestamp
-                       // Delay until the clock reaches the time of the last ID.
-                       // This detects any microtime() drift among processes.
+                       // Counter for UIDs with the same timestamp,
+                       $counter = 0;
+                       $randOffset = (int)$data[4] % $counterSize;
+
+                       // If the system clock moved backwards by an NTP sync,
+                       // or if the last writer process had its clock drift ahead,
+                       // Try to catch up if the gap is small, so that we can keep a single
+                       // monotonic logic file.
                        $time = $this->timeWaitUntil( $prevTime );
-                       if ( !$time ) { // too long to delay?
-                               $clockChanged = true; // bump clock sequence number
-                               $time = self::millitime();
-                       } elseif ( $time == $prevTime ) {
-                               // Bump the counter if there are timestamp collisions
+                       if ( $time === false ) {
+                               // Timed out. Treat it as a new clock
+                               $clockChanged = true;
+                               $time = $this->millitime();
+                       } elseif ( $time === $prevTime ) {
+                               // Sanity check, only keep remainder if a previous writer wrote
+                               // something here that we don't accept.
                                $counter = (int)$data[3] % $counterSize;
-                               if ( ++$counter >= $counterSize ) { // sanity (starts at 0)
-                                       flock( $handle, LOCK_UN ); // abort
+                               // Bump the counter if the time has not changed yet
+                               if ( ++$counter >= $counterSize ) {
+                                       // More IDs generated with the same time than counterSize can accomodate
+                                       flock( $handle, LOCK_UN );
                                        throw new RuntimeException( "Counter overflow for timestamp value." );
                                }
                        }
-               } else { // last UID info not initialized
+               } else {
+                       // Initialize UID lock file information
                        $clkSeq = mt_rand( 0, $clockSeqSize - 1 );
+                       $time = $this->millitime();
                        $counter = 0;
-                       $offset = mt_rand( 0, $offsetSize - 1 );
-                       $time = self::millitime();
+                       $randOffset = mt_rand( 0, $offsetSize - 1 );
                }
                // microtime() and gettimeofday() can drift from time() at least on Windows.
                // The drift is immediate for processes running while the system clock changes.
@@ -484,26 +517,26 @@ class UIDGenerator {
                }
                // If microtime() is synced and a clock change was detected, then the clock went back
                if ( $clockChanged ) {
-                       // Bump the clock sequence number and also randomize the counter offset,
+                       // Bump the clock sequence number and also randomize the extra offset,
                        // which is useful for UIDs that do not include the clock sequence number.
                        $clkSeq = ( $clkSeq + 1 ) % $clockSeqSize;
-                       $offset = mt_rand( 0, $offsetSize - 1 );
+                       $randOffset = mt_rand( 0, $offsetSize - 1 );
                        trigger_error( "Clock was set back; sequence number incremented." );
                }
-               // Update the (clock sequence number, timestamp, counter)
+
+               // Update and release the UID lock file
                ftruncate( $handle, 0 );
                rewind( $handle );
-               fwrite( $handle, "{$clkSeq} {$time[0]} {$time[1]} {$counter} {$offset}" );
+               fwrite( $handle, "{$clkSeq} {$time[0]} {$time[1]} {$counter} {$randOffset}" );
                fflush( $handle );
-               // Release the UID lock file
                flock( $handle, LOCK_UN );
 
                return [
                        'time'          => $time,
                        'counter'       => $counter,
                        'clkSeq'        => $clkSeq,
-                       'offset'        => $offset,
-                       'offsetCounter' => $counter + $offset
+                       'offset'        => $randOffset,
+                       'offsetCounter' => $counter + $randOffset,
                ];
        }
 
@@ -516,7 +549,7 @@ class UIDGenerator {
         */
        protected function timeWaitUntil( array $time ) {
                do {
-                       $ct = self::millitime();
+                       $ct = $this->millitime();
                        if ( $ct >= $time ) { // https://secure.php.net/manual/en/language.operators.comparison.php
                                return $ct; // current timestamp is higher than $time
                        }
@@ -574,7 +607,7 @@ class UIDGenerator {
        /**
         * @return array (current time in seconds, milliseconds since then)
         */
-       protected static function millitime() {
+       protected function millitime() {
                list( $msec, $sec ) = explode( ' ', microtime() );
 
                return [ (int)$sec, (int)( $msec * 1000 ) ];
@@ -590,8 +623,9 @@ class UIDGenerator {
         *
         * @see unitTestTearDown
         * @since 1.23
+        * @codeCoverageIgnore
         */
-       protected function deleteCacheFiles() {
+       private function deleteCacheFiles() {
                // T46850
                foreach ( $this->fileHandles as $path => $handle ) {
                        if ( $handle !== null ) {
@@ -615,8 +649,10 @@ class UIDGenerator {
         * environment it should be used with caution as it may destroy state saved
         * in the files.
         *
+        * @internal For use by unit tests
         * @see deleteCacheFiles
         * @since 1.23
+        * @codeCoverageIgnore
         */
        public static function unitTestTearDown() {
                // T46850
diff --git a/languages/i18n/skr.json b/languages/i18n/skr.json
deleted file mode 100644 (file)
index 845378e..0000000
+++ /dev/null
@@ -1,586 +0,0 @@
-{
-       "@metadata": {
-               "authors": [
-                       "Rachitrali",
-                       "Saraiki",
-                       "محمد جنید حفیظ کھوسہ"
-               ]
-       },
-       "sunday": "اتوار",
-       "monday": "سون٘وار",
-       "tuesday": "منگل",
-       "wednesday": "ٻُدھ",
-       "thursday": "خمیس",
-       "friday": "جمعہ",
-       "saturday": "چھݨ چھݨ",
-       "sun": "اتوار",
-       "mon": "سون٘وار",
-       "tue": "منگل",
-       "wed": "ٻُدھ",
-       "thu": "خمیس",
-       "fri": "جمعہ",
-       "sat": "چھݨ چھݨ",
-       "january": "جنوری",
-       "february": "فروری",
-       "march": "مارچ",
-       "april": "اپريل",
-       "may_long": "مئی",
-       "june": "جون",
-       "july": "جولائی",
-       "august": "اگست",
-       "september": "ستمبر",
-       "october": "اکتوبر",
-       "november": "نومبر",
-       "december": "دسمبر",
-       "january-gen": "جنوری",
-       "february-gen": "فروری",
-       "march-gen": "مارچ",
-       "april-gen": "اپريل",
-       "may-gen": "مئی",
-       "june-gen": "جون",
-       "july-gen": "جولائی",
-       "august-gen": "اگست",
-       "september-gen": "ستمبر",
-       "october-gen": "اکتوبر",
-       "november-gen": "نومبر",
-       "december-gen": "دسمبر",
-       "jan": "جنوری",
-       "feb": "فروری",
-       "mar": "مارچ",
-       "apr": "اپریل",
-       "may": "مئی",
-       "jun": "جون",
-       "jul": "جولائی",
-       "aug": "اگست",
-       "sep": "ستمبر",
-       "oct": "اکتوبر",
-       "nov": "نومبر",
-       "dec": "دسمبر",
-       "pagecategories": "{{PLURAL:$1|ونکی|ونکیاں}}",
-       "category_header": "ونکی \"$1\" وچ ورقے",
-       "subcategories": "ذیلی ونکیاں",
-       "category-media-header": "ونکی \"$1\" وچ میڈیا",
-       "category-empty": "<em>ایں قسم وچ اڄݨ تائیں کوئی ورقہ یا میڈیا کائنی۔</em>",
-       "hidden-categories": "{{PLURAL:$1|لڳی ونکی|لُڳیاں ونکیاں}}",
-       "listingcontinuesabbrev": "جاری۔",
-       "noindex-category": "غیر فہرست شدہ صفحات",
-       "broken-file-category": "ٹٹے ہوۓ جوڑاں آلے صفحے",
-       "about": "تعارف",
-       "newwindow": "(نویں ونڈو وچ کھولو)",
-       "cancel": "مکاؤ",
-       "mytalk": "ڳالھ مہاڑ",
-       "navigation": "نیوی ڳیشݨ",
-       "and": "&#32;تے",
-       "namespaces": "ناں جاہیں",
-       "variants": "ونکیاں",
-       "navigation-heading": "نیوی ڳیشݨ فہرست",
-       "returnto": "واپس $1 چلو",
-       "tagline": " {{SITENAME}} توں",
-       "help": "مدد",
-       "search": "کھوج",
-       "searchbutton": "کھوج",
-       "searcharticle": "ڄلو",
-       "history": "فائل دا تاریخچہ",
-       "history_short": "تاریخچہ",
-       "printableversion": "چھپݨ جوگا ورقہ",
-       "permalink": "پکا جوڑ",
-       "view": "ݙکھالے",
-       "view-foreign": "$1 تے ݙیکھو",
-       "edit": "لکھو، ترمیم",
-       "create": "بݨاؤ",
-       "create-local": "آپݨی لکھت رَلاؤ",
-       "delete": "مٹاؤ",
-       "newpage": "نواں ورقہ",
-       "talkpagelinktext": "ڳالھ مہاڑ",
-       "personaltools": "ذاتی آوزار",
-       "talk": "ڳالھ مہاڑ",
-       "views": "ݙکھالے",
-       "toolbox": "آوزار",
-       "otherlanguages": "ٻنھاں زباناں وچ",
-       "redirectedfrom": "($1 کنوں ولدا رجوع )",
-       "redirectpagesub": "ورقہ ریڈائریکٹ کرو",
-       "redirectto": "اڳے کرو:",
-       "lastmodifiedat": "ایہ ورقہ چھیکڑی واری  $1 کوں $2 تے تبدیل تھیا ہائی۔",
-       "jumpto": "ٹپ مارو",
-       "jumptonavigation": "نیوی ڳیشݨ",
-       "jumptosearch": "کھوج",
-       "aboutsite": "{{SITENAME}} دا تعارف",
-       "aboutpage": "Project:تعارف",
-       "copyrightpage": "{{ns:project}}:حقوق تصانیف",
-       "currentevents": "حالیہ واقعات",
-       "currentevents-url": "Project:حالیہ واقعات",
-       "disclaimers": "اظہار لاتعلقی",
-       "disclaimerpage": "Project:عام لاتعلقی اظہار",
-       "edithelp": "لکھݨ وچ مدد",
-       "mainpage": "پہلا ورقہ",
-       "mainpage-description": "وݙا ورقہ",
-       "portal": "بیٹھک",
-       "portal-url": "Project:دیوان عام",
-       "privacy": "پرائیویسی پالیسی",
-       "privacypage": "Project:پرائیویسی پالیسی",
-       "retrievedfrom": "\"$1\" توں گھدا",
-       "youhavenewmessages": "{{PLURAL:$3| تہاݙے کیتے}} $1 ($2).",
-       "youhavenewmessagesfromusers": "{{PLURAL:$4|تہاݙے کیتے}} {{PLURAL:$3|کہیں ٻئے صارف|$3 صارفین}} دی طرفوں $1 ($2)۔",
-       "newmessagesdifflinkplural": "چھیکڑی {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
-       "editsection": "لکھو، ترمیم",
-       "editold": "لکھو",
-       "viewsourceold": "ماخذ ݙیکھو",
-       "editlink": "لکھو",
-       "viewsourcelink": "ماخذ ݙیکھو",
-       "editsectionhint": "حصہ لکھو: $1",
-       "toc": "شامل حصے",
-       "site-atom-feed": "$1 اٹوم فیڈ",
-       "page-atom-feed": "$1 اٹوم فیڈ",
-       "red-link-title": "$1 (ایہ ورقہ اڄݨ تائیں کائنی بݨیا)",
-       "nstab-main": "ورقہ",
-       "nstab-user": "صفحۂ صارف",
-       "nstab-special": "خاص ورقہ",
-       "nstab-project": "پروجیکٹ ورقہ",
-       "nstab-image": "فائل",
-       "nstab-mediawiki": "سنیہہ",
-       "nstab-template": "سانچہ",
-       "nstab-category": "ونکی",
-       "mainpage-nstab": "وݙا ورقہ",
-       "nosuchspecialpage": "اینجھا کوئی خاص ورقہ کائنی",
-       "badtitle": "بھیڑا عنوان",
-       "viewsource": "ماخذ ݙیکھو",
-       "viewsource-title": "$1 دا مسودہ ݙیکھو",
-       "viewsourcetext": "تساں ایں ورقے کوں صرف ݙیکھ تے ماخز نقل کر سڳدے ہو لیکن تبدیلی نہوے کر سڳدے",
-       "userlogin-yourname": "صارف ناں",
-       "userlogin-yourname-ph": "آپݨا ورتݨ ناں صارف درج کرو",
-       "userlogin-yourpassword": "پاس ورڈ",
-       "userlogin-yourpassword-ph": "پاس ورڈ درج کرو",
-       "createacct-yourpassword-ph": "پاس ورڈ درج کرو",
-       "createacct-yourpasswordagain": "پاس ورڈ دی تصدیق کرو",
-       "createacct-yourpasswordagain-ph": "پاس ورڈ ولدا درج کرو",
-       "userlogin-remembermypassword": "میکوں لاگ ان رکھو",
-       "login": "لاگ ان تھیوو",
-       "userlogin-noaccount": "تہاݙا کھاتہ کائنی؟",
-       "userlogin-joinproject": "جُڑ ونڄو {{SITENAME}} نال",
-       "createaccount": "کھاتہ کھولو",
-       "userlogin-resetpassword-link": "پاسورڈ بھل ڳئے ہو؟",
-       "userlogin-helplink2": "لاگ ان تھیوݨ کیتے مدد دی لوڑ ہے؟",
-       "createacct-emailoptional": "ای-میل پتہ، آپشنل",
-       "createacct-email-ph": "اپنا ای-میل پتہ لکھو",
-       "createacct-submit": "اپݨاں کھاتا کھولو",
-       "createacct-benefit-heading": "{{SITENAME}} تہاݙے وانگوں علم دوست افراد دا مرہون منت ہے۔",
-       "createacct-benefit-body1": "$1 {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
-       "createacct-benefit-body2": "\n$1 {{PLURAL:$1|ورقہ|ورقے}}",
-       "createacct-benefit-body3": "ہݨ دے {{PLURAL:$1|کم|کماں}}",
-       "loginlanguagelabel": "زبان: $1",
-       "pt-login": "لاگ ان تھیوو",
-       "pt-login-button": "لاگ ان تھیوو",
-       "pt-createaccount": "کھاتہ کھولو",
-       "pt-userlogout": "لاگ آؤٹ",
-       "passwordreset": "نواں پاس ورڈ بݨاؤ",
-       "bold_sample": "موٹی لکھائی",
-       "bold_tip": "موٹی لکھائی",
-       "italic_sample": "ترچھا متن",
-       "italic_tip": "ترچھی لکھائی",
-       "link_sample": "جوڑ",
-       "link_tip": "اندرونی جوڑ",
-       "extlink_sample": "http://www.example.com جوڑ دا ناں",
-       "extlink_tip": "باہرلے جوڑ (remember http:// prefix)",
-       "headline_sample": "شہ سرخی",
-       "headline_tip": "ݙوجھے درجے دی سرخی",
-       "nowiki_sample": "فارمیٹ نہ تھئی ہوئی لکھائی اتھ درج کرو",
-       "nowiki_tip": "ویکی فارمیٹ کوں نظرانداز کرو",
-       "image_tip": "پیوستہ فائل",
-       "media_tip": "فائل دا جوڑ",
-       "sig_tip": "تہاݙے دستخط ویلے دے نال",
-       "hr_tip": "اُفقی لکیر (زیادہ استعمال نہ کریں)",
-       "summary": "خلاصہ",
-       "minoredit": "ایہ ہک چھوٹی تبدیلی ہے",
-       "watchthis": "ایں ورقے تے اکھ رکھو",
-       "savearticle": "محفوظ",
-       "preview": "نمائش",
-       "showpreview": "نمائش",
-       "showdiff": "تبدیلیاں ݙکھاؤ",
-       "loginreqlink": "لاگ ان",
-       "noarticletext": "ہݨ ایں ورقے تے کجھ کائنی لکھیا ہویا۔تساں ٻیاں ورقیاں وچ [[Special:Search/{{PAGENAME}}|ایں ورقے دے عنوان کوں ڳولھ سڳدے ہو]]، <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} متعلقہ لاڳ وچ ڳولھ سڳدے ہو]،\nیا [{{fullurl:{{FULLPAGENAME}}|action=edit}} ایں ورقے کوں مُنڈھوں بݨا سڳدے ہو]</span>۔",
-       "userpage-userdoesnotexist-view": "صارف کھاتہ \"$1\" رجسٹرڈ کائنی۔",
-       "continue-editing": "خانہ ترمیم وچ ونڄو",
-       "editing": "تساں \"$1\" لکھدے پئے ہو",
-       "creating": "زیر تخلیق $1",
-       "editingsection": "«$1» دے قطعہ دی ترمیم",
-       "templatesused": "ایں ورقے تے  ورتے ڳئے {{PLURAL:$1|سانچے|سانچہ}}:",
-       "templatesusedpreview": "ایں کچے کم تے  ورتے ڳئے {{PLURAL:$1|سانچے|سانچہ}}:",
-       "template-protected": "(بچایا ڳیا)",
-       "template-semiprotected": "(نیم محفوظ)",
-       "hiddencategories": "ایہ ورقہ {{PLURAL:$1|1 لُڳیاں ونکیاں|$1 لڳی ونکی }} وچ شامل ہے:",
-       "permissionserrors": "خطائے اجازت",
-       "permissionserrorstext-withaction": "انہاں {{PLURAL:$1|وجہ|وجوہات}} پاروں تہاکوں$2 دی اجازت کائنی",
-       "moveddeleted-notice": "ایہ ورقہ مٹایا ڳیا ہے۔ مٹاوݨ دا لاگ،حفاظت دا لاگ تے ورقہ ٹورݨ دا لاگ  حوالے کیتے ہیٹھاں ݙتے ہوئے ہن۔",
-       "content-model-wikitext": "ویکی متن",
-       "undo-failure": "متنازع تبدیلیاں پاروں ایہ تبدیلی واپس نی تھی سڳدی۔",
-       "viewpagelogs": "صفحے دے لاگ ݙیکھو",
-       "currentrev-asof": "حالیہ نسخہ بمطابق $1",
-       "revisionasof": "دی تبدیلیاں $1",
-       "revision-info": "$1 دی دہرائی  توں {{GENDER:$6|$2}}$7",
-       "previousrevision": "→ پراݨا نسخہ",
-       "nextrevision": "نویں تبدیلی →",
-       "currentrevisionlink": "موجودہ حالت",
-       "cur": " رائج",
-       "last": "پچھلا",
-       "history-fieldset-title": "دہرائی کیتے لبھت",
-       "histfirst": "قدیم ترین",
-       "histlast": "تازہ ترین",
-       "history-feed-title": "ریویژن رکارڈ",
-       "history-feed-description": "وکی تے ایں ورقے دی ریویژن ہسٹری",
-       "history-feed-item-nocomment": "$2 کوں $1",
-       "rev-delundel": "ݙکھاؤ/لکاؤ",
-       "mergelog": "لاگ رلاؤ",
-       "history-title": "\"$1\" دا ریکارڈ",
-       "difference-title": "\"$1\" دے نسخیاں دے درمیان فرق",
-       "lineno": "سطر $1:",
-       "compareselectedversions": "منتخب متـن دا موازنہ",
-       "editundo": "واپس",
-       "diff-empty": "(کوئی فرق کائنی)",
-       "searchresults": "ڳولݨ دا چھاݨاں",
-       "searchresults-title": "\"$1\" دے کھوج نتارے",
-       "prevn": "پچھلے {{PLURAL:$1|$1}}",
-       "nextn": "اگلے {{PLURAL:$1|$1}}",
-       "prevn-title": "پہلے $1 {{PLURAL:$1|نتیجے}}",
-       "nextn-title": "اگلے $1 {{PLURAL:$1|نتیجے}}",
-       "shown-title": "وکھاؤ $1 {{PLURAL:$1|نتیجے}}",
-       "viewprevnext": "($1 {{int:pipe-separator}} $2) ݙیکھو ($3)",
-       "searchprofile-articles": "لسٹ ورقے",
-       "searchprofile-images": "ملٹی میڈیا",
-       "searchprofile-everything": "سب کجھ",
-       "searchprofile-advanced": "اگلا",
-       "searchprofile-articles-tooltip": "$1 وچ ڳولو",
-       "searchprofile-images-tooltip": "فائلاں ڳولو",
-       "searchprofile-everything-tooltip": " سارا مواد ڳولو",
-       "searchprofile-advanced-tooltip": "کسٹم نانواں وچ ڳولو",
-       "search-result-size": "$1 ({{PLURAL:$2|1 لفظ|$2 الفاظ}})",
-       "search-redirect": "($1 کنوں ولدا رجوع )",
-       "search-section": "(قطعہ $1)",
-       "search-file-match": "فائل مواد نال ملدا ہے",
-       "search-suggest": "بھلا تہاݙا مطلب ہائی: $1",
-       "searchall": "یکے",
-       "search-nonefound": "سوال دے نال رلدے ملدے چھاݨے کائنی۔",
-       "mypreferences": "ترجیحات",
-       "group-bot": "بوٹ",
-       "group-sysop": "منتظمین",
-       "grouppage-bot": "{{ns:project}}:بوٹ",
-       "grouppage-sysop": "{{ns:project}}:ایڈمنسٹریٹر",
-       "right-writeapi": "اے پی آئی تحریر دا استعمال",
-       "newuserlogpage": "کھاتہ بݨاوݨ آلی لاگ",
-       "rightslog": "ورتݨ والے دے حقاں دی لاگ",
-       "action-edit": "ایں ورقے تے لکھو",
-       "action-createaccount": "ایہ ورتݨ آلا کھاتہ کھولو",
-       "enhancedrc-history": "تاریخچہ",
-       "recentchanges": "نویاں تبدیلیاں",
-       "recentchanges-legend": "اِختیاراتِ حالیہ تبدیلیاں",
-       "recentchanges-feed-description": "ایں فیڈ وچ وکی تے تھیوݨ آلیاں نویاں نکور تبدیلیاں ݙیکھو۔",
-       "recentchanges-label-newpage": "ایں تبدیلی نواں ورقہ بݨایا ہے",
-       "recentchanges-label-minor": "ایہ ہک چھوٹی تبدیلی ہے",
-       "recentchanges-label-bot": "ایہ تبدیلی  بوٹ نے کیتی ہے۔",
-       "recentchanges-label-unpatrolled": "ایہ تبدیلی اڄݨ تائیں واپس کائنی ولی۔",
-       "recentchanges-label-plusminus": "ورقے دا تبدیل شدہ حجم بلحاظ تعداد بائٹ",
-       "recentchanges-legend-heading": "<strong>اختصارات:</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (ایہ وی ݙیکھو [[Special:NewPages|نویں ورقیاں دی لسٹ]])",
-       "rclistfrom": "$3 $2 توں تھیوݨ آلیاں نویاں تبدیلیاں ݙکھاؤ",
-       "rcshowhideminor": "$1 معمولی تبدیلیاں",
-       "rcshowhideminor-show": "ݙیکھاؤ",
-       "rcshowhideminor-hide": "لُکاؤ",
-       "rcshowhidebots": "$1 بوٹ",
-       "rcshowhidebots-show": "ݙیکھاؤ",
-       "rcshowhidebots-hide": "لُکاؤ",
-       "rcshowhideliu": "مندرج صارفین $1",
-       "rcshowhideliu-show": "ݙیکھاؤ",
-       "rcshowhideliu-hide": "لُکاؤ",
-       "rcshowhideanons": "گمنام صارف $1",
-       "rcshowhideanons-show": "ݙیکھاؤ",
-       "rcshowhideanons-hide": "لُکاؤ",
-       "rcshowhidepatr": "$1 مراجعت شدہ ترامیم",
-       "rcshowhidemine": "ذاتی ترامیم میݙے کم $1",
-       "rcshowhidemine-show": "ݙیکھاؤ",
-       "rcshowhidemine-hide": "لُکاؤ",
-       "rclinks": "آخری $2 ݙینہ دیاں $1 تبدیلیاں ݙکھاؤ",
-       "diff": "فرق",
-       "hist": "پچھلا کم",
-       "hide": "لُکاؤ",
-       "show": "ݙیکھاؤ",
-       "minoreditletter": "چھوٹا کم",
-       "newpageletter": "نواں",
-       "boteditletter": " خودکار",
-       "rc-change-size-new": "تبدیلی دے بعد $1 {{PLURAL:$1|بائٹ}}",
-       "rc-old-title": "اصلاً «$1» دے عنوان نال تخلیق شدہ",
-       "recentchangeslinked": "رلدیاں ملدیاں تبدیلیاں",
-       "recentchangeslinked-feed": "رلدیاں ملدیاں تبدیلیاں",
-       "recentchangeslinked-toolbox": "رلدیاں ملدیاں تبدیلیاں",
-       "recentchangeslinked-title": "\"$1\" دے متعلقہ تبدیلیاں",
-       "recentchangeslinked-page": "ورقے دا ناں",
-       "recentchangeslinked-to": "کھلے ہوئے ورقے دی بجائے ایندے نال جُڑے ہوئے ورقے دیاں تبدیلیاں ݙکھاؤ",
-       "upload": "فائل چڑھاؤ",
-       "uploadlogpage": "اپلوڈ لاگ",
-       "filedesc": "خلاصہ",
-       "license": "اجازت نامہ:",
-       "license-header": "اجازہ کاری",
-       "imgfile": "فائل",
-       "listfiles": "فائل لسٹ",
-       "file-anchor-link": "فائل",
-       "filehist": "فائل دا تاریخچہ",
-       "filehist-help": "کہیں خاص ویلے تے تریخ کوں فائل کین٘ویں  نظردی ہائی، ݙیکھݨ کیتے اوں ویلے تے کلک کرو۔",
-       "filehist-revert": "واپس",
-       "filehist-current": "موجودہ",
-       "filehist-datetime": "تریخ/ویلہ",
-       "filehist-thumb": "تھمب نیل",
-       "filehist-thumbtext": "مورخہ $1 دا تھمب نیل",
-       "filehist-nothumb": "کوئی تھمبنیل کائنی۔",
-       "filehist-user": "ورتݨ والا",
-       "filehist-dimensions": "پاسے",
-       "filehist-comment": "رائے",
-       "imagelinks": "فائل ورتݨ",
-       "linkstoimage-redirect": "$1 (فائل وت رجوع) $2",
-       "sharedupload-desc-here": "ایہ فائل $1 توں ہے تے ݙوجھیاں منصوبیاں تے وی ورتی ویسی۔\nایندی وضاحت [$2 فائل دی وضاحت دا ورقہ]  تے تھلے ݙتی ڳئی۔",
-       "filepage-nofile": "ایں ناں دی کوئی فائل کائنی۔",
-       "upload-disallowed-here": "تساں ایں فائل تے لکھ نی سڳدے۔",
-       "randompage": "رلے ملے ورقے",
-       "statistics": "شماريات",
-       "double-redirect-fixer": "ریڈائرکٹ فکسر",
-       "nbytes": "$1 {{PLURAL:$1|بائٹ}}",
-       "nmembers": "{{PLURAL:$1|رکن|اراکین}}",
-       "prefixindex": "سارے ورقے بمع سابقہ",
-       "listusers": "ورتݨ آلیاں دے ناں",
-       "newpages": "نویں ورقے",
-       "move": "ٹورو",
-       "pager-newer-n": "{{PLURAL:$1|newer 1|زیادہ نواں $1}}",
-       "pager-older-n": "{{PLURAL:$1|قدیم}} $1",
-       "booksources": "کتابی وسائل",
-       "booksources-search-legend": "ایں مضمون تے کتاباں لبھو",
-       "booksources-search": "ڳولو",
-       "specialloguserlabel": "کرݨ آلا :",
-       "speciallogtitlelabel": "ہدف (عنوان یا {{ns:user}}: صارف کیتے صارف دا ناں):",
-       "log": "لاگز",
-       "all-logs-page": "سارےعوامی لاگ",
-       "logempty": "لاگ وچ رلدیاں ملدیاں چیزاں کائنی۔",
-       "allpages": "سارے مقالے",
-       "allarticles": "سارے مقالے",
-       "allpagessubmit": "ڄلو",
-       "allpages-hide-redirects": "رجوع مکررات لکاؤ",
-       "categories": "ونکی",
-       "listgrouprights-members": "(رکناں دی لسٹ)",
-       "emailuser": "ایں ورتݨ والے کوں ای میل کرو",
-       "usermessage-editor": "نظامی پیغام رساں",
-       "watchlist": "زیرنظر فہرست",
-       "mywatchlist": "زیرنظر فہرست",
-       "watchlistfor2": "$1 تے $2 کیتے",
-       "watch": "اکھ تلے رکھو",
-       "unwatch": "اکھ ہیٹھوں ہٹاؤ",
-       "watchlist-details": "{{PLURAL:$1|$1 ورقہ ہے|$1 ورقے ہن}} تہاݙیاں نظراں ہیٹھ (تے ڳالھ مہاڑ آلے ورقے).",
-       "wlshowlast": "ݙیکھاؤ چھیکڑی $1 گھنٹے $2 ݙینہ",
-       "watchlist-options": "نظر تھلے رکھݨ دیاں راہواں",
-       "enotif_reset": "سارے ورقے ڈیکھ گھدن",
-       "dellogpage": "مٹاوݨ آلی لاگ",
-       "rollbacklink": "واپس",
-       "rollbacklinkcount": "واپس $1 {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
-       "protectlogpage": "حفاظت لاگ",
-       "protectedarticle": "\"[[$1]]\" بچایا ڳیا ہے",
-       "modifiedarticleprotection": "«[[$1]]» دا درجہ حفاظت تبدیل کیتا",
-       "restriction-edit": "لکھو",
-       "restriction-move": "ٹورو",
-       "namespace": "ناں دی جگہ:",
-       "invert": "انتخاب معکوس",
-       "namespace_association": "رلدے ناں دی تھاں",
-       "blanknamespace": "(مکھ)",
-       "contributions": "\n{{GENDER:$1|ورتݨ آلے}} دیاں حصے داریاں",
-       "contributions-title": "صارف $1 دی شراکتاں",
-       "mycontris": "شراکتاں",
-       "anoncontribs": "شراکتاں",
-       "contribsub2": "{{GENDER:$3|$1}} ($2)",
-       "nocontribs": "ایں معیار دے مطابق کوئی تبدیلی نی لبھی۔",
-       "uctop": "(موجودہ)",
-       "month": "مہینے توں (تے پہلاں):",
-       "year": "سال توں (تے پہلاں):",
-       "sp-contributions-newbies": "صرف نویں ورتݨ آلیاں دے کم ݙکھاؤ",
-       "sp-contributions-blocklog": "پابندی دی لاڳ",
-       "sp-contributions-uploads": "اپلوڈ کردہ",
-       "sp-contributions-logs": "لاگز",
-       "sp-contributions-talk": "ڳالھ مہاڑ",
-       "sp-contributions-search": "حصے پاؤݨ آلیاں دی تلاش",
-       "sp-contributions-username": "آئی پی پتہ یا ورتݨ آلا ناں:",
-       "sp-contributions-toponly": "صرف اوہ تبدیلیاں ݙکھاؤ جیہڑیاں ہُݨے ہُݨے تھیاں ہن۔",
-       "sp-contributions-newonly": "صرف نویں ورقیاں بݨݨ آلیاں لکھتاں ݙیکھاؤ",
-       "sp-contributions-submit": "ڳولو",
-       "whatlinkshere": "مربوط ورقے",
-       "whatlinkshere-title": "«$1» دے نال جُڑے ہوے ورقے",
-       "whatlinkshere-page": "ورقہ",
-       "linkshere": "<strong>$2</strong> نال درج ذیل ورقے مربوط ہن:",
-       "nolinkshere": "<strong>$2</strong> نال کوئی ورقہ مربوط کائنی۔",
-       "isredirect": "ورقہ ریڈائریکٹ کرو",
-       "istemplate": "شامل شدہ",
-       "isimage": "فائل دا ربط",
-       "whatlinkshere-prev": "{{PLURAL:$1|پچھلا|پچھلے $1}}",
-       "whatlinkshere-next": "{{PLURAL:$1|اگلا|اگلے $1}}",
-       "whatlinkshere-links": "→ روابط",
-       "whatlinkshere-hideredirs": "رجوع مکررات $1",
-       "whatlinkshere-hidetrans": "استعمالات $1",
-       "whatlinkshere-hidelinks": "روابط $1",
-       "whatlinkshere-hideimages": "تصویر دے روابط $1",
-       "whatlinkshere-filters": "چھاݨے",
-       "infiniteblock": "بے انت",
-       "blocklink": "پابندی لاؤ",
-       "contribslink": "حصے داری",
-       "blocklogpage": "پابندی دی لاگ",
-       "blocklogentry": "«[[$1]]» تے $2 کیتے پابندی عائد کی ڳئی ہے $3",
-       "reblock-logentry": "[[$1]] دی ترتیبات پابندی کوں تبدیل کیتاڳئے، ہݨ میعاد $2 $3 تے مُکسی",
-       "proxyblocker": "پراکسی روکݨ آلا",
-       "movelogpage": "ناں تبدیل کرݨ دا لاگ",
-       "export": "ورقے ٻاہر بھیڄو",
-       "thumbnail-more": "وݙا کرو",
-       "importlogpage": "لاگ گھن آؤ",
-       "tooltip-pt-userpage": "تہاݙا صارف ورقہ",
-       "tooltip-pt-mytalk": "{{GENDER:|Your}} گالھ مہاڑ",
-       "tooltip-pt-preferences": "تہاݙیاں ترجیحاں",
-       "tooltip-pt-watchlist": " انہاں ورقیاں دی لسٹ جنہاں وچ تساں تبدیلیاں کرݨ کیتے ݙیہدے پئے ہو۔",
-       "tooltip-pt-mycontris": "میݙے کم",
-       "tooltip-pt-login": "لاگ ان تھیوو تاں چنگا ہے، ضروری کائنی۔",
-       "tooltip-pt-logout": "لاگ آؤٹ",
-       "tooltip-pt-createaccount": "ایہ تہاݙے کیتے چنگا ہے جو کھاتہ کھولو تے لاگ ان تھیوو، پر ایہ لازمی کائنی۔",
-       "tooltip-ca-talk": "مضمون بارے بحث",
-       "tooltip-ca-edit": "ایں ورقے تے لکھو",
-       "tooltip-ca-addsection": "نواں حصہ شروع کرو",
-       "tooltip-ca-viewsource": "ایہ ورقہ محفوظ تھیا ہویا ہے۔ \nتساں صرف ایندا ماخذ ݙیکھ سڳدے ہو۔",
-       "tooltip-ca-history": "ایں ورقے دا پراݨا روپ۔",
-       "tooltip-ca-protect": "ایہ ورقہ محفوظ کرو",
-       "tooltip-ca-delete": "ایں ورقے کوں مٹاؤ",
-       "tooltip-ca-move": "ایں ورقے کوں گھن ڄلو",
-       "tooltip-ca-watch": "ایں ورقے کوں آپݨی دید آلے ورقیاں وچ رکھو",
-       "tooltip-ca-unwatch": "ایں ورقے کوں آپݨی دید آلے ورقیاں وچ رکھو",
-       "tooltip-search": "ڳولو {{SITENAME}}",
-       "tooltip-search-go": "جے ایں عنوان دا ورقہ ہے تاں اتھ ونڄو",
-       "tooltip-search-fulltext": "ایں عبارت کوں ورقیاں وچ ڳولو",
-       "tooltip-p-logo": "پہلا ورقہ ݙیکھو",
-       "tooltip-n-mainpage": "وݙا ورقہ ݙیکھو",
-       "tooltip-n-mainpage-description": "پہلے ورقے تے ونڄو",
-       "tooltip-n-portal": "ایں مںصوبے بارے، تساں کیا کر سڳدو، ، چیزاں کتھوں ڳولوں",
-       "tooltip-n-currentevents": "موجودہ حالات وچ پچھلیاں معلومات ݙیکھو",
-       "tooltip-n-recentchanges": "وکی تے نویاں تبدیلیاں۔",
-       "tooltip-n-randompage": "کوئی ورقہ کھولو۔",
-       "tooltip-n-help": "لبھݨ دی جاہ",
-       "tooltip-t-whatlinkshere": "ایں نال جڑے سارے وکی ورقے۔",
-       "tooltip-t-recentchangeslinked": "ایں ورقے توں جڑے ورقیاں وچ نویاں تبدیلیاں",
-       "tooltip-feed-atom": "اِیں ورقے دا اٹوم فیڈ",
-       "tooltip-t-emailuser": "{{GENDER:$1|اایں صارف}} کوں ای میل بھیڄو",
-       "tooltip-t-upload": "فائل چڑھاؤ",
-       "tooltip-t-specialpages": "سارے خاص ورقیاں دی فہرست",
-       "tooltip-t-print": "ایں ورقے دا چھپݨ آلا انگ ݙیکھو",
-       "tooltip-t-permalink": "ایں ورقے دی ایں رویژن دا پکا لنک جوڑ",
-       "tooltip-ca-nstab-main": "مواد آلا ورقہ ݙیکھو",
-       "tooltip-ca-nstab-user": "صارف دا ورقہ ݙیکھو",
-       "tooltip-ca-nstab-special": "ایہ ہک خاص ورقہ ہے، اینکوں تبدیل نسے کرسڳدے",
-       "tooltip-ca-nstab-project": "منصبے آلا ورقہ ݙیکھو",
-       "tooltip-ca-nstab-image": "فائل دا ورقہ ݙیکھو",
-       "tooltip-ca-nstab-mediawiki": "نظامی سنیہہ ݙیکھو",
-       "tooltip-ca-nstab-template": "سانچہ ݙیکھو",
-       "tooltip-ca-nstab-category": "کیٹاگری آلا ورقہ ݙیکھو",
-       "tooltip-minoredit": "ایں کوں نکی ترممیم وچ ڳݨو",
-       "tooltip-save": "تبدیلیاں محفوظ کرو",
-       "tooltip-preview": "محفوظ کرݨ کنے پہلے تبدیلیاں ݙیکھو، مہربانی ہوسی۔",
-       "tooltip-diff": "ایں لکھت وچ کیتیاں ڳیاں تبدیلیاں ݙیکھاؤ",
-       "tooltip-compareselectedversions": "چݨے ہوئے ورقیاں وچ فرق ݙیکھو",
-       "tooltip-watch": "ایں ورقے کوں آپݨی دید آلے ورقیاں وچ رکھو",
-       "tooltip-rollback": "رول بیک\" ہک کلک وچ ورقے کوں پچھلی حالت وچ گھن ویسی\"",
-       "tooltip-undo": "واپس تے کلک کرݨ نال  پچھلی ترمیم تے پُڄ ویسو، نمائشی انداز وچ ترمیم دا خانہ کھلسی۔ تساں مختصر سسب وی بیان کر سڳدے ہو۔",
-       "tooltip-summary": "مختصر خلاصہ درج کرو",
-       "simpleantispam-label": "سپام روک پھاٹک\nاینکوں <strong>نہ</strong>  بھرو!",
-       "pageinfo-title": "«$1» دی معلومات",
-       "pageinfo-header-basic": "بنیادی معلومات",
-       "pageinfo-header-edits": "تاریخچۂ ترمیم",
-       "pageinfo-header-restrictions": "ورقے دی حفاظت",
-       "pageinfo-header-properties": "ورقے دیاں خاصیتاں",
-       "pageinfo-display-title": "عنوان",
-       "pageinfo-default-sort": "کلید برائے ابتدائی ترتیب",
-       "pageinfo-length": "ورقے دی لمباݨ (بائٹ وچ)",
-       "pageinfo-article-id": "ورقے دی شناخت",
-       "pageinfo-language": "زبان",
-       "pageinfo-content-model": "انداز متن",
-       "pageinfo-robot-policy": "روبوٹ دی فہرست سازی",
-       "pageinfo-robot-index": "مجاز",
-       "pageinfo-robot-noindex": "ممنوع",
-       "pageinfo-watchers": "تعداد ناظرین",
-       "pageinfo-few-watchers": "$1 کنوں گھٹ {{PLURAL:$1|ناظر|ناظرین}}",
-       "pageinfo-redirects-name": "رجوعاں  دی تعداد",
-       "pageinfo-subpages-name": "ایں ورقے دے ذیلی ورقیاں دی تعداد",
-       "pageinfo-firstuser": "ورقہ ساز",
-       "pageinfo-firsttime": "ورقہ بݨݨ دی تریخ",
-       "pageinfo-lastuser": "چھیکڑی ترمیم کنندہ",
-       "pageinfo-lasttime": "چھیکڑی ترمیم دی تریخ",
-       "pageinfo-edits": "ترامیم دی مجموعی تعداد",
-       "pageinfo-authors": "مختلف مصنفین دی  تعداد",
-       "pageinfo-recent-edits": "حالیہ ترامیم دی تعداد (گزشتہ $1 وچ)",
-       "pageinfo-recent-authors": "مختلف مصنفین دی حالیہ تعداد",
-       "pageinfo-magic-words": "جادوئی {{PLURAL:$1|لفظ|الفاظ}} ($1)",
-       "pageinfo-hidden-categories": "لڳے {{PLURAL:$1|ونکی|ونکیاں}} ($1)",
-       "pageinfo-templates": "زیر استعمال {{PLURAL:$1|سانچہ|سانچے}} ($1)",
-       "pageinfo-toolboxlink": "معلومات ورقہ",
-       "pageinfo-contentpage": "شمار بطور ورقہ",
-       "pageinfo-contentpage-yes": "ڄیا",
-       "patrol-log-page": "گشت لاگ",
-       "previousdiff": "← پرانی لکھائی",
-       "nextdiff": "نویں لکھائی →",
-       "widthheightpage": "$1×$2، $3 {{PLURAL:$3|ورقہ|ورقے}}",
-       "file-info-size": "\n$1 × $2 پکسل، فائل دا حجم: $3، MIME قسم: $4",
-       "file-info-size-pages": "$1 × $2 پکسل، فائل دا حجم: $3، MIME قسم: $4، $5 {{PLURAL:$5|ورقہ|ورقے}}",
-       "file-nohires": "ایں توں زیادہ ریزولیوشن دستیاب کائنی۔",
-       "svg-long-desc": "ایس وی جی فائل، ابعاد $1 × $2 پکسل، فائل دا حجم: $3",
-       "show-big-image": "اصل فائل",
-       "show-big-image-preview": "ایں نمائش دا حجم:$1",
-       "show-big-image-other": "ٻیاں {{PLURAL:$2|قرارداد|قرارداداں}}: $1۔",
-       "show-big-image-size": "$1 × $2 پکسلز",
-       "metadata": "میٹا ڈیٹا",
-       "metadata-help": "ایں فائل وچ ٻیاں معلومات وی ہن۔ شاید او تہاݙے کیمرے یا سیکنر توں آیاں ہن، جیندے نال تساں ایہ فائل بݨائی ہائی۔\nجے ایہ فائل آپݨی اصل حالت وچ نہ ہووے تاں کجھ معلومات تبدیل تھئی ہوئی فائل دی پوری پوری عکاسی کائناں کریسی۔",
-       "metadata-fields": "تصویر دے میٹاڈیٹا دے او خانے جہڑے پیغام میں درج ہن او تصویر دے صفحے تے شامل ہوندے ہن۔ ایہ ااوں ویلے ظاہر تھیندن جڈݨ میٹاڈیٹا کوں ودھایا ونڄے۔\nٻئے خانے شروع وچ لُڳے ہوندن۔\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
-       "exif-orientation": "اورینٹیشن",
-       "exif-xresolution": "افقی ریزولوشن",
-       "exif-yresolution": "عمودی ریزولیشن",
-       "exif-datetime": "فائل بدلݨ دی تریخ تے ویلا",
-       "exif-make": "کیمرہ ساز کمپنی",
-       "exif-model": "کیمرے دا ماڈل",
-       "exif-software": "مستعمل سافٹ ویئر",
-       "exif-exifversion": "اکزیف ورژن",
-       "exif-colorspace": "رنگ فضا",
-       "exif-datetimeoriginal": "ڈیٹا بݨاوݨ دی تریخ تے ویلا",
-       "exif-datetimedigitized": "ڈجیٹائزنگ دا ویلہ تے تریخ",
-       "exif-orientation-1": "عام",
-       "namespacesall": "یکے",
-       "monthsall": "یکے",
-       "imgmultipagenext": "اگلا →",
-       "imgmultigo": "ونڄو!",
-       "imgmultigoto": "$1 تے ونڄو",
-       "watchlisttools-clear": "زیرنظر فہرست دی صفائی",
-       "watchlisttools-view": "متعلقہ تبدیلیاں ݙیکھو",
-       "watchlisttools-edit": "زیرنظر فہرست  کوں ݙیکھو تے تبدیلی کرو",
-       "watchlisttools-raw": "کچی زیرِنظرفہرست وچ تبدیلی کرو",
-       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|تبادلۂ خیال]])",
-       "redirect": "فائل، صارف، ورقہ،دہرائی یا آئی ڈی لاگ دے ذریعے ولدا واپس",
-       "redirect-submit": "ڄلو",
-       "redirect-lookup": "تلاش:",
-       "redirect-value": "قدر:",
-       "redirect-user": "صارف دی شناخت",
-       "redirect-page": "ورقے دی شناخت",
-       "redirect-revision": "ورقے دا رویژن",
-       "redirect-file": "فائل دا ناں",
-       "specialpages": "خاص ورقے",
-       "tag-filter": "[[Special:Tags|Tag]] چھاݨاں:",
-       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ٹیگ|ٹیگز}}]]: $2)",
-       "tags-active-yes": "ڄیا",
-       "tags-active-no": "کو",
-       "tags-hitcount": "$1 {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
-       "logentry-delete-delete": "$1 {{GENDER:$2|مٹایا ڳیا}} ورقہ $3",
-       "logentry-delete-restore": "$1 {{جنس:$2|پلٹی}} ورقہ $3 توں $4",
-       "revdelete-content-hid": "مواد لکیا",
-       "logentry-move-move": "$1 {{جنس:$2|پلٹی}} ورقہ $3 توں $4",
-       "logentry-move-move-noredirect": "$1 نے $3 کوں $4 آلے پاسے ریڈائرکٹ کیتے ٻاڄھ {{GENDER:$2|منتقل کیتے}}",
-       "logentry-move-move_redir": "$1 نے رجوع مکرر ہٹا تے ورقہ $3 کوں $4 آلے پاسے {{GENDER:$2|منتقل کیتا}}",
-       "logentry-patrol-patrol-auto": "$1 نے ورقہ $3 دے نسخہ $4 کوں خودکار طور تے گشت کیتا ہویا {{GENDER:$2|نشان زد کیتا}}",
-       "logentry-newusers-create": "صارف کھاتہ $1 {{GENDER:$2|بݨایا ڳیا}}",
-       "logentry-newusers-autocreate": "صارف کھاتہ $1 خودکار طور  {{GENDER:$2|تخلیق تھیا}}",
-       "logentry-upload-upload": "$1 {{GENDER:$2|اپلوڈ}} $3",
-       "logentry-upload-overwrite": "$1 نے $3 دا نواں نسخہ {{GENDER:$2|اپلوڈ کیتا}}",
-       "searchsuggest-search": "ڳولو",
-       "duration-days": "$1 {{PLURAL:$1|ݙینہ}}",
-       "randomrootpage": "بے ترتيب بنیادی ورقہ"
-}
index f9b84d7..8385d7a 100644 (file)
@@ -130,7 +130,7 @@ $wgAutoloadClasses += [
        'WikiPageDbTestBase' => "$testDir/phpunit/includes/page/WikiPageDbTestBase.php",
 
        # tests/phpunit/includes/parser
-       'ParserIntegrationTest' => "$testDir/phpunit/includes/parser/ParserIntegrationTest.php",
+       'ParserIntegrationTest' => "$testDir/phpunit/suites/ParserIntegrationTest.php",
 
        # tests/phpunit/includes/password
        'PasswordTestCase' => "$testDir/phpunit/includes/password/PasswordTestCase.php",
index d675e85..dfd4309 100644 (file)
@@ -112,6 +112,13 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         */
        private $cliArgs = [];
 
+       /**
+        * Holds a list of services that were overridden with setService().  Used for printing an error
+        * if overrideMwServices() overrides a service that was previously set.
+        * @var string[]
+        */
+       private $overriddenServices = [];
+
        /**
         * Table name prefixes. Oracle likes it shorter.
         */
@@ -158,7 +165,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         *
         * @since 1.28
         *
-        * @param string[] $groups Groups the test user should be in.
+        * @param string|string[] $groups Groups the test user should be in.
         * @return TestUser
         */
        public static function getTestUser( $groups = [] ) {
@@ -170,7 +177,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         *
         * @since 1.28
         *
-        * @param string[] $groups Groups the test user should be added in.
+        * @param string|string[] $groups Groups the test user should be added in.
         * @return TestUser
         */
        public static function getMutableTestUser( $groups = [] ) {
@@ -408,6 +415,9 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
 
                parent::run( $result );
 
+               // We don't mind if we override already-overridden services during cleanup
+               $this->overriddenServices = [];
+
                if ( $needsResetDB ) {
                        $this->resetDB( $this->db, $this->tablesUsed );
                }
@@ -486,6 +496,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
 
                $this->phpErrorLevel = intval( ini_get( 'error_reporting' ) );
 
+               $this->overriddenServices = [];
+
                // Cleaning up temporary files
                foreach ( $this->tmpFiles as $fileName ) {
                        if ( is_file( $fileName ) || ( is_link( $fileName ) ) ) {
@@ -630,6 +642,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                                . 'instance has been replaced by test code.' );
                }
 
+               $this->overriddenServices[] = $name;
+
                $this->localServices->disableService( $name );
                $this->localServices->redefineService(
                        $name,
@@ -891,6 +905,12 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        protected function overrideMwServices(
                Config $configOverrides = null, array $services = []
        ) {
+               if ( $this->overriddenServices ) {
+                       throw new MWException(
+                               'The following services were set and are now being unset by overrideMwServices: ' .
+                                       implode( ', ', $this->overriddenServices )
+                       );
+               }
                $newInstance = self::installMockMwServices( $configOverrides );
 
                if ( $this->localServices ) {
@@ -1013,14 +1033,17 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         */
        public function setContentLang( $lang ) {
                if ( $lang instanceof Language ) {
-                       $langCode = $lang->getCode();
-                       $langObj = $lang;
+                       $this->setMwGlobals( 'wgLanguageCode', $lang->getCode() );
+                       // Set to the exact object requested
+                       $this->setService( 'ContentLanguage', $lang );
                } else {
-                       $langCode = $lang;
-                       $langObj = Language::factory( $langCode );
+                       $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() ] );
                }
-               $this->setMwGlobals( 'wgLanguageCode', $langCode );
-               $this->setService( 'ContentLanguage', $langObj );
        }
 
        /**
index 283e99f..054636e 100644 (file)
@@ -1020,9 +1020,14 @@ class OutputPageTest extends MediaWikiTestCase {
 
                $op = $this->getMockBuilder( OutputPage::class )
                        ->setConstructorArgs( [ new RequestContext() ] )
-                       ->setMethods( [ 'addCategoryLinksToLBAndGetResult' ] )
+                                  ->setMethods( [ 'addCategoryLinksToLBAndGetResult', 'getTitle' ] )
                        ->getMock();
 
+               $title = Title::newFromText( 'My test page' );
+               $op->expects( $this->any() )
+                       ->method( 'getTitle' )
+                       ->will( $this->returnValue( $title ) );
+
                $op->expects( $this->any() )
                        ->method( 'addCategoryLinksToLBAndGetResult' )
                        ->will( $this->returnCallback( function ( array $categories ) use ( $fakeResults ) {
index 7931236..18f039d 100644 (file)
@@ -15,6 +15,7 @@ use MediaWiki\Storage\RevisionSlotsUpdate;
 use MediaWiki\Storage\SlotRecord;
 use MediaWikiTestCase;
 use MWCallableUpdate;
+use MWTimestamp;
 use PHPUnit\Framework\MockObject\MockObject;
 use TextContent;
 use TextContentHandler;
@@ -31,6 +32,12 @@ use WikitextContent;
  */
 class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
 
+       public function tearDown() {
+               MWTimestamp::setFakeTime( false );
+
+               parent::tearDown();
+       }
+
        /**
         * @param string $title
         *
@@ -470,6 +477,11 @@ class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
         * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
         */
        public function testGetPreparedEditAfterPrepareUpdate() {
+               $clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
+               MWTimestamp::setFakeTime( function () use ( &$clock ) {
+                       return $clock++;
+               } );
+
                $page = $this->getPage( __METHOD__ );
 
                $mainContent = new WikitextContent( 'first [[main]] ~~~' );
index 24aee24..3064a3d 100644 (file)
@@ -28,7 +28,7 @@ class TestUserRegistry {
         *
         * @param string $testName Caller's __CLASS__. Used to generate the
         *  user's username.
-        * @param string[] $groups Groups the test user should be added to.
+        * @param string|string[] $groups Groups the test user should be added to.
         * @return TestUser
         */
        public static function getMutableTestUser( $testName, $groups = [] ) {
@@ -38,7 +38,7 @@ class TestUserRegistry {
                        "TestUser $testName $id",  // username
                        "Name $id",                // real name
                        "$id@mediawiki.test",      // e-mail
-                       $groups,                   // groups
+                       (array)$groups,            // groups
                        $password                  // password
                );
                $testUser->getUser()->clearInstanceCache();
@@ -54,11 +54,11 @@ class TestUserRegistry {
         *
         * @since 1.28
         *
-        * @param string[] $groups Groups the test user should be added to.
+        * @param string|string[] $groups Groups the test user should be added to.
         * @return TestUser
         */
        public static function getImmutableTestUser( $groups = [] ) {
-               $groups = array_unique( $groups );
+               $groups = array_unique( (array)$groups );
                sort( $groups );
                $key = implode( ',', $groups );
 
index baf8243..df125e1 100644 (file)
@@ -99,10 +99,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
        /**
         * @todo This test method should be split up into separate test methods and
         * data providers
-        *
-        * This test is failing per T201776.
-        *
-        * @group Broken
         * @covers Title::checkQuickPermissions
         */
        public function testQuickPermissions() {
@@ -696,10 +692,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
        /**
         * @todo This test method should be split up into separate test methods and
         * data providers
-        *
-        * This test is failing per T201776.
-        *
-        * @group Broken
         * @covers Title::checkPageRestrictions
         */
        public function testPageRestrictions() {
index 9980f3a..07e861f 100644 (file)
@@ -14,20 +14,12 @@ class ApiBlockTest extends ApiTestCase {
                parent::setUp();
                $this->tablesUsed = array_merge(
                        $this->tablesUsed,
-                       [ 'change_tag', 'change_tag_def', 'logging' ]
+                       [ 'ipblocks', 'change_tag', 'change_tag_def', 'logging' ]
                );
 
                $this->mUser = $this->getMutableTestUser()->getUser();
        }
 
-       protected function tearDown() {
-               $block = Block::newFromTarget( $this->mUser->getName() );
-               if ( !is_null( $block ) ) {
-                       $block->delete();
-               }
-               parent::tearDown();
-       }
-
        protected function getTokens() {
                return $this->getTokenList( self::$users['sysop'] );
        }
index a890494..4f04e64 100644 (file)
@@ -32,7 +32,7 @@ class TextContentTest extends MediaWikiLangTestCase {
                                CONTENT_MODEL_CSS,
                                CONTENT_MODEL_JAVASCRIPT,
                        ],
-                       'wgTidyConfig' => [ 'driver' => 'disabled' ],
+                       'wgTidyConfig' => [ 'driver' => 'RemexHtml' ],
                        'wgCapitalLinks' => true,
                        'wgHooks' => [], // bypass hook ContentGetParserOutput that force custom rendering
                ] );
diff --git a/tests/phpunit/includes/parser/ParserIntegrationTest.php b/tests/phpunit/includes/parser/ParserIntegrationTest.php
deleted file mode 100644 (file)
index 91653b5..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-use Wikimedia\ScopedCallback;
-
-/**
- * This is the TestCase subclass for running a single parser test via the
- * ParserTestRunner integration test system.
- *
- * Note: the following groups are not used by PHPUnit.
- * The list in ParserTestFileSuite::__construct() is used instead.
- *
- * @group large
- * @group Database
- * @group Parser
- * @group ParserTests
- *
- * @covers Parser
- * @covers BlockLevelPass
- * @covers CoreParserFunctions
- * @covers CoreTagHooks
- * @covers Sanitizer
- * @covers Preprocessor
- * @covers Preprocessor_DOM
- * @covers Preprocessor_Hash
- * @covers DateFormatter
- * @covers LinkHolderArray
- * @covers StripState
- * @covers ParserOptions
- * @covers ParserOutput
- */
-class ParserIntegrationTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       /** @var array */
-       private $ptTest;
-
-       /** @var ParserTestRunner */
-       private $ptRunner;
-
-       /** @var ScopedCallback */
-       private $ptTeardownScope;
-
-       public function __construct( $runner, $fileName, $test ) {
-               parent::__construct( 'testParse', [ '[details omitted]' ],
-                       basename( $fileName ) . ': ' . $test['desc'] );
-               $this->ptTest = $test;
-               $this->ptRunner = $runner;
-       }
-
-       public function testParse() {
-               $this->ptRunner->getRecorder()->setTestCase( $this );
-               $result = $this->ptRunner->runTest( $this->ptTest );
-               $this->assertEquals( $result->expected, $result->actual );
-       }
-
-       public function setUp() {
-               $this->ptTeardownScope = $this->ptRunner->staticSetup();
-       }
-
-       public function tearDown() {
-               if ( $this->ptTeardownScope ) {
-                       ScopedCallback::consume( $this->ptTeardownScope );
-               }
-       }
-}
index 7091d9c..94cbf5c 100644 (file)
@@ -32,6 +32,12 @@ class ParserOutputTest extends MediaWikiLangTestCase {
                ];
        }
 
+       public function tearDown() {
+               MWTimestamp::setFakeTime( false );
+
+               parent::tearDown();
+       }
+
        /**
         * Test to make sure ParserOutput::isLinkInternal behaves properly
         * @dataProvider provideIsLinkInternal
@@ -935,4 +941,33 @@ EOF
                }
        }
 
+       /**
+        * @covers ParserOutput::getCacheTime
+        * @covers ParserOutput::setCacheTime
+        */
+       public function testGetCacheTime() {
+               $clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
+               MWTimestamp::setFakeTime( function () use ( &$clock ) {
+                       return $clock++;
+               } );
+
+               $po = new ParserOutput();
+               $time = $po->getCacheTime();
+
+               // Use current (fake) time per default. Ignore the last digit.
+               // Subsequent calls must yield the exact same timestamp as the first.
+               $this->assertStringStartsWith( '2010010100000', $time );
+               $this->assertSame( $time, $po->getCacheTime() );
+
+               // After setting, the getter must return the time that was set.
+               $time = '20110606112233';
+               $po->setCacheTime( $time );
+               $this->assertSame( $time, $po->getCacheTime() );
+
+               // support -1 as a marker for "not cacheable"
+               $time = -1;
+               $po->setCacheTime( $time );
+               $this->assertSame( $time, $po->getCacheTime() );
+       }
+
 }
index 20f97bf..6b92444 100644 (file)
@@ -12,7 +12,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * @dataProvider provideMediaWikiCheck
         */
        public function testMediaWikiCheck( $coreVersion, $constraint, $expected ) {
-               $checker = new VersionChecker( $coreVersion, '7.0.0' );
+               $checker = new VersionChecker( $coreVersion, '7.0.0', [] );
                $this->assertEquals( $expected, !(bool)$checker->checkArray( [
                        'FakeExtension' => [
                                'MediaWiki' => $constraint,
@@ -48,7 +48,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * @dataProvider providePhpValidCheck
         */
        public function testPhpValidCheck( $phpVersion, $constraint, $expected ) {
-               $checker = new VersionChecker( '1.0.0', $phpVersion );
+               $checker = new VersionChecker( '1.0.0', $phpVersion, [] );
                $this->assertEquals( $expected, !(bool)$checker->checkArray( [
                        'FakeExtension' => [
                                'platform' => [
@@ -71,7 +71,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * @expectedException UnexpectedValueException
         */
        public function testPhpInvalidConstraint() {
-               $checker = new VersionChecker( '1.0.0', '7.0.0' );
+               $checker = new VersionChecker( '1.0.0', '7.0.0', [] );
                $checker->checkArray( [
                        'FakeExtension' => [
                                'platform' => [
@@ -86,7 +86,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * @expectedException UnexpectedValueException
         */
        public function testPhpInvalidVersion( $phpVersion ) {
-                $checker = new VersionChecker( '1.0.0', $phpVersion );
+                $checker = new VersionChecker( '1.0.0', $phpVersion, [] );
        }
 
        public static function providePhpInvalidVersion() {
@@ -101,7 +101,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * @dataProvider provideType
         */
        public function testType( $given, $expected ) {
-               $checker = new VersionChecker( '1.0.0', '7.0.0' );
+               $checker = new VersionChecker( '1.0.0', '7.0.0', [ 'phpLoadedExtension' ] );
                $checker->setLoadedExtensionsAndSkins( [
                                'FakeDependency' => [
                                        'version' => '1.0.0',
@@ -195,6 +195,29 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                                        ],
                                ],
                        ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ext-phpLoadedExtension' => '*',
+                                       ],
+                               ],
+                               [],
+                       ],
+                       [
+                               [
+                                       'platform' => [
+                                               'ext-phpMissingExtension' => '*',
+                                       ],
+                               ],
+                               [
+                                       [
+                                               'missing' => 'phpMissingExtension',
+                                               'type' => 'missing-phpExtension',
+                                               // phpcs:ignore Generic.Files.LineLength.TooLong
+                                               'msg' => 'FakeExtension requires phpMissingExtension PHP extension to be installed.',
+                                       ],
+                               ],
+                       ],
                ];
        }
 
@@ -203,7 +226,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * returns any error message.
         */
        public function testInvalidConstraint() {
-               $checker = new VersionChecker( '1.0.0', '7.0.0' );
+               $checker = new VersionChecker( '1.0.0', '7.0.0', [] );
                $checker->setLoadedExtensionsAndSkins( [
                                'FakeDependency' => [
                                        'version' => 'not really valid',
@@ -222,7 +245,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                        ],
                ] ) );
 
-               $checker = new VersionChecker( '1.0.0', '7.0.0' );
+               $checker = new VersionChecker( '1.0.0', '7.0.0', [] );
                $checker->setLoadedExtensionsAndSkins( [
                                'FakeDependency' => [
                                        'version' => '1.24.3',
@@ -249,6 +272,16 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                                ],
                                'undefinedPlatformDependency',
                        ],
+                       [
+                               [
+                                       'FakeExtension' => [
+                                               'platform' => [
+                                                       'phpLoadedExtension' => '*',
+                                               ],
+                                       ],
+                               ],
+                               'phpLoadedExtension',
+                       ],
                        [
                                [
                                        'FakeExtension' => [
@@ -275,11 +308,26 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * @dataProvider provideInvalidDependency
         */
        public function testInvalidDependency( $depencency, $type ) {
-               $checker = new VersionChecker( '1.0.0', '7.0.0' );
+               $checker = new VersionChecker( '1.0.0', '7.0.0', [ 'phpLoadedExtension' ] );
                $this->setExpectedException(
                        UnexpectedValueException::class,
                        "Dependency type $type unknown in FakeExtension"
                );
                $checker->checkArray( $depencency );
        }
+
+       public function testInvalidPhpExtensionConstraint() {
+               $checker = new VersionChecker( '1.0.0', '7.0.0', [ 'phpLoadedExtension' ] );
+               $this->setExpectedException(
+                       UnexpectedValueException::class,
+                       'Version constraints for PHP extensions are not supported in FakeExtension'
+               );
+               $checker->checkArray( [
+                       'FakeExtension' => [
+                               'platform' => [
+                                       'ext-phpLoadedExtension' => '1.0.0',
+                               ],
+                       ],
+               ] );
+       }
 }
index 1a65b25..6b81a66 100644 (file)
@@ -14,8 +14,10 @@ class UIDGeneratorTest extends PHPUnit\Framework\TestCase {
         * Test that generated UIDs have the expected properties
         *
         * @dataProvider provider_testTimestampedUID
-        * @covers UIDGenerator::newTimestampedUID128
         * @covers UIDGenerator::newTimestampedUID88
+        * @covers UIDGenerator::getTimestampedID88
+        * @covers UIDGenerator::newTimestampedUID128
+        * @covers UIDGenerator::getTimestampedID128
         */
        public function testTimestampedUID( $method, $digitlen, $bits, $tbits, $hostbits ) {
                $id = call_user_func( [ UIDGenerator::class, $method ] );
@@ -78,6 +80,7 @@ class UIDGeneratorTest extends PHPUnit\Framework\TestCase {
 
        /**
         * @covers UIDGenerator::newUUIDv1
+        * @covers UIDGenerator::getUUIDv1
         */
        public function testUUIDv1() {
                $ids = [];
@@ -157,6 +160,7 @@ class UIDGeneratorTest extends PHPUnit\Framework\TestCase {
 
        /**
         * @covers UIDGenerator::newSequentialPerNodeIDs
+        * @covers UIDGenerator::getSequentialPerNodeIDs
         */
        public function testNewSequentialIDs() {
                $ids = UIDGenerator::newSequentialPerNodeIDs( 'test', 32, 5 );
index 1917674..de68fec 100644 (file)
@@ -20,8 +20,6 @@
        <testsuites>
                <testsuite name="includes">
                        <directory>includes</directory>
-                       <!-- Parser tests must be invoked via their suite -->
-                       <exclude>includes/parser/ParserIntegrationTest.php</exclude>
                </testsuite>
                <testsuite name="languages">
                        <directory>languages</directory>
diff --git a/tests/phpunit/suites/ParserIntegrationTest.php b/tests/phpunit/suites/ParserIntegrationTest.php
new file mode 100644 (file)
index 0000000..91653b5
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+use Wikimedia\ScopedCallback;
+
+/**
+ * This is the TestCase subclass for running a single parser test via the
+ * ParserTestRunner integration test system.
+ *
+ * Note: the following groups are not used by PHPUnit.
+ * The list in ParserTestFileSuite::__construct() is used instead.
+ *
+ * @group large
+ * @group Database
+ * @group Parser
+ * @group ParserTests
+ *
+ * @covers Parser
+ * @covers BlockLevelPass
+ * @covers CoreParserFunctions
+ * @covers CoreTagHooks
+ * @covers Sanitizer
+ * @covers Preprocessor
+ * @covers Preprocessor_DOM
+ * @covers Preprocessor_Hash
+ * @covers DateFormatter
+ * @covers LinkHolderArray
+ * @covers StripState
+ * @covers ParserOptions
+ * @covers ParserOutput
+ */
+class ParserIntegrationTest extends PHPUnit\Framework\TestCase {
+
+       use MediaWikiCoversValidator;
+
+       /** @var array */
+       private $ptTest;
+
+       /** @var ParserTestRunner */
+       private $ptRunner;
+
+       /** @var ScopedCallback */
+       private $ptTeardownScope;
+
+       public function __construct( $runner, $fileName, $test ) {
+               parent::__construct( 'testParse', [ '[details omitted]' ],
+                       basename( $fileName ) . ': ' . $test['desc'] );
+               $this->ptTest = $test;
+               $this->ptRunner = $runner;
+       }
+
+       public function testParse() {
+               $this->ptRunner->getRecorder()->setTestCase( $this );
+               $result = $this->ptRunner->runTest( $this->ptTest );
+               $this->assertEquals( $result->expected, $result->actual );
+       }
+
+       public function setUp() {
+               $this->ptTeardownScope = $this->ptRunner->staticSetup();
+       }
+
+       public function tearDown() {
+               if ( $this->ptTeardownScope ) {
+                       ScopedCallback::consume( $this->ptTeardownScope );
+               }
+       }
+}