Import test classes from psr/log 1.1.4

Fix compatibility with PHPUnit and psr/log 3.0
Modernize code for PHP 8.0
Materialize magical methods from TestLogger::__call
This commit is contained in:
Jérôme Tamarelle
2022-08-22 17:37:11 +02:00
parent 8e034d87e8
commit 665641d90f
8 changed files with 498 additions and 11 deletions

View File

@@ -5,9 +5,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
# There is no code, we don't have to test old PHP versions
php-versions:
- 7.4
- 8.0
- 8.1
dependency-levels:
@@ -15,7 +13,7 @@ jobs:
experimental:
- false
include:
- php-versions: 7.4
- php-versions: 8.0
dependency-levels: 'lowest'
experimental: false
fail-fast: false
@@ -30,7 +28,7 @@ jobs:
php-version: ${{ matrix.php-versions }}
- name: Validating PHP syntax
run: find ./tests/ -type f -name '*.php' -print0 | xargs -0 -L 1 -P 4 -- php -l
run: find ./{src,tests}/ -type f -name '*.php' -print0 | xargs -0 -L 1 -P 4 -- php -l
- name: Validate composer.json and composer.lock
run: composer validate

View File

@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.1.0] - YYYY-MM-DD
- Import classes from [`psr/log`][] `v1.1.4`, for compatibility with `v2.0.0` and `v3.0.0`.
## [1.0.0] - YYYY-MM-DD
- Initial release. This ports the test for and from [`psr/log` v1.1][], according to

View File

@@ -43,9 +43,9 @@ If the project supports older versions of PHP:
The version of `fig/log-test` that is installed after composer dependencies resolution varies with the version of `psr/log`.
| psr/log | fig/log-test | |
|---------------|---------------|-------------------------------------------------|
| `^1.1.14` | `1.0.*` | Empty package, classes a provided by `psr/log`. |
| `^2.0\|^3.0` | `^1.1` | Imports test classes removed from `psr/log`. |
|---------------|--------------|-------------------------------------------------|
| `^1.1.14` | `1.0.*` | Empty package, classes a provided by `psr/log`. |
| `^2.0\|^3.0` | `^1.1` | Imports test classes removed from `psr/log`. |
[`psr/log`]: https://packagist.org/packages/psr/log
[PSR-3]: https://www.php-fig.org/psr/psr-3/

View File

@@ -10,13 +10,18 @@
}
],
"require": {
"php": "^5.3 | ^7.0 | ^8.0",
"psr/log": "^1.1.1"
"php": "^8.0",
"psr/log": "^2.0 | ^3.0"
},
"require-dev": {
"phpunit/phpunit": "^8.0 | ^9.0",
"squizlabs/php_codesniffer": "^3.6"
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Fig\\Log\\Tests\\": "tests"

18
src/Test/DummyTest.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace Psr\Log\Test;
/**
* This class is internal and does not follow the BC promise.
*
* Do NOT use this class in any way.
*
* @internal
*/
class DummyTest
{
public function __toString()
{
return 'DummyTest';
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace Psr\Log\Test;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use PHPUnit\Framework\TestCase;
/**
* Provides a base test class for ensuring compliance with the LoggerInterface.
*
* Implementors can extend the class and implement abstract methods to run this
* as part of their test suite.
*/
abstract class LoggerInterfaceTest extends TestCase
{
/**
* Return an instance of the logger to be tested.
*
* @return LoggerInterface
*/
abstract public function getLogger();
/**
* This must return the log messages in order.
*
* The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
*
* Example ->error('Foo') would yield "error Foo".
*
* @return string[]
*/
abstract public function getLogs();
public function testImplements()
{
$this->assertInstanceOf(LoggerInterface::class, $this->getLogger());
}
/**
* @dataProvider provideLevelsAndMessages
*/
public function testLogsAtAllLevels($level, $message)
{
$logger = $this->getLogger();
$logger->{$level}($message, ['user' => 'Bob']);
$logger->log($level, $message, ['user' => 'Bob']);
$expected = [
$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 [
LogLevel::EMERGENCY => [LogLevel::EMERGENCY, 'message of level emergency with context: {user}'],
LogLevel::ALERT => [LogLevel::ALERT, 'message of level alert with context: {user}'],
LogLevel::CRITICAL => [LogLevel::CRITICAL, 'message of level critical with context: {user}'],
LogLevel::ERROR => [LogLevel::ERROR, 'message of level error with context: {user}'],
LogLevel::WARNING => [LogLevel::WARNING, 'message of level warning with context: {user}'],
LogLevel::NOTICE => [LogLevel::NOTICE, 'message of level notice with context: {user}'],
LogLevel::INFO => [LogLevel::INFO, 'message of level info with context: {user}'],
LogLevel::DEBUG => [LogLevel::DEBUG, 'message of level debug with context: {user}'],
];
}
public function testThrowsOnInvalidLevel()
{
$this->expectException(InvalidArgumentException::class);
$logger = $this->getLogger();
$logger->log('invalid level', 'Foo');
}
public function testContextReplacement()
{
$logger = $this->getLogger();
$logger->info('{Message {nothing} {user} {foo.bar} a}', ['user' => 'Bob', 'foo.bar' => 'Bar']);
$expected = ['info {Message {nothing} Bob Bar a}'];
$this->assertEquals($expected, $this->getLogs());
}
public function testObjectCastToString()
{
$dummy = $this->createPartialMock(DummyTest::class, ['__toString']);
$dummy->expects($this->once())
->method('__toString')
->will($this->returnValue('DUMMY'));
$this->getLogger()->warning($dummy);
$expected = ['warning DUMMY'];
$this->assertEquals($expected, $this->getLogs());
}
public function testContextCanContainAnything()
{
$closed = fopen('php://memory', 'r');
fclose($closed);
$context = [
'bool' => true,
'null' => null,
'string' => 'Foo',
'int' => 0,
'float' => 0.5,
'nested' => ['with object' => new DummyTest()],
'object' => new \DateTime(),
'resource' => fopen('php://memory', 'r'),
'closed' => $closed,
];
$this->getLogger()->warning('Crazy context data', $context);
$expected = ['warning Crazy context data'];
$this->assertEquals($expected, $this->getLogs());
}
public function testContextExceptionKeyCanBeExceptionOrOtherValues()
{
$logger = $this->getLogger();
$logger->warning('Random message', ['exception' => 'oops']);
$logger->critical('Uncaught Exception!', ['exception' => new \LogicException('Fail')]);
$expected = [
'warning Random message',
'critical Uncaught Exception!'
];
$this->assertEquals($expected, $this->getLogs());
}
}

328
src/Test/TestLogger.php Normal file
View File

@@ -0,0 +1,328 @@
<?php
namespace Psr\Log\Test;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
/**
* Used for testing purposes.
*
* It records all records and gives you access to them for verification.
*/
class TestLogger implements LoggerInterface
{
use LoggerTrait;
public array $records = [];
public array $recordsByLevel = [];
/**
* @inheritdoc
*/
public function log($level, string|\Stringable $message, array $context = []): void
{
$record = [
'level' => $level,
'message' => $message,
'context' => $context,
];
$this->recordsByLevel[$record['level']][] = $record;
$this->records[] = $record;
}
/**
* @param string $level
* @return bool
*/
public function hasRecords($level)
{
return isset($this->recordsByLevel[$level]);
}
/**
* @param array $record
* @param string $level
* @return bool
*/
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);
}
/**
* @param string $message
* @param string $level
* @return bool
*/
public function hasRecordThatContains($message, $level)
{
return $this->hasRecordThatPasses(fn ($rec) => str_contains($rec['message'], $message), $level);
}
/**
* @param string $regex
* @param string $level
* @return bool
*/
public function hasRecordThatMatches($regex, $level)
{
return $this->hasRecordThatPasses(fn ($rec) => preg_match($regex, $rec['message']) > 0, $level);
}
/**
* @param callable $predicate
* @param string $level
* @return bool
*/
public function hasRecordThatPasses(callable $predicate, $level)
{
if (!isset($this->recordsByLevel[$level])) {
return false;
}
foreach ($this->recordsByLevel[$level] as $i => $rec) {
if ($predicate($rec, $i)) {
return true;
}
}
return false;
}
/**
* @deprecated Since psr/log-util 1.1
*/
public function __call($method, $args)
{
@trigger_error(sprintf('Since psr/log-util 1.1: Method "%s" is deprecated and should not be called. Use method "%s" instead.', __FUNCTION__, $method), \E_USER_DEPRECATED);
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 hasEmergency($record): bool
{
return $this->hasRecord($record, 'emergency');
}
public function hasAlert($record): bool
{
return $this->hasRecord($record, 'alert');
}
public function hasCritical($record): bool
{
return $this->hasRecord($record, 'critical');
}
public function hasError($record): bool
{
return $this->hasRecord($record, 'error');
}
public function hasWarning($record): bool
{
return $this->hasRecord($record, 'warning');
}
public function hasNotice($record): bool
{
return $this->hasRecord($record, 'notice');
}
public function hasInfo($record): bool
{
return $this->hasRecord($record, 'info');
}
public function hasDebug($record): bool
{
return $this->hasRecord($record, 'debug');
}
public function hasEmergencyRecords(): bool
{
return $this->hasRecords('emergency');
}
public function hasAlertRecords(): bool
{
return $this->hasRecords('alert');
}
public function hasCriticalRecords(): bool
{
return $this->hasRecords('critical');
}
public function hasErrorRecords(): bool
{
return $this->hasRecords('error');
}
public function hasWarningRecords(): bool
{
return $this->hasRecords('warning');
}
public function hasNoticeRecords(): bool
{
return $this->hasRecords('notice');
}
public function hasInfoRecords(): bool
{
return $this->hasRecords('info');
}
public function hasDebugRecords(): bool
{
return $this->hasRecords('debug');
}
public function hasEmergencyThatContains($message): bool
{
return $this->hasRecordThatContains($message, 'emergency');
}
public function hasAlertThatContains($message): bool
{
return $this->hasRecordThatContains($message, 'alert');
}
public function hasCriticalThatContains($message): bool
{
return $this->hasRecordThatContains($message, 'critical');
}
public function hasErrorThatContains($message): bool
{
return $this->hasRecordThatContains($message, 'error');
}
public function hasWarningThatContains($message): bool
{
return $this->hasRecordThatContains($message, 'warning');
}
public function hasNoticeThatContains($message): bool
{
return $this->hasRecordThatContains($message, 'notice');
}
public function hasInfoThatContains($message): bool
{
return $this->hasRecordThatContains($message, 'info');
}
public function hasDebugThatContains($message): bool
{
return $this->hasRecordThatContains($message, 'debug');
}
public function hasEmergencyThatMatches(string $regex): bool
{
return $this->hasRecordThatMatches($regex, 'emergency');
}
public function hasAlertThatMatches(string $regex): bool
{
return $this->hasRecordThatMatches($regex, 'alert');
}
public function hasCriticalThatMatches(string $regex): bool
{
return $this->hasRecordThatMatches($regex, 'critical');
}
public function hasErrorThatMatches(string $regex): bool
{
return $this->hasRecordThatMatches($regex, 'error');
}
public function hasWarningThatMatches(string $regex): bool
{
return $this->hasRecordThatMatches($regex, 'warning');
}
public function hasNoticeThatMatches(string $regex): bool
{
return $this->hasRecordThatMatches($regex, 'notice');
}
public function hasInfoThatMatches(string $regex): bool
{
return $this->hasRecordThatMatches($regex, 'info');
}
public function hasDebugThatMatches(string $regex): bool
{
return $this->hasRecordThatMatches($regex, 'debug');
}
public function hasEmergencyThatPasses(callable $predicate): bool
{
return $this->hasRecordThatPasses($predicate, 'emergency');
}
public function hasAlertThatPasses(callable $predicate): bool
{
return $this->hasRecordThatPasses($predicate, 'alert');
}
public function hasCriticalThatPasses(callable $predicate): bool
{
return $this->hasRecordThatPasses($predicate, 'critical');
}
public function hasErrorThatPasses(callable $predicate): bool
{
return $this->hasRecordThatPasses($predicate, 'error');
}
public function hasWarningThatPasses(callable $predicate): bool
{
return $this->hasRecordThatPasses($predicate, 'warning');
}
public function hasNoticeThatPasses(callable $predicate): bool
{
return $this->hasRecordThatPasses($predicate, 'notice');
}
public function hasInfoThatPasses(callable $predicate): bool
{
return $this->hasRecordThatPasses($predicate, 'info');
}
public function hasDebugThatPasses(callable $predicate): bool
{
return $this->hasRecordThatPasses($predicate, 'debug');
}
public function reset()
{
$this->records = [];
$this->recordsByLevel = [];
}
}

View File

@@ -8,7 +8,6 @@ use Psr\Log\Test\TestLogger;
/**
* Test classes from psr/log 1.1.x
*
* @requires PHP 7.4
* @covers \Psr\Log\Test\TestLogger
* @covers \Psr\Log\Test\LoggerInterfaceTest
*
@@ -72,7 +71,7 @@ class TestLoggerTest extends LoggerInterfaceTest
$this->assertTrue(call_user_func([$logger, $levelMethod], $record), $levelMethod.' without context');
$record = ['message' => $level.' Message', ['foo' => 'bar']];
$this->assertTrue($logger->hasRecord($record, $level),'hasRecord with context');
$this->assertTrue($logger->hasRecord($record, $level), 'hasRecord with context');
$this->assertTrue(call_user_func([$logger, $levelMethod], $record), $levelMethod.' with context');
$this->assertTrue(call_user_func([$logger, $levelMethod.'ThatContains'], 'Message'), $levelMethod.'ThatContains');