Merge "ForeignResourceManager: Catch responses other than 200 OK"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 22 Feb 2019 10:44:16 +0000 (10:44 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 22 Feb 2019 10:44:16 +0000 (10:44 +0000)
21 files changed:
.fresnel.yml
RELEASE-NOTES-1.33
autoload.php
includes/actions/HistoryAction.php
includes/api/ApiQueryUserContribs.php
includes/changes/ChangesList.php
includes/changes/EnhancedChangesList.php
includes/debug/logger/LogCapturingSpi.php [new file with mode: 0644]
includes/diff/DifferenceEngine.php
includes/password/UserPasswordPolicy.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialContributions.php
resources/Resources.php
resources/src/mediawiki.diff.styles/header.less [deleted file]
resources/src/mediawiki.interface.helpers.styles.less [new file with mode: 0644]
resources/src/mediawiki.special.changeslist.less
tests/common/TestsAutoLoader.php
tests/phpunit/MediaWikiLoggerPHPUnitTestListener.php [new file with mode: 0644]
tests/phpunit/MediaWikiPHPUnitCommand.php
tests/phpunit/MediaWikiPHPUnitResultPrinter.php [new file with mode: 0644]
tests/phpunit/MediaWikiTestCase.php

index f081fb5..5b7e0f2 100644 (file)
@@ -1,8 +1,36 @@
 warmup: true
-runs: 3
+runs: 5
 scenarios:
-  # View the Main Page without redirect
-  - url: "{MW_SERVER}{MW_SCRIPT_PATH}/index.php?mainpage"
+  # Load a page view
+  # The only page that exists by default is the main page.
+  # But, its actual name is configurable/unknown (T216791).
+  # Omit 'title' to let MediaWiki show the defaul (which is the main page),
+  # and a query string to prevent the normalization redirect.
+  - url: "{MW_SERVER}{MW_SCRIPT_PATH}/index.php?noredirectplz"
+    viewport:
+      width: 1100
+      height: 700
+    reports:
+      - navtiming
+      - paint
+      - transfer
+    probes:
+      - screenshot
+      - trace
+  # Load an 'edit' form
+  - url: "{MW_SERVER}{MW_SCRIPT_PATH}/index.php?action=edit"
+    viewport:
+      width: 1100
+      height: 700
+    reports:
+      - navtiming
+      - paint
+      - transfer
+    probes:
+      - screenshot
+      - trace
+  # View recent changes
+  - url: "{MW_SERVER}{MW_SCRIPT_PATH}/index.php?title=Special:RecentChanges"
     viewport:
       width: 1100
       height: 700
@@ -13,5 +41,3 @@ scenarios:
     probes:
       - screenshot
       - trace
-    # alerts:
-    #   navtiming/loadEventEnd: 10%
index eb8788c..dc100ef 100644 (file)
@@ -86,8 +86,6 @@ production.
 === Bug fixes in 1.33 ===
 * (T164211) Special:UserRights could sometimes fail with a
   "conflict detected" error when there weren't any conflicts.
-* (T215566) Unable to determine if the database exists
-  during a fresh installation.
 
 === Action API changes in 1.33 ===
 * (T198913) Added 'ApiOptions' hook.
index a13763e..8eaecaa 100644 (file)
@@ -960,6 +960,7 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Widget\\TitlesMultiselectWidget' => __DIR__ . '/includes/widget/TitlesMultiselectWidget.php',
        'MediaWiki\\Widget\\UserInputWidget' => __DIR__ . '/includes/widget/UserInputWidget.php',
        'MediaWiki\\Widget\\UsersMultiselectWidget' => __DIR__ . '/includes/widget/UsersMultiselectWidget.php',
+       'Mediawiki\\Logger\\LogCapturingSpi' => __DIR__ . '/includes/debug/logger/LogCapturingSpi.php',
        'MemcLockManager' => __DIR__ . '/includes/libs/lockmanager/MemcLockManager.php',
        'MemcachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedBagOStuff.php',
        'MemcachedClient' => __DIR__ . '/includes/libs/objectcache/MemcachedClient.php',
index fdf4f85..39bc830 100644 (file)
@@ -133,6 +133,7 @@ class HistoryAction extends FormlessAction {
                $out->setFeedAppendQuery( 'action=history' );
                $out->addModules( 'mediawiki.action.history' );
                $out->addModuleStyles( [
+                       'mediawiki.interface.helpers.styles',
                        'mediawiki.action.history.styles',
                        'mediawiki.special.changeslist',
                ] );
index 6082617..ae1be0d 100644 (file)
@@ -61,14 +61,11 @@ class ApiQueryUserContribs extends ApiQueryBase {
                $this->fld_patrolled = isset( $prop['patrolled'] );
                $this->fld_tags = isset( $prop['tags'] );
 
-               // Most of this code will use the 'contributions' group DB, which can map to replica DBs
+               // The main query may use the 'contributions' group DB, which can map to replica DBs
                // with extra user based indexes or partioning by user. The additional metadata
                // queries should use a regular replica DB since the lookup pattern is not all by user.
                $dbSecondary = $this->getDB(); // any random replica DB
 
-               // TODO: if the query is going only against the revision table, should this be done?
-               $this->selectNamedDB( 'contributions', DB_REPLICA, 'contributions' );
-
                $sort = ( $this->params['dir'] == 'newer' ? '' : ' DESC' );
                $op = ( $this->params['dir'] == 'older' ? '<' : '>' );
 
@@ -269,6 +266,13 @@ class ApiQueryUserContribs extends ApiQueryBase {
                        $this->orderBy = 'actor';
                }
 
+               // Use the 'contributions' replica, but only if we're querying by user ID (T216656).
+               if ( $this->orderBy === 'id' &&
+                       !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
+               ) {
+                       $this->selectNamedDB( 'contributions', DB_REPLICA, 'contributions' );
+               }
+
                $count = 0;
                $limit = $this->params['limit'];
                $userIter->rewind();
index b8ab971..bf275b3 100644 (file)
@@ -289,7 +289,10 @@ class ChangesList extends ContextSource {
                $this->rcCacheIndex = 0;
                $this->lastdate = '';
                $this->rclistOpen = false;
-               $this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' );
+               $this->getOutput()->addModuleStyles( [
+                       'mediawiki.interface.helpers.styles',
+                       'mediawiki.special.changeslist'
+               ] );
 
                return '<div class="mw-changeslist">';
        }
index 51a26ba..3e98f65 100644 (file)
@@ -78,6 +78,7 @@ class EnhancedChangesList extends ChangesList {
                $this->rclistOpen = false;
                $this->getOutput()->addModuleStyles( [
                        'mediawiki.icon',
+                       'mediawiki.interface.helpers.styles',
                        'mediawiki.special.changeslist',
                        'mediawiki.special.changeslist.enhanced',
                ] );
diff --git a/includes/debug/logger/LogCapturingSpi.php b/includes/debug/logger/LogCapturingSpi.php
new file mode 100644 (file)
index 0000000..64d5563
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+namespace Mediawiki\Logger;
+
+use Psr\Log\AbstractLogger;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Wraps another spi to capture all logs generated. This can be
+ * used, for example, to collect all logs generated during a
+ * unit test and report them when the test fails.
+ */
+class LogCapturingSpi implements Spi {
+       /** @var LoggerInterface[] */
+       private $singletons;
+       /** @var Spi */
+       private $inner;
+       /** @var array */
+       private $logs = [];
+
+       public function __construct( Spi $inner ) {
+               $this->inner = $inner;
+       }
+
+       /**
+        * @return array
+        */
+       public function getLogs() {
+               return $this->logs;
+       }
+
+       /**
+        * @param string $channel
+        * @return LoggerInterface
+        */
+       public function getLogger( $channel ) {
+               if ( !isset( $this->singletons[$channel] ) ) {
+                       $this->singletons[$channel] = $this->createLogger( $channel );
+               }
+               return $this->singletons[$channel];
+       }
+
+       /**
+        * @param array $log
+        */
+       public function capture( $log ) {
+               $this->logs[] = $log;
+       }
+
+       /**
+        * @param string $channel
+        * @return LoggerInterface
+        */
+       private function createLogger( $channel ) {
+               $inner = $this->inner->getLogger( $channel );
+               return new class( $channel, $inner, $this ) extends AbstractLogger {
+                       /** @var string */
+                       private $channel;
+                       /** @var LoggerInterface */
+                       private $logger;
+                       /** @var LogCapturingSpi */
+                       private $parent;
+
+                       // phpcs:ignore MediaWiki.Usage.NestedFunctions.NestedFunction
+                       public function __construct( $channel, LoggerInterface $logger, LogCapturingSpi $parent ) {
+                               $this->channel = $channel;
+                               $this->logger = $logger;
+                               $this->parent = $parent;
+                       }
+
+                       // phpcs:ignore MediaWiki.Usage.NestedFunctions.NestedFunction
+                       public function log( $level, $message, array $context = [] ) {
+                               $this->parent->capture( [
+                                       'channel' => $this->channel,
+                                       'level' => $level,
+                                       'message' => $message,
+                                       'context' => $context
+                               ] );
+                               $this->logger->log( $level, $message, $context );
+                       }
+               };
+       }
+}
index 87863a4..8e2b96d 100644 (file)
@@ -956,7 +956,10 @@ class DifferenceEngine extends ContextSource {
         */
        public function showDiffStyle() {
                if ( !$this->isSlotDiffRenderer ) {
-                       $this->getOutput()->addModuleStyles( 'mediawiki.diff.styles' );
+                       $this->getOutput()->addModuleStyles( [
+                               'mediawiki.interface.helpers.styles',
+                               'mediawiki.diff.styles'
+                       ] );
                        foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
                                $slotDiffRenderer->addModules( $this->getOutput() );
                        }
index 9eb921d..c61c795 100644 (file)
@@ -64,8 +64,8 @@ class UserPasswordPolicy {
        }
 
        /**
-        * Check if a passwords meets the effective password policy for a User.
-        * @param User $user who's policy we are checking
+        * Check if a password meets the effective password policy for a User.
+        * @param User $user whose policy we are checking
         * @param string $password the password to check
         * @return Status error to indicate the password didn't meet the policy, or fatal to
         *   indicate the user shouldn't be allowed to login. The status value will be an array,
@@ -83,10 +83,10 @@ class UserPasswordPolicy {
        }
 
        /**
-        * Check if a passwords meets the effective password policy for a User, using a set
+        * Check if a password meets the effective password policy for a User, using a set
         * of groups they may or may not belong to. This function does not use the DB, so can
         * be used in the installer.
-        * @param User $user who's policy we are checking
+        * @param User $user whose policy we are checking
         * @param string $password the password to check
         * @param array $groups list of groups to which we assume the user belongs
         * @return Status error to indicate the password didn't meet the policy, or fatal to
index faa2e70..c96cf8e 100644 (file)
@@ -1700,6 +1700,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                $out = $this->getOutput();
                // Styles and behavior for the legend box (see makeLegend())
                $out->addModuleStyles( [
+                       'mediawiki.interface.helpers.styles',
                        'mediawiki.special.changeslist.legend',
                        'mediawiki.special.changeslist',
                ] );
index 3a180db..8a48aa6 100644 (file)
@@ -42,6 +42,7 @@ class SpecialContributions extends IncludableSpecialPage {
                $out = $this->getOutput();
                // Modules required for viewing the list of contributions (also when included on other pages)
                $out->addModuleStyles( [
+                       'mediawiki.interface.helpers.styles',
                        'mediawiki.special',
                        'mediawiki.special.changeslist',
                ] );
index a19926d..ec7da1c 100644 (file)
@@ -914,9 +914,15 @@ return [
                ],
        ],
        'mediawiki.diff.styles' => [
+               // FIXME: Remove class and lessMessages
+               // when I6aad563e48f41c783df8b176a4f437e60a1255cc has
+               // been in production for 1 week.
                'class' => ResourceLoaderLessVarFileModule::class,
                'styles' => [
-                       'resources/src/mediawiki.diff.styles/header.less',
+                       // Remove resources/src/mediawiki.interface.helpers.styles.less
+                       // when I6aad563e48f41c783df8b176a4f437e60a1255cc has
+                       // been in production for 1 week.
+                       'resources/src/mediawiki.interface.helpers.styles.less',
                        'resources/src/mediawiki.diff.styles/diff.css',
                        'resources/src/mediawiki.diff.styles/print.css' => [
                                'media' => 'print'
@@ -925,6 +931,7 @@ return [
                'lessMessages' => [
                        'parentheses-start',
                        'parentheses-end',
+                       'pipe-separator'
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
@@ -2010,6 +2017,20 @@ return [
                        'oojs-ui.styles.icons-media',
                ],
        ],
+       'mediawiki.interface.helpers.styles' => [
+               'class' => ResourceLoaderLessVarFileModule::class,
+               'lessMessages' => [
+                       'parentheses-start',
+                       'parentheses-end',
+                       'pipe-separator'
+               ],
+               'styles' => [
+                       'resources/src/mediawiki.interface.helpers.styles.less',
+               ],
+               'targets' => [
+                       'desktop', 'mobile'
+               ],
+       ],
        'mediawiki.special' => [
                'styles' => [
                        'resources/src/mediawiki.special/special.less',
@@ -2125,13 +2146,21 @@ return [
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.special.changeslist' => [
+               // FIXME: Remove class and lessMessages
+               // when I6aad563e48f41c783df8b176a4f437e60a1255cc has
+               // been in production for 1 week.
                'class' => ResourceLoaderLessVarFileModule::class,
                'lessMessages' => [
                        'parentheses-start',
                        'parentheses-end',
                        'pipe-separator'
                ],
-               'styles' => 'resources/src/mediawiki.special.changeslist.less',
+               'styles' => [
+                       // FIXME: Remove this line when I6aad563e48f41c783df8b176a4f437e60a1255cc has
+                       // been in production for 1 week.
+                       'resources/src/mediawiki.interface.helpers.styles.less',
+                       'resources/src/mediawiki.special.changeslist.less',
+               ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.special.changeslist.enhanced' => [
diff --git a/resources/src/mediawiki.diff.styles/header.less b/resources/src/mediawiki.diff.styles/header.less
deleted file mode 100644 (file)
index d41ea08..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-.mw-tag-markers {
-       &:before {
-               content: '@{msg-parentheses-start}';
-       }
-
-       &:after {
-               content: '@{msg-parentheses-end}';
-       }
-}
diff --git a/resources/src/mediawiki.interface.helpers.styles.less b/resources/src/mediawiki.interface.helpers.styles.less
new file mode 100644 (file)
index 0000000..cfabab6
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Helper classes used across special pages
+ */
+
+/* Content dividers */
+/* @todo FIXME: Hard coded ". .". Is there a message for this? Should there be? */
+.mw-changeslist-separator:empty:before {
+       content: '. .';
+}
+
+.comment--without-parentheses,
+.mw-changeslist-links,
+.mw-diff-bytes,
+/* Needed by pages calling ChangeTags::formatSummaryRow (T212613) */
+.mw-tag-markers,
+.mw-uctop {
+       &:before {
+               content: '@{msg-parentheses-start}';
+       }
+
+       &:after {
+               content: '@{msg-parentheses-end}';
+       }
+}
+
+.mw-changeslist-links {
+       display: inline-block;
+
+       > span:not( :first-child ):before {
+               content: '@{msg-pipe-separator}';
+       }
+}
index db33f4a..ce43855 100644 (file)
 .mw-rcfilters-ui-highlights {
        display: none;
 }
-
-/* Content dividers */
-/* @todo FIXME: Hard coded ". .". Is there a message for this? Should there be? */
-.mw-changeslist-separator:empty:before {
-       content: '. .';
-}
-
-.comment--without-parentheses,
-.mw-changeslist-links,
-.mw-diff-bytes,
-.mw-tag-markers,
-.mw-uctop {
-       &:before {
-               content: '@{msg-parentheses-start}';
-       }
-
-       &:after {
-               content: '@{msg-parentheses-end}';
-       }
-}
-
-.mw-changeslist-links {
-       display: inline-block;
-
-       > span:not( :first-child ):before {
-               content: '@{msg-pipe-separator}';
-       }
-}
index 0245572..f742a1b 100644 (file)
@@ -55,7 +55,9 @@ $wgAutoloadClasses += [
        'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
        'MediaWikiCoversValidator' => "$testDir/phpunit/MediaWikiCoversValidator.php",
        'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php",
+       'MediaWikiLoggerPHPUnitTestListener' => "$testDir/phpunit/MediaWikiLoggerPHPUnitTestListener.php",
        'MediaWikiPHPUnitCommand' => "$testDir/phpunit/MediaWikiPHPUnitCommand.php",
+       'MediaWikiPHPUnitResultPrinter' => "$testDir/phpunit/MediaWikiPHPUnitResultPrinter.php",
        'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
        'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
        'MediaWikiTestResult' => "$testDir/phpunit/MediaWikiTestResult.php",
diff --git a/tests/phpunit/MediaWikiLoggerPHPUnitTestListener.php b/tests/phpunit/MediaWikiLoggerPHPUnitTestListener.php
new file mode 100644 (file)
index 0000000..502685d
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+use Mediawiki\Logger\LoggerFactory;
+use Mediawiki\Logger\Spi;
+use Mediawiki\Logger\LogCapturingSpi;
+
+/**
+ * Replaces the logging SPI on each test run. This allows
+ * another component (the printer) to fetch the logs when
+ * reporting why a test failed.
+ */
+class MediaWikiLoggerPHPUnitTestListener extends PHPUnit_Framework_BaseTestListener {
+       /** @var Spi|null */
+       private $originalSpi;
+       /** @var Spi|null */
+       private $spi;
+       /** @var array|null */
+       private $lastTestLogs;
+
+       /**
+        * A test started.
+        *
+        * @param PHPUnit_Framework_Test $test
+        */
+       public function startTest( PHPUnit_Framework_Test $test ) {
+               $this->lastTestLogs = null;
+               $this->originalSpi = LoggerFactory::getProvider();
+               $this->spi = new LogCapturingSpi( $this->originalSpi );
+               LoggerFactory::registerProvider( $this->spi );
+       }
+
+       /**
+        * A test ended.
+        *
+        * @param PHPUnit_Framework_Test $test
+        * @param float $time
+        */
+       public function endTest( PHPUnit_Framework_Test $test, $time ) {
+               $this->lastTestLogs = $this->spi->getLogs();
+               LoggerFactory::registerProvider( $this->originalSpi );
+               $this->originalSpi = null;
+               $this->spi = null;
+       }
+
+       /**
+        * Get string formatted logs generated during the last
+        * test to execute.
+        *
+        * @return string
+        */
+       public function getLog() {
+               $logs = $this->lastTestLogs;
+               if ( !$logs ) {
+                       return '';
+               }
+               $message = [];
+               foreach ( $logs as $log ) {
+                       $message[] = sprintf(
+                               '[%s] [%s] %s %s',
+                               $log['channel'],
+                               $log['level'],
+                               $log['message'],
+                               json_encode( $log['context'] )
+                       );
+               }
+               return implode( "\n", $message );
+       }
+}
index 8979195..5d139ff 100644 (file)
@@ -2,6 +2,7 @@
 
 class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
        private $cliArgs;
+       private $logListener;
 
        public function __construct( $ignorableOptions, $cliArgs ) {
                $ignore = function ( $arg ) {
@@ -18,14 +19,24 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
                        $this->arguments['configuration'] = __DIR__ . '/suite.xml';
                }
 
-               // Add our own listener
+               // Add our own listeners
                $this->arguments['listeners'][] = new MediaWikiPHPUnitTestListener;
+               $this->logListener = new MediaWikiLoggerPHPUnitTestListener;
+               $this->arguments['listeners'][] = $this->logListener;
 
                // Output only to stderr to avoid "Headers already sent" problems
                $this->arguments['stderr'] = true;
+
+               // We could create a printer instance and avoid passing the
+               // listener statically, but then we have to recreate the
+               // appropriate arguments handling + defaults.
+               if ( !isset( $this->arguments['printer'] ) ) {
+                       $this->arguments['printer'] = MediaWikiPHPUnitResultPrinter::class;
+               }
        }
 
        protected function createRunner() {
+               MediaWikiPHPUnitResultPrinter::setLogListener( $this->logListener );
                $runner = new MediaWikiTestRunner;
                $runner->setMwCliArgs( $this->cliArgs );
                return $runner;
diff --git a/tests/phpunit/MediaWikiPHPUnitResultPrinter.php b/tests/phpunit/MediaWikiPHPUnitResultPrinter.php
new file mode 100644 (file)
index 0000000..e796752
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+class MediaWikiPHPUnitResultPrinter extends PHPUnit_TextUI_ResultPrinter {
+       /** @var MediaWikiLoggerPHPUnitTestListener */
+       private static $logListener;
+
+       public static function setLogListener( MediaWikiLoggerPHPUnitTestListener $logListener ) {
+               self::$logListener = $logListener;
+       }
+
+       protected function printDefectTrace( PHPUnit_Framework_TestFailure $defect ) {
+               $log = self::$logListener->getLog();
+               if ( $log ) {
+                       $this->write( "=== Logs generated by test case\n{$log}\n===\n" );
+               }
+               parent::printDefectTrace( $defect );
+       }
+}
index 287d28c..35f396e 100644 (file)
@@ -3,6 +3,7 @@
 use MediaWiki\Logger\LegacySpi;
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\Logger\MonologSpi;
+use MediaWiki\Logger\LogCapturingSpi;
 use MediaWiki\MediaWikiServices;
 use Psr\Log\LoggerInterface;
 use Wikimedia\Rdbms\IDatabase;
@@ -1124,7 +1125,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                                $this->loggers[$channel] = $singletons['loggers'][$channel] ?? null;
                        }
                        $singletons['loggers'][$channel] = $logger;
-               } elseif ( $provider instanceof LegacySpi ) {
+               } elseif ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
                        if ( !isset( $this->loggers[$channel] ) ) {
                                $this->loggers[$channel] = $singletons[$channel] ?? null;
                        }
@@ -1151,7 +1152,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                                } else {
                                        $singletons['loggers'][$channel] = $logger;
                                }
-                       } elseif ( $provider instanceof LegacySpi ) {
+                       } elseif ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
                                if ( $logger === null ) {
                                        unset( $singletons[$channel] );
                                } else {