phpunit: Add setNullLogger() and make tests default to LegacySpi
== Motivation Mute a log channel, for which the Logger object is injected by service wiring, for a service that is overridden by default, such as 'DBLoadBalancerFactory'. For that, calling setLogger() mid-test would be too late. == Changes * Add a test-only method to LegacyLogger that makes it possible to change its `minimumLevel` attribute, thus making it turn itself into a NullLogger if raised to infinity. This is the same principle we use already for disabled log channels when using MediaWiki normally (see LegacyLogger::__construct). * Previously, the developer's LocalSettings.php was loaded which includes the Spi configuration. This meant other Spi's could be configured which means we might not be dealing with a LegacyLogger object. Similar to what we do with ObjectCache and JobQueue already, make the default Spi in tests the same as the normal MW default. * Add setNullLogger() which makes use of these two. Bug: T248195 Change-Id: Ieade3585812de47342259afa765e230fff06f526
This commit is contained in:
parent
ccddf29d97
commit
e0ed6df864
6 changed files with 162 additions and 32 deletions
|
|
@ -26,6 +26,7 @@ use MWDebug;
|
|||
use MWExceptionHandler;
|
||||
use Psr\Log\AbstractLogger;
|
||||
use Psr\Log\LogLevel;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
use UDPTransport;
|
||||
use WikiMap;
|
||||
|
|
@ -135,6 +136,23 @@ class LegacyLogger extends AbstractLogger {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change an existing Logger singleton to act like NullLogger.
|
||||
*
|
||||
* @internal For use by MediaWikiIntegrationTestCase::setNullLogger
|
||||
* @param null|int $level
|
||||
* @return int
|
||||
*/
|
||||
public function setMinimumForTest( ?int $level ) {
|
||||
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
|
||||
throw new RuntimeException( 'Not allowed outside tests' );
|
||||
}
|
||||
// Set LEVEL_INFINITY if given null, or restore the original level.
|
||||
$original = $this->minimumLevel;
|
||||
$this->minimumLevel = $level ?? self::LEVEL_INFINITY;
|
||||
return $original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs with an arbitrary level.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
namespace MediaWiki\Logger;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* LoggerFactory service provider that creates LegacyLogger instances.
|
||||
*
|
||||
|
|
@ -54,4 +56,16 @@ class LegacySpi implements Spi {
|
|||
return $this->singletons[$channel];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal For use by MediaWikiIntegrationTestCase
|
||||
* @param string $channel
|
||||
* @param LoggerInterface|null $logger
|
||||
* @return LoggerInterface|null
|
||||
*/
|
||||
public function setLoggerForTest( $channel, LoggerInterface $logger = null ) {
|
||||
$ret = $this->singletons[$channel] ?? null;
|
||||
$this->singletons[$channel] = $logger;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,4 +80,24 @@ class LogCapturingSpi implements Spi {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal For use by MediaWikiIntegrationTestCase
|
||||
* @return Spi
|
||||
*/
|
||||
public function getInnerSpi() : Spi {
|
||||
return $this->inner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal For use by MediaWikiIntegrationTestCase
|
||||
* @param string $channel
|
||||
* @param LoggerInterface|null $logger
|
||||
* @return LoggerInterface|null
|
||||
*/
|
||||
public function setLoggerForTest( $channel, LoggerInterface $logger = null ) {
|
||||
$ret = $this->singletons[$channel] ?? null;
|
||||
$this->singletons[$channel] = $logger;
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class TestSetup {
|
|||
global $wgDevelopmentWarnings;
|
||||
global $wgSessionProviders, $wgSessionPbkdf2Iterations;
|
||||
global $wgJobTypeConf;
|
||||
global $wgMWLoggerDefaultSpi;
|
||||
global $wgAuthManagerConfig;
|
||||
global $wgShowExceptionDetails;
|
||||
|
||||
|
|
@ -64,6 +65,13 @@ class TestSetup {
|
|||
$wgJobTypeConf = [
|
||||
'default' => [ 'class' => JobQueueMemory::class, 'order' => 'fifo' ],
|
||||
];
|
||||
// Always default to LegacySpi and LegacyLogger during test
|
||||
// See also MediaWikiIntegrationTestCase::setNullLogger().
|
||||
// Note that MediaWikiLoggerPHPUnitTestListener may wrap this in
|
||||
// a MediaWiki\Logger\LogCapturingSpi at run-time.
|
||||
$wgMWLoggerDefaultSpi = [
|
||||
'class' => \MediaWiki\Logger\LegacySpi::class,
|
||||
];
|
||||
|
||||
$wgUseDatabaseMessages = false; # Set for future resets
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
<?php
|
||||
// phpcs:disable MediaWiki.Commenting.FunctionAnnotations.UnrecognizedAnnotation
|
||||
|
||||
use MediaWiki\Logger\LegacyLogger;
|
||||
use MediaWiki\Logger\LegacySpi;
|
||||
use MediaWiki\Logger\LogCapturingSpi;
|
||||
use MediaWiki\Logger\LoggerFactory;
|
||||
use MediaWiki\Logger\MonologSpi;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
use PHPUnit\Framework\TestResult;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use SebastianBergmann\Comparator\ComparisonFailure;
|
||||
use Wikimedia\Rdbms\Database;
|
||||
use Wikimedia\Rdbms\IDatabase;
|
||||
use Wikimedia\Rdbms\IMaintainableDatabase;
|
||||
use Wikimedia\TestingAccessWrapper;
|
||||
use Wikimedia\Timestamp\ConvertibleTimestamp;
|
||||
|
||||
/**
|
||||
|
|
@ -112,6 +112,12 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
private $loggers = [];
|
||||
|
||||
/**
|
||||
* Holds original loggers which have been ignored by setNullLogger()
|
||||
* @var array<array<LegacyLogger|int>>
|
||||
*/
|
||||
private $ignoredLoggers = [];
|
||||
|
||||
/**
|
||||
* Holds a list of services that were overridden with setService(). Used for printing an error
|
||||
* if overrideMwServices() overrides a service that was previously set.
|
||||
|
|
@ -1119,7 +1125,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the logger for a specified channel, for the duration of the test.
|
||||
* Set the logger for a specified channel, for the duration of the test.
|
||||
* @since 1.27
|
||||
* @param string $channel
|
||||
* @param LoggerInterface $logger
|
||||
|
|
@ -1129,50 +1135,67 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
|
|||
// resetServiceForTesting() to set loggers.
|
||||
|
||||
$provider = LoggerFactory::getProvider();
|
||||
$wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
|
||||
$singletons = $wrappedProvider->singletons;
|
||||
if ( $provider instanceof MonologSpi ) {
|
||||
if ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
|
||||
$prev = $provider->setLoggerForTest( $channel, $logger );
|
||||
if ( !isset( $this->loggers[$channel] ) ) {
|
||||
$this->loggers[$channel] = $singletons['loggers'][$channel] ?? null;
|
||||
// Remember for restoreLoggers()
|
||||
$this->loggers[$channel] = $prev;
|
||||
}
|
||||
$singletons['loggers'][$channel] = $logger;
|
||||
} elseif ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
|
||||
if ( !isset( $this->loggers[$channel] ) ) {
|
||||
$this->loggers[$channel] = $singletons[$channel] ?? null;
|
||||
}
|
||||
$singletons[$channel] = $logger;
|
||||
} else {
|
||||
throw new LogicException( __METHOD__ . ': setting a logger for ' . get_class( $provider )
|
||||
. ' is not implemented' );
|
||||
throw new LogicException( __METHOD__ . ': cannot set logger for ' . get_class( $provider ) );
|
||||
}
|
||||
$wrappedProvider->singletons = $singletons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores loggers replaced by setLogger().
|
||||
* Restore loggers replaced by setLogger() or setNullLogger().
|
||||
* @since 1.27
|
||||
*/
|
||||
private function restoreLoggers() {
|
||||
$provider = LoggerFactory::getProvider();
|
||||
$wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
|
||||
$singletons = $wrappedProvider->singletons;
|
||||
foreach ( $this->loggers as $channel => $logger ) {
|
||||
if ( $provider instanceof MonologSpi ) {
|
||||
if ( $logger === null ) {
|
||||
unset( $singletons['loggers'][$channel] );
|
||||
} else {
|
||||
$singletons['loggers'][$channel] = $logger;
|
||||
}
|
||||
} elseif ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
|
||||
if ( $logger === null ) {
|
||||
unset( $singletons[$channel] );
|
||||
} else {
|
||||
$singletons[$channel] = $logger;
|
||||
}
|
||||
if ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
|
||||
// Replace override with original object or null
|
||||
$provider->setLoggerForTest( $channel, $logger );
|
||||
}
|
||||
}
|
||||
$wrappedProvider->singletons = $singletons;
|
||||
$this->loggers = [];
|
||||
|
||||
foreach (
|
||||
array_splice( $this->ignoredLoggers, 0 )
|
||||
as list( $logger, $level )
|
||||
) {
|
||||
$logger->setMinimumForTest( $level );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore all messages for the specified log channel.
|
||||
*
|
||||
* This is an alternative to setLogger() for when an existing logger
|
||||
* must be changed as well (T248195).
|
||||
*
|
||||
* @since 1.35
|
||||
* @param string $channel
|
||||
*/
|
||||
protected function setNullLogger( $channel ) {
|
||||
$spi = LoggerFactory::getProvider();
|
||||
$spiCapture = null;
|
||||
if ( $spi instanceof LogCapturingSpi ) {
|
||||
$spiCapture = $spi;
|
||||
$spi = $spiCapture->getInnerSpi();
|
||||
}
|
||||
if ( !$spi instanceof LegacySpi ) {
|
||||
throw new LogicException( __METHOD__ . ': cannot set logger for ' . get_class( $spi ) );
|
||||
}
|
||||
|
||||
$existing = $spi->getLogger( $channel );
|
||||
$level = $existing->setMinimumForTest( null );
|
||||
$this->ignoredLoggers[] = [ $existing, $level ];
|
||||
if ( $spiCapture ) {
|
||||
$spiCapture->setLoggerForTest( $channel, new NullLogger() );
|
||||
// Remember to unset in restoreLoggers()
|
||||
$this->loggers[$channel] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use MediaWiki\Logger\LoggerFactory;
|
|||
use MediaWiki\MediaWikiServices;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Wikimedia\Rdbms\LoadBalancer;
|
||||
use Wikimedia\TestingAccessWrapper;
|
||||
|
||||
/**
|
||||
* @covers MediaWikiTestCase
|
||||
|
|
@ -151,6 +152,52 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
|
|||
$this->assertSame( $logger1, $logger2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWikiTestCase::setNullLogger
|
||||
* @covers MediaWikiTestCase::restoreLoggers
|
||||
*/
|
||||
public function testNullLogger_createAndRemove() {
|
||||
$this->setNullLogger( 'tocreate' );
|
||||
$logger = LoggerFactory::getInstance( 'tocreate' );
|
||||
$this->assertInstanceOf( \Psr\Log\NullLogger::class, $logger );
|
||||
|
||||
$this->mediaWikiTearDown();
|
||||
$logger = LoggerFactory::getInstance( 'tocreate' );
|
||||
// Unwrap from LogCapturingSpi
|
||||
$inner = TestingAccessWrapper::newFromObject( $logger )->logger;
|
||||
$this->assertInstanceOf( \MediaWiki\Logger\LegacyLogger::class, $inner );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWikiTestCase::setNullLogger
|
||||
* @covers MediaWikiTestCase::restoreLoggers
|
||||
*/
|
||||
public function testNullLogger_mutateAndRestore() {
|
||||
$logger = LoggerFactory::getInstance( 'tomutate' );
|
||||
// Unwrap from LogCapturingSpi
|
||||
$inner = TestingAccessWrapper::newFromObject( $logger )->logger;
|
||||
$this->assertInstanceOf( \MediaWiki\Logger\LegacyLogger::class, $inner );
|
||||
$this->assertSame(
|
||||
100,
|
||||
TestingAccessWrapper::newFromObject( $inner )->minimumLevel,
|
||||
'original minimumLevel'
|
||||
);
|
||||
|
||||
$this->setNullLogger( 'tomutate' );
|
||||
$this->assertSame(
|
||||
999,
|
||||
TestingAccessWrapper::newFromObject( $inner )->minimumLevel,
|
||||
'changed minimumLevel'
|
||||
);
|
||||
|
||||
$this->mediaWikiTearDown();
|
||||
$this->assertSame(
|
||||
100,
|
||||
TestingAccessWrapper::newFromObject( $inner )->minimumLevel,
|
||||
'restored minimumLevel'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWikiTestCase::setupDatabaseWithTestPrefix
|
||||
* @covers MediaWikiTestCase::copyTestData
|
||||
|
|
|
|||
Loading…
Reference in a new issue