pax_global_header00006660000000000000000000000064151046103410014505gustar00rootroot0000000000000052 comment=83acb6c12875ea4f349dc4fe8b7d8e239c2b3715 php-fig-log-test-83acb6c/000077500000000000000000000000001510461034100152665ustar00rootroot00000000000000php-fig-log-test-83acb6c/CHANGELOG.md000066400000000000000000000016311510461034100171000ustar00rootroot00000000000000# Change log 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] - 2022-10-18 ### Added - Import classes from [`psr/log`][] `v1.1.4`, for compatibility with `v2.0.0` and `v3.0.0`. ## [1.0.0] - 2022-09-07 ### Changed - Compatible with PHP 7.4 and 8.x. Dropped support for lower versions as Test class is marked @requires PHP 7.4 - Initial release. This ports the test for and from [`psr/log` v1.1][], according to [decision][1]. [`psr/log`]: https://packagist.org/packages/psr/log [1]: https://github.com/php-fig/log/pull/76#issuecomment-858743302 ## [0.0.0] - 2022-08-15 ### Changed - Class namespaces are now under the Fig\\ namespace. - Unreleased. Transitioned from the psr/log-util package to the fig/log-test package due to policy discussion. php-fig-log-test-83acb6c/LICENSE000066400000000000000000000020501510461034100162700ustar00rootroot00000000000000MIT License Copyright (c) 2021 PHP-FIG Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-fig-log-test-83acb6c/README.md000066400000000000000000000026611510461034100165520ustar00rootroot00000000000000# FIG - Log Test Testing utilities for the [`psr/log`][] package that backs the [PSR-3][] specification. * `Psr\Log\Test\LoggerInterfaceTest` provides a base test class for ensuring compliance with the LoggerInterface. * `Psr\Log\Test\TestLogger` is a mock class for testing purposes. ## Installation This package should be used only for tests. For PHP 8.0+ only, you may remove support for `psr/log: 1.x`: ```json { "require": { "php": ">=8.0.0", "psr/log": "^2.0 | ^3.0" }, "require-dev": { "fig/log-test": "^1.1" } } ``` If the project supports older versions of PHP: ```json { "require": { "psr/log": "^1.1.14 | ^2.0" }, "require-dev": { "fig/log-test": "^1.0" } } ``` > **Note:** In `psr/log: 3.0.0`, `Psr\Log\LoggerInterface` has union types for method arguments. > Implementing this interface with PHP 7 compatibility is not possible. ## Versions 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`. | [`psr/log`]: https://packagist.org/packages/psr/log [PSR-3]: https://www.php-fig.org/psr/psr-3/ php-fig-log-test-83acb6c/composer.json000066400000000000000000000012011510461034100200020ustar00rootroot00000000000000{ "name": "fig/log-test", "description": "Test utilities for the psr/log package that backs the PSR-3 specification.", "keywords": ["testing"], "license": "MIT", "authors": [ { "name": "PHP-FIG", "homepage": "https://www.php-fig.org/", "role": "Organisation" } ], "require": { "php": "^8.0", "psr/log": "^2.0 | ^3.0" }, "require-dev": { "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0 || ^11.0", "squizlabs/php_codesniffer": "^3.6" }, "autoload": { "psr-4": { "Psr\\Log\\": "src" } }, "autoload-dev": { "psr-4": { "Fig\\Log\\Tests\\": "tests" } } } php-fig-log-test-83acb6c/src/000077500000000000000000000000001510461034100160555ustar00rootroot00000000000000php-fig-log-test-83acb6c/src/Test/000077500000000000000000000000001510461034100167745ustar00rootroot00000000000000php-fig-log-test-83acb6c/src/Test/DummyTest.php000066400000000000000000000003731510461034100214430ustar00rootroot00000000000000 ". * * Example ->error('Foo') would yield "error Foo". * * @return string[] */ abstract public function getLogs(); /** * @return void */ public function testImplements() { $this->assertInstanceOf(LoggerInterface::class, $this->getLogger()); } /** * @param string $level * @param string|\Stringable $message * @return void * * @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()); } /** * @return array */ public static 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}'], ]; } /** * @return void */ public function testThrowsOnInvalidLevel() { $this->expectException(InvalidArgumentException::class); $logger = $this->getLogger(); $logger->log('invalid level', 'Foo'); } /** * @return void */ 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()); } /** * @return void */ 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()); } /** * @return void */ 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()); } /** * @return void */ 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()); } } php-fig-log-test-83acb6c/src/Test/TestLogger.php000066400000000000000000000270531510461034100215730ustar00rootroot00000000000000} */ class TestLogger implements LoggerInterface { use LoggerTrait; /** @var list */ public array $records = []; /** @var array> */ public array $recordsByLevel = []; private bool $placeholderInterpolation; public function __construct(bool $placeholderInterpolation = false) { $this->placeholderInterpolation = $placeholderInterpolation; } /** * @inheritdoc */ public function log($level, string|\Stringable $message, array $context = []): void { if ($this->placeholderInterpolation === true) { $message = $this->interpolate($message, $context); } $level = self::normalizeLevel($level); $record = [ 'level' => $level, 'message' => $message, 'context' => $context, ]; $this->recordsByLevel[$record['level']][] = $record; $this->records[] = $record; } /** * @param string|int|\Stringable|\UnitEnum $level * @return bool */ public function hasRecords($level) { return isset($this->recordsByLevel[self::normalizeLevel($level)]); } /** * @param has_record_array|string $record * @param string|int|\Stringable|\UnitEnum $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; }, self::normalizeLevel($level)); } /** * @param string $message * @param string|int|\Stringable|\UnitEnum $level * @return bool */ public function hasRecordThatContains($message, $level) { return $this->hasRecordThatPasses(fn ($rec) => str_contains($rec['message'], $message), self::normalizeLevel($level)); } /** * @param string $regex * @param string|int|\Stringable|\UnitEnum $level * @return bool */ public function hasRecordThatMatches($regex, $level) { return $this->hasRecordThatPasses(fn ($rec) => preg_match($regex, $rec['message']) > 0, self::normalizeLevel($level)); } /** * @param callable $predicate * @param string|int|\Stringable|\UnitEnum $level * @return bool */ public function hasRecordThatPasses(callable $predicate, $level) { $level = self::normalizeLevel($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 . '()'); } /** * @param has_record_array|string $record */ public function hasEmergency($record): bool { return $this->hasRecord($record, 'emergency'); } /** * @param has_record_array|string $record */ public function hasAlert($record): bool { return $this->hasRecord($record, 'alert'); } /** * @param has_record_array|string $record */ public function hasCritical($record): bool { return $this->hasRecord($record, 'critical'); } /** * @param has_record_array|string $record */ public function hasError($record): bool { return $this->hasRecord($record, 'error'); } /** * @param has_record_array|string $record */ public function hasWarning($record): bool { return $this->hasRecord($record, 'warning'); } /** * @param has_record_array|string $record */ public function hasNotice($record): bool { return $this->hasRecord($record, 'notice'); } /** * @param has_record_array|string $record */ public function hasInfo($record): bool { return $this->hasRecord($record, 'info'); } /** * @param has_record_array|string $record */ 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'); } /** * @param string $message * @return bool */ public function hasEmergencyThatContains($message): bool { return $this->hasRecordThatContains($message, 'emergency'); } /** * @param string $message * @return bool */ public function hasAlertThatContains($message): bool { return $this->hasRecordThatContains($message, 'alert'); } /** * @param string $message * @return bool */ public function hasCriticalThatContains($message): bool { return $this->hasRecordThatContains($message, 'critical'); } /** * @param string $message * @return bool */ public function hasErrorThatContains($message): bool { return $this->hasRecordThatContains($message, 'error'); } /** * @param string $message * @return bool */ public function hasWarningThatContains($message): bool { return $this->hasRecordThatContains($message, 'warning'); } /** * @param string $message * @return bool */ public function hasNoticeThatContains($message): bool { return $this->hasRecordThatContains($message, 'notice'); } /** * @param string $message * @return bool */ public function hasInfoThatContains($message): bool { return $this->hasRecordThatContains($message, 'info'); } /** * @param string $message * @return bool */ 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'); } /** * @return void */ public function reset() { $this->records = []; $this->recordsByLevel = []; } /** * Interpolates context values into the message placeholders. * * @param string|\Stringable $message * @param array $context * @return string */ private function interpolate($message, array $context = []): string { // build a replacement array with braces around the context keys $replace = array(); foreach ($context as $key => $val) { // check that the value can be cast to string if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { $replace['{' . $key . '}'] = $val; } } // interpolate replacement values into the message and return return strtr($message, $replace); } /** * @param mixed $level */ private static function normalizeLevel($level): int|string { if ($level instanceof \UnitEnum) { $level = $level->value; } if ($level instanceof \Stringable) { $level = (string) $level; } if (is_string($level) || is_int($level)) { return $level; } throw new \InvalidArgumentException('The given level of type "'.gettype($level).'" could not be normalized to a string or int.'); } } php-fig-log-test-83acb6c/tests/000077500000000000000000000000001510461034100164305ustar00rootroot00000000000000php-fig-log-test-83acb6c/tests/Test/000077500000000000000000000000001510461034100173475ustar00rootroot00000000000000php-fig-log-test-83acb6c/tests/Test/TestLoggerTest.php000066400000000000000000000114511510461034100230010ustar00rootroot00000000000000 */ class TestLoggerTest extends LoggerInterfaceTest { private TestLogger $logger; public function getLogger(): TestLogger { if (!isset($this->logger)) { $this->logger = new TestLogger(); } return $this->logger; } public function getLogs(): array { $logs = array(); foreach ($this->getLogger()->records as $record) { // Interpolate context $message = preg_replace_callback( '!\{([^\}\s]*)\}!', fn (array $matches): string => $record['context'][$matches[1] ?? null] ?? $matches[0], $record['message'] ); $logs[] = $record['level'] . ' ' . $message; } return $logs; } public function testThrowsOnInvalidLevel(): void { self::markTestSkipped('TestLogger from psr/log 1.1 does not throw on invalid level.'); } /** * @dataProvider getLogLevels */ public function testHasRecord(string $level): void { $levelMethod = 'has' . ucfirst($level); $logger = $this->getLogger(); $this->assertFalse(call_user_func([$logger, $levelMethod . 'Records'])); $logger->log($level, $level . ' Message', ['foo' => 'bar']); $logger->log($level, $level . ' Hello'); $this->assertTrue(call_user_func([$logger, $levelMethod . 'Records'])); $record = $level . ' Message'; $this->assertTrue($logger->hasRecord($record, $level), 'hasRecord without context'); $this->assertTrue(call_user_func([$logger, $levelMethod], $record), $levelMethod . ' without context'); $record = ['message' => $level . ' Message']; $this->assertTrue($logger->hasRecord($record, $level), 'hasRecord without context'); $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(call_user_func([$logger, $levelMethod], $record), $levelMethod . ' with context'); $this->assertTrue( call_user_func([$logger, $levelMethod . 'ThatContains'], 'Message'), $levelMethod . 'ThatContains' ); $this->assertTrue( call_user_func([$logger, $levelMethod . 'ThatMatches'], '/^[a-z]+ Message$/i'), $levelMethod . 'ThatMatches' ); $passesCallback = fn (array $record) => $record['message'] === $level . ' Message'; $this->assertTrue( call_user_func([$logger, $levelMethod . 'ThatPasses'], $passesCallback), $levelMethod . 'ThatPasses' ); } public static function getLogLevels(): array { return [ ['debug'], ['info'], ['notice'], ['warning'], ['error'], ['critical'], ['alert'], ['emergency'], ]; } public function testFalseHasRecord(): void { $logger = $this->getLogger(); $logger->debug('debug message'); $logger->debug('log message'); $logger->debug('log message with context', ['debug' => true]); $logger->debug('log message with context', ['debug' => false]); $logger->warning('log message'); $logger->warning('warning message'); $this->assertFalse($logger->hasAlert('my message')); $this->assertFalse($logger->hasAlert(['message' => 'my message'])); $this->assertFalse($logger->hasAlertRecords()); $this->assertFalse($logger->hasAlertThatContains('my message')); $this->assertFalse($logger->hasAlertThatMatches('/my message/')); $this->assertFalse($logger->hasAlertThatPasses(fn (array $record) => true)); $this->assertFalse($logger->hasDebug('warning message')); $this->assertFalse($logger->hasDebug(['message' => 'warning message'])); $this->assertFalse($logger->hasDebugThatContains('warning message')); $this->assertFalse($logger->hasDebugThatMatches('/warning message/')); $this->assertFalse($logger->hasDebugThatPasses(fn (array $record) => 'warning message' === $record['message'])); } public function testPlaceholderInterpolation() : void { $logger = new TestLogger(true); $message = 'User {username} created'; $logger->debug($message, ['username' => 'bolivar']); $this->assertTrue($logger->hasDebugThatContains('User bolivar created')); } } php-fig-log-test-83acb6c/tests/bootstrap.php000066400000000000000000000003311510461034100211530ustar00rootroot00000000000000