Merge "Do not limit filesize when running a maintenance script"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 14 Nov 2017 16:45:02 +0000 (16:45 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 14 Nov 2017 16:45:02 +0000 (16:45 +0000)
18 files changed:
.mailmap
CREDITS
autoload.php
includes/MediaWikiServices.php
includes/ServiceWiring.php
includes/externalstore/ExternalStore.php
includes/externalstore/ExternalStoreFactory.php [new file with mode: 0644]
includes/libs/http/HttpAcceptNegotiator.php
tests/common/TestsAutoLoader.php
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/actions/ActionTest.php
tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php [new file with mode: 0644]
tests/phpunit/includes/externalstore/ExternalStoreForTesting.php [new file with mode: 0644]
tests/phpunit/includes/externalstore/ExternalStoreTest.php
tests/phpunit/includes/jobqueue/JobQueueMemoryTest.php
tests/phpunit/includes/specials/SpecialPageTestBase.php
tests/phpunit/languages/LanguageCodeTest.php

index 5a76fb9..c4f8604 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -413,8 +413,9 @@ Sumit Asthana <asthana.sumit23@gmail.com>
 TerraCodes <terracodes@tools.wmflabs.org>
 Thalia Chan <thalia@cantorion.org>
 Thalia Chan <thalia@cantorion.org> <thalia.e.chan@googlemail.com>
-Thiemo Mättig <thiemo.maettig@wikimedia.de>
-Thiemo Mättig <thiemo.maettig@wikimedia.de> <mr.heat@gmx.de>
+Thiemo Kreuz <thiemo.kreuz@wikimedia.de>
+Thiemo Kreuz <thiemo.kreuz@wikimedia.de> <thiemo.maettig@wikimedia.de>
+Thiemo Kreuz <thiemo.kreuz@wikimedia.de> <mr.heat@gmx.de>
 This, that and the other <at.light@live.com.au>
 tholam <t.lam@lamsinfosystem.com>
 Thomas Bleher <ThomasBleher@gmx.de> <tbleher@users.mediawiki.org>
diff --git a/CREDITS b/CREDITS
index c38c3fc..6ab4ad3 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -625,7 +625,7 @@ The following list can be found parsed under Special:Version/Credits -->
 * The Discoverer
 * The Evil IP address
 * theopolisme
-* Thiemo Mättig
+* Thiemo Kreuz
 * This, that and the other
 * tholam
 * Thomas Arrow
index edac2c5..a998e9a 100644 (file)
@@ -454,6 +454,7 @@ $wgAutoloadLocalClasses = [
        'ExtensionRegistry' => __DIR__ . '/includes/registration/ExtensionRegistry.php',
        'ExternalStore' => __DIR__ . '/includes/externalstore/ExternalStore.php',
        'ExternalStoreDB' => __DIR__ . '/includes/externalstore/ExternalStoreDB.php',
+       'ExternalStoreFactory' => __DIR__ . '/includes/externalstore/ExternalStoreFactory.php',
        'ExternalStoreHttp' => __DIR__ . '/includes/externalstore/ExternalStoreHttp.php',
        'ExternalStoreMedium' => __DIR__ . '/includes/externalstore/ExternalStoreMedium.php',
        'ExternalStoreMwstore' => __DIR__ . '/includes/externalstore/ExternalStoreMwstore.php',
index 0d010b4..b39c8a4 100644 (file)
@@ -690,6 +690,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'ShellCommandFactory' );
        }
 
+       /**
+        * @since 1.31
+        * @return \ExternalStoreFactory
+        */
+       public function getExternalStoreFactory() {
+               return $this->getService( 'ExternalStoreFactory' );
+       }
+
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service getter here, don't forget to add a test
        // case for it in MediaWikiServicesTest::provideGetters() and in
index 0496b67..ae88d37 100644 (file)
@@ -447,6 +447,14 @@ return [
                return $factory;
        },
 
+       'ExternalStoreFactory' => function ( MediaWikiServices $services ) {
+               $config = $services->getMainConfig();
+
+               return new ExternalStoreFactory(
+                       $config->get( 'ExternalStores' )
+               );
+       },
+
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service here, don't forget to add a getter function
        // in the MediaWikiServices class. The convenience getter should just call
index 1563baf..3beab29 100644 (file)
@@ -3,6 +3,8 @@
  * @defgroup ExternalStorage ExternalStorage
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Interface for data storage in external repositories.
  *
@@ -52,16 +54,9 @@ class ExternalStore {
         * @return ExternalStoreMedium|bool The store class or false on error
         */
        public static function getStoreObject( $proto, array $params = [] ) {
-               global $wgExternalStores;
-
-               if ( !$wgExternalStores || !in_array( $proto, $wgExternalStores ) ) {
-                       return false; // protocol not enabled
-               }
-
-               $class = 'ExternalStore' . ucfirst( $proto );
-
-               // Any custom modules should be added to $wgAutoLoadClasses for on-demand loading
-               return class_exists( $class ) ? new $class( $params ) : false;
+               return MediaWikiServices::getInstance()
+                       ->getExternalStoreFactory()
+                       ->getStoreObject( $proto, $params );
        }
 
        /**
diff --git a/includes/externalstore/ExternalStoreFactory.php b/includes/externalstore/ExternalStoreFactory.php
new file mode 100644 (file)
index 0000000..940fb2e
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @defgroup ExternalStorage ExternalStorage
+ */
+
+/**
+ * @ingroup ExternalStorage
+ */
+class ExternalStoreFactory {
+
+       /**
+        * @var array
+        */
+       private $externalStores;
+
+       /**
+        * @param array $externalStores See $wgExternalStores
+        */
+       public function __construct( array $externalStores ) {
+               $this->externalStores = array_map( 'strtolower', $externalStores );
+       }
+
+       /**
+        * Get an external store object of the given type, with the given parameters
+        *
+        * @param string $proto Type of external storage, should be a value in $wgExternalStores
+        * @param array $params Associative array of ExternalStoreMedium parameters
+        * @return ExternalStoreMedium|bool The store class or false on error
+        */
+       public function getStoreObject( $proto, array $params = [] ) {
+               if ( !$this->externalStores || !in_array( strtolower( $proto ), $this->externalStores ) ) {
+                       // Protocol not enabled
+                       return false;
+               }
+
+               $class = 'ExternalStore' . ucfirst( $proto );
+
+               // Any custom modules should be added to $wgAutoLoadClasses for on-demand loading
+               return class_exists( $class ) ? new $class( $params ) : false;
+       }
+
+}
index 5f8d9a6..84c1182 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+namespace Wikimedia\Http;
+
 /**
  * Utility for negotiating a value from a set of supported values using a preference list.
  * This is intended for use with HTTP headers like Accept, Accept-Language, Accept-Encoding, etc.
  *
  * @license GPL-2.0+
  * @author Daniel Kinzler
- * @author Thiemo Mättig
+ * @author Thiemo Kreuz
  */
-
-namespace Wikimedia\Http;
-
 class HttpAcceptNegotiator {
 
        /**
index f7bf7a6..993f8d3 100644 (file)
@@ -105,6 +105,9 @@ $wgAutoloadClasses += [
        # tests/phpunit/includes/diff
        'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php",
 
+       # tests/phpunit/includes/externalstore
+       'ExternalStoreForTesting' => "$testDir/phpunit/includes/externalstore/ExternalStoreForTesting.php",
+
        # tests/phpunit/includes/logging
        'LogFormatterTestCase' => "$testDir/phpunit/includes/logging/LogFormatterTestCase.php",
 
index 73559f3..5e8b0c8 100644 (file)
@@ -1222,4 +1222,130 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $this->assertSame( $this->testPage->getContentHandler(), $rev->getContentHandler() );
        }
 
+       /**
+        * @covers Revision::newKnownCurrent
+        */
+       public function testNewKnownCurrent() {
+               // Setup the services
+               $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
+               $this->setService( 'MainWANObjectCache', $cache );
+               $db = wfGetDB( DB_MASTER );
+
+               // Get a fresh revision to use during testing
+               $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+               $rev = $this->testPage->getRevision();
+
+               // Clear any previous cache for the revision during creation
+               $key = $cache->makeGlobalKey( 'revision', $db->getDomainID(), $rev->getPage(), $rev->getId() );
+               $cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
+               $this->assertFalse( $cache->get( $key ) );
+
+               // Get the new revision and make sure it is in the cache and correct
+               $newRev = Revision::newKnownCurrent( $db, $rev->getPage(), $rev->getId() );
+               $this->assertRevEquals( $rev, $newRev );
+               $this->assertRevEquals( $rev, $cache->get( $key ) );
+       }
+
+       public function provideUserCanBitfield() {
+               yield [ 0, 0, [], null, true ];
+               // Bitfields match, user has no permissions
+               yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], null, false ];
+               yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], null, false ];
+               yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], null, false ];
+               yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], null, false ];
+               // Bitfields match, user (admin) does have permissions
+               yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], null, true ];
+               yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], null, true ];
+               yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], null, true ];
+               // Bitfields match, user (admin) does not have permissions
+               yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], null, false ];
+               // Bitfields match, user (oversight) does have permissions
+               yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], null, true ];
+               // Check permissions using the title
+               yield [
+                       Revision::DELETED_TEXT,
+                       Revision::DELETED_TEXT,
+                       [ 'sysop' ],
+                       Title::newFromText( __METHOD__ ),
+                       true,
+               ];
+               yield [
+                       Revision::DELETED_TEXT,
+                       Revision::DELETED_TEXT,
+                       [],
+                       Title::newFromText( __METHOD__ ),
+                       false,
+               ];
+       }
+
+       /**
+        * @dataProvider provideUserCanBitfield
+        * @covers Revision::userCanBitfield
+        */
+       public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
+               $this->setMwGlobals(
+                       'wgGroupPermissions',
+                       [
+                               'sysop' => [
+                                       'deletedtext' => true,
+                                       'deletedhistory' => true,
+                               ],
+                               'oversight' => [
+                                       'viewsuppressed' => true,
+                                       'suppressrevision' => true,
+                               ],
+                       ]
+               );
+               $user = $this->getTestUser( $userGroups )->getUser();
+
+               $this->assertSame(
+                       $expected,
+                       Revision::userCanBitfield( $bitField, $field, $user, $title )
+               );
+       }
+
+       public function provideUserCan() {
+               yield [ 0, 0, [], true ];
+               // Bitfields match, user has no permissions
+               yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], false ];
+               yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], false ];
+               yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], false ];
+               yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], false ];
+               // Bitfields match, user (admin) does have permissions
+               yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], true ];
+               yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], true ];
+               yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], true ];
+               // Bitfields match, user (admin) does not have permissions
+               yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], false ];
+               // Bitfields match, user (oversight) does have permissions
+               yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], true ];
+       }
+
+       /**
+        * @dataProvider provideUserCan
+        * @covers Revision::userCan
+        */
+       public function testUserCan( $bitField, $field, $userGroups, $expected ) {
+               $this->setMwGlobals(
+                       'wgGroupPermissions',
+                       [
+                               'sysop' => [
+                                       'deletedtext' => true,
+                                       'deletedhistory' => true,
+                               ],
+                               'oversight' => [
+                                       'viewsuppressed' => true,
+                                       'suppressrevision' => true,
+                               ],
+                       ]
+               );
+               $user = $this->getTestUser( $userGroups )->getUser();
+               $revision = new Revision( [ 'deleted' => $bitField ] );
+
+               $this->assertSame(
+                       $expected,
+                       $revision->userCan( $field, $user )
+               );
+       }
+
 }
index 953c795..ca8ed2b 100644 (file)
@@ -466,4 +466,70 @@ class RevisionTest extends MediaWikiTestCase {
                );
        }
 
+       /**
+        * @covers Revision::getRevisionText
+        */
+       public function testGetRevisionText_returnsFalseWhenNoTextField() {
+               $this->assertFalse( Revision::getRevisionText( new stdClass() ) );
+       }
+
+       public function provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal() {
+               yield 'Just text' => [
+                       (object)[ 'old_text' => 'SomeText' ],
+                       'old_',
+                       'SomeText'
+               ];
+               // gzip string below generated with gzdeflate( 'AAAABBAAA' )
+               yield 'gzip text' => [
+                       (object)[
+                               'old_text' => "sttttr\002\022\000",
+                               'old_flags' => 'gzip'
+                       ],
+                       'old_',
+                       'AAAABBAAA'
+               ];
+               yield 'gzip text and different prefix' => [
+                       (object)[
+                               'jojo_text' => "sttttr\002\022\000",
+                               'jojo_flags' => 'gzip'
+                       ],
+                       'jojo_',
+                       'AAAABBAAA'
+               ];
+       }
+
+       /**
+        * @dataProvider provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal
+        * @covers Revision::getRevisionText
+        */
+       public function testGetRevisionText_returnsDecompressedTextFieldWhenNotExternal(
+               $row,
+               $prefix,
+               $expected
+       ) {
+               $this->assertSame( $expected, Revision::getRevisionText( $row, $prefix ) );
+       }
+
+       public function provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts() {
+               yield 'Just some text' => [ 'someNonUrlText' ];
+               yield 'No second URL part' => [ 'someProtocol://' ];
+       }
+
+       /**
+        * @dataProvider provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts
+        * @covers Revision::getRevisionText
+        */
+       public function testGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts(
+               $text
+       ) {
+               $this->assertFalse(
+                       Revision::getRevisionText(
+                               (object)[
+                                       'old_text' => $text,
+                                       'old_flags' => 'external',
+                               ]
+                       )
+               );
+       }
+
 }
index 4a30292..f97dd73 100644 (file)
@@ -3,10 +3,11 @@
 /**
  * @covers Action
  *
- * @author Thiemo Mättig
- *
  * @group Action
  * @group Database
+ *
+ * @licence GNU GPL v2+
+ * @author Thiemo Kreuz
  */
 class ActionTest extends MediaWikiTestCase {
 
diff --git a/tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php b/tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php
new file mode 100644 (file)
index 0000000..a0bac63
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @covers ExternalStoreFactory
+ */
+class ExternalStoreFactoryTest extends PHPUnit_Framework_TestCase {
+
+       public function testExternalStoreFactory_noStores() {
+               $factory = new ExternalStoreFactory( [] );
+               $this->assertFalse( $factory->getStoreObject( 'ForTesting' ) );
+               $this->assertFalse( $factory->getStoreObject( 'foo' ) );
+       }
+
+       public function provideStoreNames() {
+               yield 'Same case as construction' => [ 'ForTesting' ];
+               yield 'All lower case' => [ 'fortesting' ];
+               yield 'All upper case' => [ 'FORTESTING' ];
+               yield 'Mix of cases' => [ 'FOrTEsTInG' ];
+       }
+
+       /**
+        * @dataProvider provideStoreNames
+        */
+       public function testExternalStoreFactory_someStore_protoMatch( $proto ) {
+               $factory = new ExternalStoreFactory( [ 'ForTesting' ] );
+               $store = $factory->getStoreObject( $proto );
+               $this->assertInstanceOf( ExternalStoreForTesting::class, $store );
+       }
+
+       /**
+        * @dataProvider provideStoreNames
+        */
+       public function testExternalStoreFactory_someStore_noProtoMatch( $proto ) {
+               $factory = new ExternalStoreFactory( [ 'SomeOtherClassName' ] );
+               $store = $factory->getStoreObject( $proto );
+               $this->assertFalse( $store );
+       }
+
+}
diff --git a/tests/phpunit/includes/externalstore/ExternalStoreForTesting.php b/tests/phpunit/includes/externalstore/ExternalStoreForTesting.php
new file mode 100644 (file)
index 0000000..b151957
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+class ExternalStoreForTesting {
+
+       protected $data = [
+               'cluster1' => [
+                       '200' => 'Hello',
+                       '300' => [
+                               'Hello', 'World',
+                       ],
+               ],
+       ];
+
+       /**
+        * Fetch data from given URL
+        * @param string $url An url of the form FOO://cluster/id or FOO://cluster/id/itemid.
+        * @return mixed
+        */
+       public function fetchFromURL( $url ) {
+               // Based on ExternalStoreDB
+               $path = explode( '/', $url );
+               $cluster = $path[2];
+               $id = $path[3];
+               if ( isset( $path[4] ) ) {
+                       $itemID = $path[4];
+               } else {
+                       $itemID = false;
+               }
+
+               if ( !isset( $this->data[$cluster][$id] ) ) {
+                       return null;
+               }
+
+               if ( $itemID !== false
+                       && is_array( $this->data[$cluster][$id] )
+                       && isset( $this->data[$cluster][$id][$itemID] )
+               ) {
+                       return $this->data[$cluster][$id][$itemID];
+               }
+
+               return $this->data[$cluster][$id];
+       }
+
+}
index a365c4d..7ca3874 100644 (file)
@@ -1,31 +1,39 @@
 <?php
-/**
- * External Store tests
- */
 
 class ExternalStoreTest extends MediaWikiTestCase {
 
        /**
         * @covers ExternalStore::fetchFromURL
         */
-       public function testExternalFetchFromURL() {
-               $this->setMwGlobals( 'wgExternalStores', false );
+       public function testExternalFetchFromURL_noExternalStores() {
+               $this->setService(
+                       'ExternalStoreFactory',
+                       new ExternalStoreFactory( [] )
+               );
 
                $this->assertFalse(
-                       ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
+                       ExternalStore::fetchFromURL( 'ForTesting://cluster1/200' ),
                        'Deny if wgExternalStores is not set to a non-empty array'
                );
+       }
 
-               $this->setMwGlobals( 'wgExternalStores', [ 'FOO' ] );
+       /**
+        * @covers ExternalStore::fetchFromURL
+        */
+       public function testExternalFetchFromURL_someExternalStore() {
+               $this->setService(
+                       'ExternalStoreFactory',
+                       new ExternalStoreFactory( [ 'ForTesting' ] )
+               );
 
                $this->assertEquals(
-                       ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
                        'Hello',
+                       ExternalStore::fetchFromURL( 'ForTesting://cluster1/200' ),
                        'Allow FOO://cluster1/200'
                );
                $this->assertEquals(
-                       ExternalStore::fetchFromURL( 'FOO://cluster1/300/0' ),
                        'Hello',
+                       ExternalStore::fetchFromURL( 'ForTesting://cluster1/300/0' ),
                        'Allow FOO://cluster1/300/0'
                );
                # Assertions for r68900
@@ -43,45 +51,3 @@ class ExternalStoreTest extends MediaWikiTestCase {
                );
        }
 }
-
-class ExternalStoreFOO {
-
-       protected $data = [
-               'cluster1' => [
-                       '200' => 'Hello',
-                       '300' => [
-                               'Hello', 'World',
-                       ],
-               ],
-       ];
-
-       /**
-        * Fetch data from given URL
-        * @param string $url An url of the form FOO://cluster/id or FOO://cluster/id/itemid.
-        * @return mixed
-        */
-       function fetchFromURL( $url ) {
-               // Based on ExternalStoreDB
-               $path = explode( '/', $url );
-               $cluster = $path[2];
-               $id = $path[3];
-               if ( isset( $path[4] ) ) {
-                       $itemID = $path[4];
-               } else {
-                       $itemID = false;
-               }
-
-               if ( !isset( $this->data[$cluster][$id] ) ) {
-                       return null;
-               }
-
-               if ( $itemID !== false
-                       && is_array( $this->data[$cluster][$id] )
-                       && isset( $this->data[$cluster][$id][$itemID] )
-               ) {
-                       return $this->data[$cluster][$id][$itemID];
-               }
-
-               return $this->data[$cluster][$id];
-       }
-}
index 4b03fda..fe7c506 100644 (file)
@@ -6,7 +6,7 @@
  * @group JobQueue
  *
  * @licence GNU GPL v2+
- * @author Thiemo Mättig
+ * @author Thiemo Kreuz
  */
 class JobQueueMemoryTest extends PHPUnit_Framework_TestCase {
 
index 930bbe4..b1d8c69 100644 (file)
@@ -9,7 +9,7 @@
  * @author Jeroen De Dauw < jeroendedauw@gmail.com >
  * @author Daniel Kinzler
  * @author Addshore
- * @author Thiemo Mättig
+ * @author Thiemo Kreuz
  */
 abstract class SpecialPageTestBase extends MediaWikiTestCase {
 
index 0da03df..950d2df 100644 (file)
@@ -6,7 +6,7 @@
  * @group Language
  *
  * @license GPL-2.0+
- * @author Thiemo Mättig
+ * @author Thiemo Kreuz
  */
 class LanguageCodeTest extends PHPUnit_Framework_TestCase {