From 2bda4992857186f1801d16f520ae15cd5367c06f Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Sat, 12 Jun 2021 14:51:08 +0200 Subject: [PATCH 01/21] Include the tests This begins porting as per https://github.com/php-fig/log/pull/76#issuecomment-858743302 --- tests/DummyTest.php | 18 +++++ tests/LoggerInterfaceTest.php | 137 +++++++++++++++++++++++++++++++ tests/LoggerTest.php | 34 ++++++++ tests/TestLogger.php | 147 ++++++++++++++++++++++++++++++++++ 4 files changed, 336 insertions(+) create mode 100644 tests/DummyTest.php create mode 100644 tests/LoggerInterfaceTest.php create mode 100644 tests/LoggerTest.php create mode 100644 tests/TestLogger.php diff --git a/tests/DummyTest.php b/tests/DummyTest.php new file mode 100644 index 0000000..9638c11 --- /dev/null +++ b/tests/DummyTest.php @@ -0,0 +1,18 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + + $this->expectException(InvalidArgumentException::class); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php new file mode 100644 index 0000000..f384316 --- /dev/null +++ b/tests/LoggerTest.php @@ -0,0 +1,34 @@ +getMockBuilder('Psr\Log\Test\TestLogger') + ->enableProxyingToOriginalMethods() + ->getMock(); + + return $mock; + } + + public function testLog() + { + $message = uniqid('message'); + $level = LogLevel::INFO; + $subject = $this->createSubject(); + + $subject->log($level, $message, array()); + + $this->assertTrue($subject->hasRecord($message, $level)); + } +} \ No newline at end of file diff --git a/tests/TestLogger.php b/tests/TestLogger.php new file mode 100644 index 0000000..1be3230 --- /dev/null +++ b/tests/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} From ac26769b4f8a8ae67c6dc804e24e00d05dc4475c Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Sat, 12 Jun 2021 14:53:08 +0200 Subject: [PATCH 02/21] Adjust namespaces --- tests/DummyTest.php | 2 +- tests/LoggerInterfaceTest.php | 2 +- tests/LoggerTest.php | 4 ++-- tests/TestLogger.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/DummyTest.php b/tests/DummyTest.php index 9638c11..5ac4034 100644 --- a/tests/DummyTest.php +++ b/tests/DummyTest.php @@ -1,6 +1,6 @@ assertTrue($subject->hasRecord($message, $level)); } -} \ No newline at end of file +} diff --git a/tests/TestLogger.php b/tests/TestLogger.php index 1be3230..9afc2c1 100644 --- a/tests/TestLogger.php +++ b/tests/TestLogger.php @@ -1,6 +1,6 @@ Date: Sat, 19 Jun 2021 12:32:46 +0200 Subject: [PATCH 03/21] Adapt tests - Create a padding to provide compatibility between PHPUnit 4 and 8. - Move `TestLogger` to `Stub`. - Add an actual test that will test the `TestLogger`. --- tests/AbstractTestCase.php | 10 +++++++++ tests/LoggerInterfaceTest.php | 3 +-- tests/{ => Stub}/TestLogger.php | 2 +- tests/TestLoggerTest.php | 39 +++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 tests/AbstractTestCase.php rename tests/{ => Stub}/TestLogger.php (99%) create mode 100644 tests/TestLoggerTest.php diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php new file mode 100644 index 0000000..4f9fd19 --- /dev/null +++ b/tests/AbstractTestCase.php @@ -0,0 +1,10 @@ +logger = $this->createSubject(); + } + + /** + * @return TestLogger A new mock of the test subject. + */ + protected function createSubject() + { + $mock = $this->getMockBuilder('Psr\Log\Util\Tests\Stub\TestLogger') + ->enableProxyingToOriginalMethods() + ->getMock(); + + return $mock; + } + + public function getLogger() + { + return $this->logger; + } + + public function getLogs() + { + return $this->logger->records; + } +} From 2e80eb4c66c765f2bdbb0f48a5f2ba6da206371b Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Sat, 19 Jun 2021 12:35:17 +0200 Subject: [PATCH 04/21] Remove illegal annotation As per php-fig/log#75. --- tests/LoggerInterfaceTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/LoggerInterfaceTest.php b/tests/LoggerInterfaceTest.php index 55656dc..ac1c162 100644 --- a/tests/LoggerInterfaceTest.php +++ b/tests/LoggerInterfaceTest.php @@ -64,11 +64,12 @@ public function provideLevelsAndMessages() ); } + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ public function testThrowsOnInvalidLevel() { $logger = $this->getLogger(); - - $this->expectException(InvalidArgumentException::class); $logger->log('invalid level', 'Foo'); } From 2e29776a3427e809d981f73facecbfc69bd2c519 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Sat, 19 Jun 2021 12:39:01 +0200 Subject: [PATCH 05/21] Remove redundant test --- tests/LoggerTest.php | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 tests/LoggerTest.php diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php deleted file mode 100644 index a8beed4..0000000 --- a/tests/LoggerTest.php +++ /dev/null @@ -1,34 +0,0 @@ -getMockBuilder('Psr\Log\Test\TestLogger') - ->enableProxyingToOriginalMethods() - ->getMock(); - - return $mock; - } - - public function testLog() - { - $message = uniqid('message'); - $level = LogLevel::INFO; - $subject = $this->createSubject(); - - $subject->log($level, $message, array()); - - $this->assertTrue($subject->hasRecord($message, $level)); - } -} From 7fd7e8cec34ed2d31b6a14a8941c2b806cc57846 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Sat, 19 Jun 2021 12:39:25 +0200 Subject: [PATCH 06/21] Correct syntax in test Attempt to make compatible with PHP 5.3. --- tests/Stub/TestLogger.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Stub/TestLogger.php b/tests/Stub/TestLogger.php index 9cd9a72..25eb146 100644 --- a/tests/Stub/TestLogger.php +++ b/tests/Stub/TestLogger.php @@ -59,20 +59,20 @@ class TestLogger extends AbstractLogger /** * @var array */ - public $records = []; + public $records = array(); - public $recordsByLevel = []; + public $recordsByLevel = array(); /** * @inheritdoc */ - public function log($level, $message, array $context = []) + public function log($level, $message, array $context = array()) { - $record = [ + $record = array( 'level' => $level, 'message' => $message, 'context' => $context, - ]; + ); $this->recordsByLevel[$record['level']][] = $record; $this->records[] = $record; @@ -86,7 +86,7 @@ public function hasRecords($level) public function hasRecord($record, $level) { if (is_string($record)) { - $record = ['message' => $record]; + $record = array('message' => $record); } return $this->hasRecordThatPasses(function ($rec) use ($record) { if ($rec['message'] !== $record['message']) { @@ -133,7 +133,7 @@ public function __call($method, $args) $level = strtolower($matches[2]); if (method_exists($this, $genericMethod)) { $args[] = $level; - return call_user_func_array([$this, $genericMethod], $args); + return call_user_func_array(array($this, $genericMethod), $args); } } throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); @@ -141,7 +141,7 @@ public function __call($method, $args) public function reset() { - $this->records = []; - $this->recordsByLevel = []; + $this->records = array(); + $this->recordsByLevel = array(); } } From 14ab818c69ae4d6f9fc89fac93e6eb12e5ce9845 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Sat, 19 Jun 2021 13:22:00 +0200 Subject: [PATCH 07/21] Correct tests - Format messages. - Validate log level. - Expose entries. - Normalize entries for test. - Fixed date not having a timezone (important if no timezone set on server). --- tests/LoggerInterfaceTest.php | 3 ++- tests/Stub/TestLogger.php | 41 ++++++++++++++++++++++++++++++++++- tests/TestLoggerTest.php | 7 +++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/tests/LoggerInterfaceTest.php b/tests/LoggerInterfaceTest.php index ac1c162..3017432 100644 --- a/tests/LoggerInterfaceTest.php +++ b/tests/LoggerInterfaceTest.php @@ -2,6 +2,7 @@ namespace Psr\Log\Util\Tests; +use DateTimeZone; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; @@ -111,7 +112,7 @@ public function testContextCanContainAnything() 'int' => 0, 'float' => 0.5, 'nested' => array('with object' => new DummyTest), - 'object' => new \DateTime, + 'object' => new \DateTime('now', new DateTimeZone('+00:00')), 'resource' => fopen('php://memory', 'r'), 'closed' => $closed, ); diff --git a/tests/Stub/TestLogger.php b/tests/Stub/TestLogger.php index 25eb146..ca9589a 100644 --- a/tests/Stub/TestLogger.php +++ b/tests/Stub/TestLogger.php @@ -3,6 +3,8 @@ namespace Psr\Log\Util\Tests\Stub; use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use ReflectionClass; /** * Used for testing purposes. @@ -68,9 +70,13 @@ class TestLogger extends AbstractLogger */ public function log($level, $message, array $context = array()) { + if (!in_array($level, $this->getLogLevels(), true)) { + throw new InvalidArgumentException(sprintf('Log level "%1$s" is not valid', $level)); + } + $record = array( 'level' => $level, - 'message' => $message, + 'message' => $this->formatMessage($message, $level, $context), 'context' => $context, ); @@ -78,6 +84,39 @@ public function log($level, $message, array $context = array()) $this->records[] = $record; } + public function interpolateContext($message, array $context) + { + return preg_replace_callback('!\{([^\}\s]*)\}!', function ($matches) use ($context) { + $key = isset($matches[1]) ? $matches[1] : null; + if (array_key_exists($key, $context)) { + return $context[$key]; + } + + return $matches[0]; + }, $message); + } + + public function formatMessage($message, $level, array $context) + { + $message = $this->interpolateContext($message, $context); + $message = "$level $message"; + + return $message; + } + + public function getLogLevels() + { + $reflection = new ReflectionClass('Psr\Log\LogLevel'); + $constants = $reflection->getConstants(); + + return $constants; + } + + public function getRecords() + { + return $this->records; + } + public function hasRecords($level) { return isset($this->recordsByLevel[$level]); diff --git a/tests/TestLoggerTest.php b/tests/TestLoggerTest.php index 0d50403..2ddff4d 100644 --- a/tests/TestLoggerTest.php +++ b/tests/TestLoggerTest.php @@ -34,6 +34,11 @@ public function getLogger() public function getLogs() { - return $this->logger->records; + $records = $this->logger->getRecords(); + $messages = array_map(function ($record) { + return isset($record['message']) ? $record['message'] : null; + }, $records); + + return $messages; } } From 82ce817b6d0baa7b46ae0fb9fd18df251c056d80 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Sat, 19 Jun 2021 13:25:00 +0200 Subject: [PATCH 08/21] No longer fail build immediately if a job fails This ensures that we build on all PHP versions, every time. --- .github/workflows/continuous-integration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1ba957d..64d27b3 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -24,6 +24,7 @@ jobs: - php-versions: 5.4 dependency-levels: 'lowest' experimental: false + fail-fast: false continue-on-error: ${{ matrix.experimental }} steps: From b9b0f0418a27b33dcdf6ac94bbfb880b93f3f27a Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Sat, 19 Jun 2021 13:29:52 +0200 Subject: [PATCH 09/21] Escape namespace separators in class name --- tests/TestLoggerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestLoggerTest.php b/tests/TestLoggerTest.php index 2ddff4d..8a7ad55 100644 --- a/tests/TestLoggerTest.php +++ b/tests/TestLoggerTest.php @@ -20,7 +20,7 @@ public function setUp() */ protected function createSubject() { - $mock = $this->getMockBuilder('Psr\Log\Util\Tests\Stub\TestLogger') + $mock = $this->getMockBuilder('Psr\\Log\\Util\\Tests\\Stub\\TestLogger') ->enableProxyingToOriginalMethods() ->getMock(); From 60f5ffea1ea55ea62976a78f2210c15bef118756 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 21:12:34 +0200 Subject: [PATCH 10/21] Revert "No longer fail build immediately if a job fails" This reverts commit 82ce817b, according to https://github.com/php-fig/log-util/pull/2#discussion_r655343642. See #3. --- .github/workflows/continuous-integration.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 64d27b3..1ba957d 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -24,7 +24,6 @@ jobs: - php-versions: 5.4 dependency-levels: 'lowest' experimental: false - fail-fast: false continue-on-error: ${{ matrix.experimental }} steps: From 9de4c996049f6aaf8b613e5e1de24399c3171543 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 21:14:29 +0200 Subject: [PATCH 11/21] Remove `@internal` annotation See https://github.com/php-fig/log-util/pull/2#discussion_r655221239. --- tests/DummyTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/DummyTest.php b/tests/DummyTest.php index 5ac4034..f3fb851 100644 --- a/tests/DummyTest.php +++ b/tests/DummyTest.php @@ -6,8 +6,6 @@ * This class is internal and does not follow the BC promise. * * Do NOT use this class in any way. - * - * @internal */ class DummyTest { From 2a66682c5124e5b0a512e6c28145a8a93e6d4373 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 21:19:32 +0200 Subject: [PATCH 12/21] Move `DummyTest` to `Stub` sub-namespace --- tests/LoggerInterfaceTest.php | 5 +++-- tests/{ => Stub}/DummyTest.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) rename tests/{ => Stub}/DummyTest.php (85%) diff --git a/tests/LoggerInterfaceTest.php b/tests/LoggerInterfaceTest.php index 3017432..8bd574f 100644 --- a/tests/LoggerInterfaceTest.php +++ b/tests/LoggerInterfaceTest.php @@ -5,6 +5,7 @@ use DateTimeZone; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; +use Psr\Log\Util\Tests\Stub\DummyTest; /** * Provides a base test class for ensuring compliance with the LoggerInterface. @@ -86,9 +87,9 @@ public function testContextReplacement() public function testObjectCastToString() { if (method_exists($this, 'createPartialMock')) { - $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + $dummy = $this->createPartialMock('Psr\Log\Test\Stub\DummyTest', array('__toString')); } else { - $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + $dummy = $this->getMock('Psr\Log\Test\Stub\DummyTest', array('__toString')); } $dummy->expects($this->once()) ->method('__toString') diff --git a/tests/DummyTest.php b/tests/Stub/DummyTest.php similarity index 85% rename from tests/DummyTest.php rename to tests/Stub/DummyTest.php index f3fb851..bdbba61 100644 --- a/tests/DummyTest.php +++ b/tests/Stub/DummyTest.php @@ -1,6 +1,6 @@ Date: Mon, 21 Jun 2021 21:33:52 +0200 Subject: [PATCH 13/21] Remove `DummyTest` in favour of a configured mock This removes the need for this extra class, giving the test more control. This also re-factors some tests, making them easier to understand. --- tests/LoggerInterfaceTest.php | 33 +++++++++++++++++++++++---------- tests/Stub/DummyTest.php | 16 ---------------- 2 files changed, 23 insertions(+), 26 deletions(-) delete mode 100644 tests/Stub/DummyTest.php diff --git a/tests/LoggerInterfaceTest.php b/tests/LoggerInterfaceTest.php index 8bd574f..e8d403d 100644 --- a/tests/LoggerInterfaceTest.php +++ b/tests/LoggerInterfaceTest.php @@ -5,7 +5,6 @@ use DateTimeZone; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; -use Psr\Log\Util\Tests\Stub\DummyTest; /** * Provides a base test class for ensuring compliance with the LoggerInterface. @@ -86,18 +85,14 @@ public function testContextReplacement() public function testObjectCastToString() { - if (method_exists($this, 'createPartialMock')) { - $dummy = $this->createPartialMock('Psr\Log\Test\Stub\DummyTest', array('__toString')); - } else { - $dummy = $this->getMock('Psr\Log\Test\Stub\DummyTest', array('__toString')); - } + $string = uniqid('DUMMY'); + $dummy = $this->createStringable($string); $dummy->expects($this->once()) - ->method('__toString') - ->will($this->returnValue('DUMMY')); + ->method('__toString'); $this->getLogger()->warning($dummy); - $expected = array('warning DUMMY'); + $expected = array("warning $string"); $this->assertEquals($expected, $this->getLogs()); } @@ -112,7 +107,7 @@ public function testContextCanContainAnything() 'string' => 'Foo', 'int' => 0, 'float' => 0.5, - 'nested' => array('with object' => new DummyTest), + 'nested' => array('with object' => $this->createStringable()), 'object' => new \DateTime('now', new DateTimeZone('+00:00')), 'resource' => fopen('php://memory', 'r'), 'closed' => $closed, @@ -136,4 +131,22 @@ public function testContextExceptionKeyCanBeExceptionOrOtherValues() ); $this->assertEquals($expected, $this->getLogs()); } + + /** + * Creates a mock of a `Stringable` + * + * @param string $string The string that must be represented by the stringable. + * @return \PHPUnit_Framework_MockObject_MockObject A mock of an object that has a `__toString()` method. + */ + protected function createStringable($string = '') + { + $mock = $this->getMockBuilder('Stringable') + ->setMethods(array('__toString')) + ->getMock(); + + $mock->method('__toString') + ->will($this->returnValue($string)); + + return $mock; + } } diff --git a/tests/Stub/DummyTest.php b/tests/Stub/DummyTest.php deleted file mode 100644 index bdbba61..0000000 --- a/tests/Stub/DummyTest.php +++ /dev/null @@ -1,16 +0,0 @@ - Date: Mon, 21 Jun 2021 21:46:00 +0200 Subject: [PATCH 14/21] Remove `callable` typehint in favour of PHPDoc/Psalm This was incompatible with PHP 5.3. The new docs far more correctly describe the contract. --- tests/Stub/TestLogger.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/Stub/TestLogger.php b/tests/Stub/TestLogger.php index ca9589a..d549726 100644 --- a/tests/Stub/TestLogger.php +++ b/tests/Stub/TestLogger.php @@ -152,7 +152,15 @@ public function hasRecordThatMatches($regex, $level) }, $level); } - public function hasRecordThatPasses(callable $predicate, $level) + /** + * Determines whether the logger has logged matching records of the specified level. + * + * @param callable(array{level: \Psr\Log\LogLevel::*, message: string, context: array}, int): bool $predicate + * The function used to evaluate whether a record matches. + * @param \Psr\Log\LogLevel::* $level The level of the record + * @return bool True if a matching record has been logged; false otherwise. + */ + public function hasRecordThatPasses($predicate, $level) { if (!isset($this->recordsByLevel[$level])) { return false; From ee4e2e3a89e797435f6547a7fbe25a4c619950ad Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 21:50:38 +0200 Subject: [PATCH 15/21] Fix PHPCS --- tests/AbstractTestCase.php | 11 +++++++---- tests/LoggerInterfaceTest.php | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 4f9fd19..d4da2a4 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -3,8 +3,11 @@ namespace Psr\Log\Util\Tests; if (class_exists('PHPUnit\Framework\TestCase')) { - class AbstractTestCase extends \PHPUnit\Framework\TestCase {} -} -elseif (class_exists('PHPUnit_Framework_TestCase')) { - class AbstractTestCase extends \PHPUnit_Framework_TestCase {} + class AbstractTestCase extends \PHPUnit\Framework\TestCase + { + } +} elseif (class_exists('PHPUnit_Framework_TestCase')) { + class AbstractTestCase extends \PHPUnit_Framework_TestCase + { + } } diff --git a/tests/LoggerInterfaceTest.php b/tests/LoggerInterfaceTest.php index e8d403d..b98b17b 100644 --- a/tests/LoggerInterfaceTest.php +++ b/tests/LoggerInterfaceTest.php @@ -45,8 +45,8 @@ public function testLogsAtAllLevels($level, $message) $logger->log($level, $message, array('user' => 'Bob')); $expected = array( - $level.' message of level '.$level.' with context: Bob', - $level.' message of level '.$level.' with context: Bob', + "$level message of level $level with context: Bob", + "$level message of level $level with context: Bob", ); $this->assertEquals($expected, $this->getLogs()); } From c06e6856a3cb2462d49acd562334307210b7c521 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 21:52:46 +0200 Subject: [PATCH 16/21] Allow class re-declaration In this particular scenario, this is necessary to make the creation of tests easier. --- tests/AbstractTestCase.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index d4da2a4..a5f5686 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -7,6 +7,7 @@ class AbstractTestCase extends \PHPUnit\Framework\TestCase { } } elseif (class_exists('PHPUnit_Framework_TestCase')) { + // phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses class AbstractTestCase extends \PHPUnit_Framework_TestCase { } From 599e7f1c740a1a755ad8442a50b8b18fd6f17af6 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 21:59:08 +0200 Subject: [PATCH 17/21] Re-factor SUT instantiation The `setUp()` method signature here was not compatible with later PHPUnit versions due to the `void` typehint in the latter. Debugging output confirmed that a different instance of the logger still gets created for every test after refactoring. --- tests/TestLoggerTest.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/TestLoggerTest.php b/tests/TestLoggerTest.php index 8a7ad55..4c61be0 100644 --- a/tests/TestLoggerTest.php +++ b/tests/TestLoggerTest.php @@ -2,19 +2,13 @@ namespace Psr\Log\Util\Tests; +use Psr\Log\LoggerInterface; use Psr\Log\Util\Tests\Stub\TestLogger; class TestLoggerTest extends LoggerInterfaceTest { protected $logger; - public function setUp() - { - parent::setUp(); - - $this->logger = $this->createSubject(); - } - /** * @return TestLogger A new mock of the test subject. */ @@ -29,6 +23,10 @@ protected function createSubject() public function getLogger() { + if (! $this->logger instanceof LoggerInterface) { + $this->logger = $this->createSubject(); + } + return $this->logger; } From 036943221949e303d7be2abdd258ebcffc151607 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 22:02:05 +0200 Subject: [PATCH 18/21] Remove unsupported annotation in favour of explicit method call This also addresses php-fig/log#75. --- tests/LoggerInterfaceTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/LoggerInterfaceTest.php b/tests/LoggerInterfaceTest.php index b98b17b..b0ed52d 100644 --- a/tests/LoggerInterfaceTest.php +++ b/tests/LoggerInterfaceTest.php @@ -65,12 +65,11 @@ public function provideLevelsAndMessages() ); } - /** - * @expectedException \Psr\Log\InvalidArgumentException - */ public function testThrowsOnInvalidLevel() { $logger = $this->getLogger(); + + $this->setExpectedException('Psr\Log\InvalidArgumentException'); $logger->log('invalid level', 'Foo'); } From 9195cac1ab077a7e340fd3c72a9c956470cc9568 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 22:12:28 +0200 Subject: [PATCH 19/21] Increase compatibility with later PHPUnit versions --- tests/AbstractTestCase.php | 9 +++++++++ tests/LoggerInterfaceTest.php | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index a5f5686..928329b 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -5,6 +5,15 @@ if (class_exists('PHPUnit\Framework\TestCase')) { class AbstractTestCase extends \PHPUnit\Framework\TestCase { + public function setExpectedException($exceptionName, $exceptionMessage = '', $exceptionCode = null) + { + if (method_exists($this, 'expectException')) { + $this->expectException($exceptionName, $exceptionMessage, $exceptionCode); + return; + } + + parent::setExpectedException($exceptionName, $exceptionMessage, $exceptionCode); + } } } elseif (class_exists('PHPUnit_Framework_TestCase')) { // phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses diff --git a/tests/LoggerInterfaceTest.php b/tests/LoggerInterfaceTest.php index b0ed52d..c3c5108 100644 --- a/tests/LoggerInterfaceTest.php +++ b/tests/LoggerInterfaceTest.php @@ -68,7 +68,7 @@ public function provideLevelsAndMessages() public function testThrowsOnInvalidLevel() { $logger = $this->getLogger(); - + $this->setExpectedException('Psr\Log\InvalidArgumentException'); $logger->log('invalid level', 'Foo'); } From 9dff2ae3819ceaaefd4c6992bd48a368dfd80d2a Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 22:16:05 +0200 Subject: [PATCH 20/21] Use a named supported timezone PHPUnit test on at least PHP 5.3 and 5.4 failing because `+00:00` is unrecognized there. --- tests/LoggerInterfaceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LoggerInterfaceTest.php b/tests/LoggerInterfaceTest.php index c3c5108..ed7fa7a 100644 --- a/tests/LoggerInterfaceTest.php +++ b/tests/LoggerInterfaceTest.php @@ -107,7 +107,7 @@ public function testContextCanContainAnything() 'int' => 0, 'float' => 0.5, 'nested' => array('with object' => $this->createStringable()), - 'object' => new \DateTime('now', new DateTimeZone('+00:00')), + 'object' => new \DateTime('now', new DateTimeZone('Europe/London')), 'resource' => fopen('php://memory', 'r'), 'closed' => $closed, ); From abca75afc1c77927597268f0714fb4c4ba36bfa6 Mon Sep 17 00:00:00 2001 From: Anton Ukhanev Date: Mon, 21 Jun 2021 22:19:23 +0200 Subject: [PATCH 21/21] Add missing fullstop --- tests/LoggerInterfaceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LoggerInterfaceTest.php b/tests/LoggerInterfaceTest.php index ed7fa7a..066d79b 100644 --- a/tests/LoggerInterfaceTest.php +++ b/tests/LoggerInterfaceTest.php @@ -132,7 +132,7 @@ public function testContextExceptionKeyCanBeExceptionOrOtherValues() } /** - * Creates a mock of a `Stringable` + * Creates a mock of a `Stringable`. * * @param string $string The string that must be represented by the stringable. * @return \PHPUnit_Framework_MockObject_MockObject A mock of an object that has a `__toString()` method.