== Change ==
Remove integration from MWDebug methods in favour of capturing
messages from the 'error' channel. This way we fix the following
that we were all blind to:
* PHP built-in errors for native notices and deprecation warnings.
* trigger_error calls from our own code such as in standolone libs,
where we can't use wfLogWarning or wfDeprecated, and instead use
E_USER_WARNING and E_USER_DEPRECATED instead.
* trigger_error calls from third-party Composer libraries, which
similarly can't and don't know about these at all.
I removed support for including a backtrace. XDebug does a better job
of this nowadays, and the information is also available in
mw-error.log already in a more readable manner. Cramming it into a
100px scrollable area made the "Console" tab much less readable in
my opinion. To make it work, we'd need to duplicate the pretty
string formatting code of wfBacktrace() and make it re-usable when
given only a trace array (rather than capture a new backtrace). I
considered re-using MWExceptionHandler::prettyPrintTrace but that is
far more verbose than the string format in wfBacktrace. Creating yet
another untested error pretty print function seemed not worthwhile
as imho in the majority of cases the $caller suffices, and when not,
the toolbar suffices as a nudge to take a look in mw-debug.log, or
check XDebug display_errors, or engage an IDE.
== Test plan ==
1. Add the following to MediaWiki.php#main() as example:
```
wfLogWarning('Hello');
wfDeprecated('Something');
trigger_error('Goodbye');
$x = []; $a = $x['a'];
```
2. Before this change, your native display_errors=1 or XDebug shows
all four. With $wgDebugToolbar enabled, the "Console" tab
showed only the wfLogWarning and wfDeprecated entry.
3. After this change, all four are included
Change-Id: I62d864823ec8ab9b940aae0e0f47b47a728ba861
183 lines
4.7 KiB
PHP
183 lines
4.7 KiB
PHP
<?php
|
|
|
|
use MediaWiki\Request\FauxRequest;
|
|
use MediaWiki\Title\TitleValue;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* @covers MWDebug
|
|
*/
|
|
class MWDebugTest extends MediaWikiIntegrationTestCase {
|
|
|
|
protected function setUp(): void {
|
|
$this->setMwGlobals( 'wgDevelopmentWarnings', false );
|
|
|
|
parent::setUp();
|
|
/** Clear log before each test */
|
|
MWDebug::clearLog();
|
|
}
|
|
|
|
public static function setUpBeforeClass(): void {
|
|
parent::setUpBeforeClass();
|
|
MWDebug::init();
|
|
}
|
|
|
|
public static function tearDownAfterClass(): void {
|
|
MWDebug::deinit();
|
|
parent::tearDownAfterClass();
|
|
}
|
|
|
|
public function testLog() {
|
|
@MWDebug::log( 'logging a string' );
|
|
$this->assertEquals(
|
|
[ [
|
|
'msg' => 'logging a string',
|
|
'type' => 'log',
|
|
'caller' => 'MWDebugTest->testLog',
|
|
] ],
|
|
MWDebug::getLog()
|
|
);
|
|
}
|
|
|
|
public function testWarningProduction() {
|
|
$logger = $this->createMock( LoggerInterface::class );
|
|
$logger->expects( $this->once() )->method( 'info' );
|
|
$this->setLogger( 'warning', $logger );
|
|
|
|
@MWDebug::warning( 'Ohnosecond!' );
|
|
}
|
|
|
|
public function testWarningDevelopment() {
|
|
$this->setMwGlobals( 'wgDevelopmentWarnings', true );
|
|
|
|
$this->expectNotice();
|
|
$this->expectNoticeMessage( 'Ohnosecond!' );
|
|
|
|
MWDebug::warning( 'Ohnosecond!' );
|
|
}
|
|
|
|
/**
|
|
* Message from the error channel are copied to the debug toolbar "Console" log.
|
|
*
|
|
* This normally happens via wfDeprecated -> MWDebug::deprecated -> trigger_error
|
|
* -> MWExceptionHandler -> LoggerFactory -> LegacyLogger -> MWDebug::debugMsg.
|
|
*
|
|
* The above test asserts up until trigger_error.
|
|
* This test asserts from LegacyLogger down.
|
|
*/
|
|
public function testMessagesFromErrorChannel() {
|
|
// Turn off to keep mw-error.log file empty in CI (and thus avoid build failure)
|
|
$this->setMwGlobals( 'wgDebugLogGroups', [] );
|
|
|
|
MWExceptionHandler::handleError( E_USER_DEPRECATED, 'Warning message' );
|
|
$this->assertEquals(
|
|
[ [
|
|
'msg' => 'PHP Deprecated: Warning message',
|
|
'type' => 'warn',
|
|
'caller' => 'MWDebugTest::testMessagesFromErrorChannel',
|
|
] ],
|
|
MWDebug::getLog()
|
|
);
|
|
}
|
|
|
|
public function testDetectDeprecatedOverride() {
|
|
$baseclassInstance = new TitleValue( NS_MAIN, 'Test' );
|
|
|
|
$this->assertFalse(
|
|
MWDebug::detectDeprecatedOverride(
|
|
$baseclassInstance,
|
|
TitleValue::class,
|
|
'getNamespace',
|
|
MW_VERSION
|
|
)
|
|
);
|
|
|
|
// create a dummy subclass that overrides a method
|
|
$subclassInstance = new class ( NS_MAIN, 'Test' ) extends TitleValue {
|
|
public function getNamespace(): int {
|
|
// never called
|
|
return -100;
|
|
}
|
|
};
|
|
|
|
$this->expectDeprecation();
|
|
$this->expectDeprecationMessage( '@anonymous' );
|
|
|
|
MWDebug::detectDeprecatedOverride(
|
|
$subclassInstance,
|
|
TitleValue::class,
|
|
'getNamespace',
|
|
MW_VERSION
|
|
);
|
|
}
|
|
|
|
public function testDeprecated() {
|
|
$this->expectDeprecation();
|
|
$this->expectDeprecationMessage( 'wfOldFunction' );
|
|
|
|
MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
|
|
}
|
|
|
|
/**
|
|
* @doesNotPerformAssertions
|
|
*/
|
|
public function testDeprecatedIgnoreDuplicate() {
|
|
@MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
|
|
MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
|
|
|
|
// If we reach here, than the second one did not throw any deprecation warning.
|
|
// The first one was silenced to seed the ignore logic.
|
|
}
|
|
|
|
/**
|
|
* @doesNotPerformAssertions
|
|
*/
|
|
public function testDeprecatedIgnoreNonConsecutivesDuplicate() {
|
|
@MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
|
|
@MWDebug::warning( 'some warning' );
|
|
@MWDebug::log( 'we could have logged something too' );
|
|
// Another deprecation (not silenced)
|
|
MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
|
|
}
|
|
|
|
public function testAppendDebugInfoToApiResultXmlFormat() {
|
|
$request = $this->newApiRequest(
|
|
[ 'action' => 'help', 'format' => 'xml' ],
|
|
'/api.php?action=help&format=xml'
|
|
);
|
|
|
|
$context = new RequestContext();
|
|
$context->setRequest( $request );
|
|
|
|
$result = new ApiResult( false );
|
|
|
|
MWDebug::appendDebugInfoToApiResult( $context, $result );
|
|
|
|
$this->assertInstanceOf( ApiResult::class, $result );
|
|
$data = $result->getResultData();
|
|
|
|
$expectedKeys = [ 'mwVersion', 'phpEngine', 'phpVersion', 'gitRevision', 'gitBranch',
|
|
'gitViewUrl', 'time', 'log', 'debugLog', 'queries', 'request', 'memory',
|
|
'memoryPeak', 'includes', '_element' ];
|
|
|
|
foreach ( $expectedKeys as $expectedKey ) {
|
|
$this->assertArrayHasKey( $expectedKey, $data['debuginfo'], "debuginfo has $expectedKey" );
|
|
}
|
|
|
|
$xml = ApiFormatXml::recXmlPrint( 'help', $data, null );
|
|
|
|
// exception not thrown
|
|
$this->assertIsString( $xml );
|
|
}
|
|
|
|
/**
|
|
* @param string[] $params
|
|
* @param string $requestUrl
|
|
* @return FauxRequest
|
|
*/
|
|
private function newApiRequest( array $params, $requestUrl ) {
|
|
$req = new FauxRequest( $params );
|
|
$req->setRequestURL( $requestUrl );
|
|
return $req;
|
|
}
|
|
}
|