diff options
Diffstat (limited to 'www/wiki/tests/phpunit/includes/debug/logger')
6 files changed, 744 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/debug/logger/LegacyLoggerTest.php b/www/wiki/tests/phpunit/includes/debug/logger/LegacyLoggerTest.php new file mode 100644 index 00000000..37a28c36 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/debug/logger/LegacyLoggerTest.php @@ -0,0 +1,175 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger; + +use MediaWikiTestCase; +use Psr\Log\LogLevel; + +class LegacyLoggerTest extends MediaWikiTestCase { + + /** + * @covers MediaWiki\Logger\LegacyLogger::interpolate + * @dataProvider provideInterpolate + */ + public function testInterpolate( $message, $context, $expect ) { + $this->assertEquals( + $expect, LegacyLogger::interpolate( $message, $context ) ); + } + + public function provideInterpolate() { + $e = new \Exception( 'boom!' ); + $d = new \DateTime(); + return [ + [ + 'no-op', + [], + 'no-op', + ], + [ + 'Hello {world}!', + [ + 'world' => 'World', + ], + 'Hello World!', + ], + [ + '{greeting} {user}', + [ + 'greeting' => 'Goodnight', + 'user' => 'Moon', + ], + 'Goodnight Moon', + ], + [ + 'Oops {key_not_set}', + [], + 'Oops {key_not_set}', + ], + [ + '{ not interpolated }', + [ + 'not interpolated' => 'This should NOT show up in the message', + ], + '{ not interpolated }', + ], + [ + '{null}', + [ + 'null' => null, + ], + '[Null]', + ], + [ + '{bool}', + [ + 'bool' => true, + ], + 'true', + ], + [ + '{float}', + [ + 'float' => 1.23, + ], + '1.23', + ], + [ + '{array}', + [ + 'array' => [ 1, 2, 3 ], + ], + '[Array(3)]', + ], + [ + '{exception}', + [ + 'exception' => $e, + ], + '[Exception ' . get_class( $e ) . '( ' . + $e->getFile() . ':' . $e->getLine() . ') ' . + $e->getMessage() . ']', + ], + [ + '{datetime}', + [ + 'datetime' => $d, + ], + $d->format( 'c' ), + ], + [ + '{object}', + [ + 'object' => new \stdClass, + ], + '[Object stdClass]', + ], + ]; + } + + /** + * @covers MediaWiki\Logger\LegacyLogger::shouldEmit + * @dataProvider provideShouldEmit + */ + public function testShouldEmit( $level, $config, $expected ) { + $this->setMwGlobals( 'wgDebugLogGroups', [ 'fakechannel' => $config ] ); + $this->assertEquals( + $expected, + LegacyLogger::shouldEmit( 'fakechannel', 'some message', $level, [] ) + ); + } + + public static function provideShouldEmit() { + $dest = [ 'destination' => 'foobar' ]; + $tests = [ + [ + LogLevel::DEBUG, + $dest, + true + ], + [ + LogLevel::WARNING, + $dest + [ 'level' => LogLevel::INFO ], + true, + ], + [ + LogLevel::INFO, + $dest + [ 'level' => LogLevel::CRITICAL ], + false, + ], + ]; + + if ( class_exists( '\Monolog\Logger' ) ) { + $tests[] = [ + \Monolog\Logger::INFO, + $dest + [ 'level' => LogLevel::INFO ], + true, + ]; + $tests[] = [ + \Monolog\Logger::WARNING, + $dest + [ 'level' => LogLevel::EMERGENCY ], + false, + ]; + } + + return $tests; + } + +} diff --git a/www/wiki/tests/phpunit/includes/debug/logger/MonologSpiTest.php b/www/wiki/tests/phpunit/includes/debug/logger/MonologSpiTest.php new file mode 100644 index 00000000..fda3ac61 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/debug/logger/MonologSpiTest.php @@ -0,0 +1,136 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger; + +use MediaWikiTestCase; +use Wikimedia\TestingAccessWrapper; + +class MonologSpiTest extends MediaWikiTestCase { + + /** + * @covers MediaWiki\Logger\MonologSpi::mergeConfig + */ + public function testMergeConfig() { + $base = [ + 'loggers' => [ + '@default' => [ + 'processors' => [ 'constructor' ], + 'handlers' => [ 'constructor' ], + ], + ], + 'processors' => [ + 'constructor' => [ + 'class' => 'constructor', + ], + ], + 'handlers' => [ + 'constructor' => [ + 'class' => 'constructor', + 'formatter' => 'constructor', + ], + ], + 'formatters' => [ + 'constructor' => [ + 'class' => 'constructor', + ], + ], + ]; + + $fixture = new MonologSpi( $base ); + $this->assertSame( + $base, + TestingAccessWrapper::newFromObject( $fixture )->config + ); + + $fixture->mergeConfig( [ + 'loggers' => [ + 'merged' => [ + 'processors' => [ 'merged' ], + 'handlers' => [ 'merged' ], + ], + ], + 'processors' => [ + 'merged' => [ + 'class' => 'merged', + ], + ], + 'magic' => [ + 'idkfa' => [ 'xyzzy' ], + ], + 'handlers' => [ + 'merged' => [ + 'class' => 'merged', + 'formatter' => 'merged', + ], + ], + 'formatters' => [ + 'merged' => [ + 'class' => 'merged', + ], + ], + ] ); + $this->assertSame( + [ + 'loggers' => [ + '@default' => [ + 'processors' => [ 'constructor' ], + 'handlers' => [ 'constructor' ], + ], + 'merged' => [ + 'processors' => [ 'merged' ], + 'handlers' => [ 'merged' ], + ], + ], + 'processors' => [ + 'constructor' => [ + 'class' => 'constructor', + ], + 'merged' => [ + 'class' => 'merged', + ], + ], + 'handlers' => [ + 'constructor' => [ + 'class' => 'constructor', + 'formatter' => 'constructor', + ], + 'merged' => [ + 'class' => 'merged', + 'formatter' => 'merged', + ], + ], + 'formatters' => [ + 'constructor' => [ + 'class' => 'constructor', + ], + 'merged' => [ + 'class' => 'merged', + ], + ], + 'magic' => [ + 'idkfa' => [ 'xyzzy' ], + ], + ], + TestingAccessWrapper::newFromObject( $fixture )->config + ); + } + +} diff --git a/www/wiki/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php b/www/wiki/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php new file mode 100644 index 00000000..baa4df73 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php @@ -0,0 +1,76 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger\Monolog; + +use MediaWikiTestCase; +use PHPUnit_Framework_Error_Notice; + +/** + * @covers \MediaWiki\Logger\Monolog\AvroFormatter + */ +class AvroFormatterTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'AvroStringIO' ) ) { + $this->markTestSkipped( 'Avro is required for the AvroFormatterTest' ); + } + parent::setUp(); + } + + public function testSchemaNotAvailable() { + $formatter = new AvroFormatter( [] ); + $this->setExpectedException( + 'PHPUnit_Framework_Error_Notice', + "The schema for channel 'marty' is not available" + ); + $formatter->format( [ 'channel' => 'marty' ] ); + } + + public function testSchemaNotAvailableReturnValue() { + $formatter = new AvroFormatter( [] ); + $noticeEnabled = PHPUnit_Framework_Error_Notice::$enabled; + // disable conversion of notices + PHPUnit_Framework_Error_Notice::$enabled = false; + // have to keep the user notice from being output + \Wikimedia\suppressWarnings(); + $res = $formatter->format( [ 'channel' => 'marty' ] ); + \Wikimedia\restoreWarnings(); + PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled; + $this->assertNull( $res ); + } + + public function testDoesSomethingWhenSchemaAvailable() { + $formatter = new AvroFormatter( [ + 'string' => [ + 'schema' => [ 'type' => 'string' ], + 'revision' => 1010101, + ] + ] ); + $res = $formatter->format( [ + 'channel' => 'string', + 'context' => 'better to be', + ] ); + $this->assertNotNull( $res ); + // basically just tell us if avro changes its string encoding, or if + // we completely fail to generate a log message. + $this->assertEquals( 'AAAAAAAAD2m1GGJldHRlciB0byBiZQ==', base64_encode( $res ) ); + } +} diff --git a/www/wiki/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php b/www/wiki/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php new file mode 100644 index 00000000..4c0ca04f --- /dev/null +++ b/www/wiki/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php @@ -0,0 +1,227 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger\Monolog; + +use MediaWikiTestCase; +use Monolog\Logger; +use Wikimedia\TestingAccessWrapper; + +/** + * @covers \MediaWiki\Logger\Monolog\KafkaHandler + */ +class KafkaHandlerTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'Monolog\Handler\AbstractProcessingHandler' ) + || !class_exists( 'Kafka\Produce' ) + ) { + $this->markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' ); + } + + parent::setUp(); + } + + public function topicNamingProvider() { + return [ + [ [], 'monolog_foo' ], + [ [ 'alias' => [ 'foo' => 'bar' ] ], 'bar' ] + ]; + } + + /** + * @dataProvider topicNamingProvider + */ + public function testTopicNaming( $options, $expect ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( [ 'A' ] ) ); + $produce->expects( $this->once() ) + ->method( 'setMessages' ) + ->with( $expect, $this->anything(), $this->anything() ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->returnValue( true ) ); + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ] ); + } + + public function swallowsExceptionsWhenRequested() { + return [ + // defaults to false + [ [], true ], + // also try false explicitly + [ [ 'swallowExceptions' => false ], true ], + // turn it on + [ [ 'swallowExceptions' => true ], false ], + ]; + } + + /** + * @dataProvider swallowsExceptionsWhenRequested + */ + public function testGetAvailablePartitionsException( $options, $expectException ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->throwException( new \Kafka\Exception ) ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->returnValue( true ) ); + + if ( $expectException ) { + $this->setExpectedException( 'Kafka\Exception' ); + } + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ] ); + + if ( !$expectException ) { + $this->assertTrue( true, 'no exception was thrown' ); + } + } + + /** + * @dataProvider swallowsExceptionsWhenRequested + */ + public function testSendException( $options, $expectException ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( [ 'A' ] ) ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->throwException( new \Kafka\Exception ) ); + + if ( $expectException ) { + $this->setExpectedException( 'Kafka\Exception' ); + } + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ] ); + + if ( !$expectException ) { + $this->assertTrue( true, 'no exception was thrown' ); + } + } + + public function testHandlesNullFormatterResult() { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( [ 'A' ] ) ); + $mockMethod = $produce->expects( $this->exactly( 2 ) ) + ->method( 'setMessages' ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->returnValue( true ) ); + // evil hax + $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher; + TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher = + new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [ + [ $this->anything(), $this->anything(), [ 'words' ] ], + [ $this->anything(), $this->anything(), [ 'lines' ] ] + ] ); + + $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, [] ); + $handler->setFormatter( $formatter ); + for ( $i = 0; $i < 3; ++$i ) { + $handler->handle( [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ] ); + } + } + + public function testBatchHandlesNullFormatterResult() { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( [ 'A' ] ) ); + $produce->expects( $this->once() ) + ->method( 'setMessages' ) + ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->returnValue( true ) ); + + $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, [] ); + $handler->setFormatter( $formatter ); + $handler->handleBatch( [ + [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ], + [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ], + [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ], + ] ); + } +} diff --git a/www/wiki/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php b/www/wiki/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php new file mode 100644 index 00000000..2768d329 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php @@ -0,0 +1,75 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger\Monolog; + +use InvalidArgumentException; +use LengthException; +use LogicException; +use MediaWikiTestCase; +use Wikimedia\TestingAccessWrapper; + +class LineFormatterTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'Monolog\Formatter\LineFormatter' ) ) { + $this->markTestSkipped( 'This test requires monolog to be installed' ); + } + parent::setUp(); + } + + /** + * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException + */ + public function testNormalizeExceptionNoTrace() { + $fixture = new LineFormatter(); + $fixture->includeStacktraces( false ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new LogicException( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertNotContains( "\n #0", $out ); + } + + /** + * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException + */ + public function testNormalizeExceptionTrace() { + $fixture = new LineFormatter(); + $fixture->includeStacktraces( true ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new LogicException( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertContains( "\n #0", $out ); + } +} diff --git a/www/wiki/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php b/www/wiki/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php new file mode 100644 index 00000000..1ee188e7 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php @@ -0,0 +1,55 @@ +<?php + +namespace MediaWiki\Logger\Monolog; + +class LogstashFormatterTest extends \PHPUnit\Framework\TestCase { + /** + * @dataProvider provideV1 + * @param array $record The input record. + * @param array $expected Associative array of expected keys and their values. + * @param array $notExpected List of keys that should not exist. + */ + public function testV1( array $record, array $expected, array $notExpected ) { + $formatter = new LogstashFormatter( 'app', 'system', null, null, LogstashFormatter::V1 ); + $formatted = json_decode( $formatter->format( $record ), true ); + foreach ( $expected as $key => $value ) { + $this->assertArrayHasKey( $key, $formatted ); + $this->assertSame( $value, $formatted[$key] ); + } + foreach ( $notExpected as $key ) { + $this->assertArrayNotHasKey( $key, $formatted ); + } + } + + public function provideV1() { + return [ + [ + [ 'extra' => [ 'foo' => 1 ], 'context' => [ 'bar' => 2 ] ], + [ 'foo' => 1, 'bar' => 2 ], + [ 'logstash_formatter_key_conflict' ], + ], + [ + [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ], + [ 'url' => 1, 'c_url' => 2, 'logstash_formatter_key_conflict' => [ 'url' ] ], + [], + ], + [ + [ 'channel' => 'x', 'context' => [ 'channel' => 'y' ] ], + [ 'channel' => 'x', 'c_channel' => 'y', + 'logstash_formatter_key_conflict' => [ 'channel' ] ], + [], + ], + ]; + } + + public function testV1WithPrefix() { + $formatter = new LogstashFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 ); + $record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ]; + $formatted = json_decode( $formatter->format( $record ), true ); + $this->assertArrayHasKey( 'url', $formatted ); + $this->assertSame( 1, $formatted['url'] ); + $this->assertArrayHasKey( 'ctx_url', $formatted ); + $this->assertSame( 2, $formatted['ctx_url'] ); + $this->assertArrayNotHasKey( 'c_url', $formatted ); + } +} |