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
probes:
- screenshot
- trace
- # alerts:
- # navtiming/loadEventEnd: 10%
=== 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.
'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',
$out->setFeedAppendQuery( 'action=history' );
$out->addModules( 'mediawiki.action.history' );
$out->addModuleStyles( [
+ 'mediawiki.interface.helpers.styles',
'mediawiki.action.history.styles',
'mediawiki.special.changeslist',
] );
$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' ? '<' : '>' );
$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();
$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">';
}
$this->rclistOpen = false;
$this->getOutput()->addModuleStyles( [
'mediawiki.icon',
+ 'mediawiki.interface.helpers.styles',
'mediawiki.special.changeslist',
'mediawiki.special.changeslist.enhanced',
] );
--- /dev/null
+<?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 );
+ }
+ };
+ }
+}
*/
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() );
}
}
/**
- * 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,
}
/**
- * 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
$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',
] );
$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',
] );
],
],
'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'
'lessMessages' => [
'parentheses-start',
'parentheses-end',
+ 'pipe-separator'
],
'targets' => [ 'desktop', 'mobile' ],
],
'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',
'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' => [
+++ /dev/null
-.mw-tag-markers {
- &:before {
- content: '@{msg-parentheses-start}';
- }
-
- &:after {
- content: '@{msg-parentheses-end}';
- }
-}
--- /dev/null
+/**
+ * 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}';
+ }
+}
.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}';
- }
-}
'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",
--- /dev/null
+<?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 );
+ }
+}
class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
private $cliArgs;
+ private $logListener;
public function __construct( $ignorableOptions, $cliArgs ) {
$ignore = function ( $arg ) {
$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;
--- /dev/null
+<?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 );
+ }
+}
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;
$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;
}
} else {
$singletons['loggers'][$channel] = $logger;
}
- } elseif ( $provider instanceof LegacySpi ) {
+ } elseif ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
if ( $logger === null ) {
unset( $singletons[$channel] );
} else {