Dustin Wilson
1 year ago
8 changed files with 299 additions and 28 deletions
@ -0,0 +1,11 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @license MIT |
||||
|
* Copyright 2022 Dustin Wilson, et al. |
||||
|
* See LICENSE and AUTHORS files for details |
||||
|
*/ |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
namespace MensBeam\Catcher; |
||||
|
|
||||
|
class RangeException extends \RangeException {} |
@ -0,0 +1,194 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @license MIT |
||||
|
* Copyright 2022 Dustin Wilson, et al. |
||||
|
* See LICENSE and AUTHORS files for details |
||||
|
*/ |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
namespace MensBeam\Catcher\Test; |
||||
|
use MensBeam\Catcher\{ |
||||
|
Error, |
||||
|
Handler, |
||||
|
RangeException, |
||||
|
ThrowableController |
||||
|
}; |
||||
|
use Psr\Log\LoggerInterface, |
||||
|
Phake; |
||||
|
|
||||
|
|
||||
|
/** @covers \MensBeam\Catcher\Handler */ |
||||
|
class TestHandler extends ErrorHandlingTestCase { |
||||
|
protected ?Handler $handler = null; |
||||
|
|
||||
|
|
||||
|
public function setUp(): void { |
||||
|
parent::setUp(); |
||||
|
|
||||
|
$this->handler = new TestingHandler([ |
||||
|
'outputBacktrace' => true |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** @dataProvider provideHandlingTests */ |
||||
|
public function testHandling(\Throwable $throwable, int $expectedCode, array $options = []): void { |
||||
|
foreach ($options as $k => $v) { |
||||
|
$this->handler->setOption($k, $v); |
||||
|
} |
||||
|
|
||||
|
$o = $this->handler->handle(new ThrowableController($throwable)); |
||||
|
$this->assertSame($throwable::class, $o['controller']->getThrowable()::class); |
||||
|
$this->assertEquals($expectedCode, $o['code']); |
||||
|
} |
||||
|
|
||||
|
public function testInvocation(): void { |
||||
|
$this->handler->handle(new ThrowableController(new \Exception('Ook!'))); |
||||
|
$r = new \ReflectionProperty($this->handler::class, 'outputBuffer'); |
||||
|
$r->setAccessible(true); |
||||
|
$this->assertEquals(1, count($r->getValue($this->handler))); |
||||
|
|
||||
|
$h = $this->handler; |
||||
|
$h(); |
||||
|
$this->assertEquals(0, count($r->getValue($this->handler))); |
||||
|
$h(); |
||||
|
$this->assertEquals(0, count($r->getValue($this->handler))); |
||||
|
} |
||||
|
|
||||
|
/** @dataProvider provideLogTests */ |
||||
|
public function testLog(\Throwable $throwable, string $methodName): void { |
||||
|
$l = Phake::mock(LoggerInterface::class); |
||||
|
$this->handler->setOption('logger', $l); |
||||
|
$this->handler->handle(new ThrowableController($throwable)); |
||||
|
$h = $this->handler; |
||||
|
$h(); |
||||
|
Phake::verify($l, Phake::times(1))->$methodName; |
||||
|
} |
||||
|
|
||||
|
/** @dataProvider provideOptionsTests */ |
||||
|
public function testOptions(string $option, mixed $value): void { |
||||
|
$this->handler->setOption($option, $value); |
||||
|
$this->assertSame($value, $this->handler->getOption($option)); |
||||
|
} |
||||
|
|
||||
|
public function testPrinting(): void { |
||||
|
$this->handler->setOption('print', true); |
||||
|
$this->handler->setOption('outputToStderr', false); |
||||
|
$this->handler->handle(new ThrowableController(new \Exception('Ook!'))); |
||||
|
$h = $this->handler; |
||||
|
ob_start(); |
||||
|
$h(); |
||||
|
$o = ob_get_clean(); |
||||
|
$this->assertNotNull($o); |
||||
|
$o = json_decode($o, true); |
||||
|
$this->assertSame(\Exception::class, $o['class']); |
||||
|
$this->assertSame(__FILE__, $o['file']); |
||||
|
$this->assertSame(__LINE__ - 9, $o['line']); |
||||
|
$this->assertSame('Ook!', $o['message']); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public function testFatalError(): void { |
||||
|
$this->expectException(RangeException::class); |
||||
|
$this->handler->setOption('httpCode', 42); |
||||
|
} |
||||
|
|
||||
|
/** @dataProvider provideNonFatalErrorTests */ |
||||
|
public function testNonFatalErrors(int $code, string $message, \Closure $closure): void { |
||||
|
$closure($this->handler); |
||||
|
$this->assertEquals($code, $this->lastError?->getCode()); |
||||
|
$this->assertSame($message, $this->lastError?->getMessage()); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static function provideHandlingTests(): iterable { |
||||
|
$options = [ |
||||
|
[ new \Exception('Ook!'), Handler::BUBBLES | Handler::OUTPUT | Handler::EXIT, [ 'forceExit' => true ] ], |
||||
|
[ new \Error('Ook!'), Handler::BUBBLES | Handler::OUTPUT ], |
||||
|
[ new \Exception('Ook!'), Handler::BUBBLES, [ 'silent' => true ] ], |
||||
|
[ new Error('Ook!', \E_ERROR, '/dev/null', 42, new \Error('Eek!')), Handler::BUBBLES | Handler::OUTPUT | Handler::NOW, [ 'forceOutputNow' => true ] ], |
||||
|
[ new \Exception('Ook!'), Handler::BUBBLES, [ 'silent' => true, 'logger' => Phake::mock(LoggerInterface::class), 'logWhenSilent' => false ] ], |
||||
|
[ new \Error('Ook!'), Handler::BUBBLES | Handler::OUTPUT | Handler::LOG, [ 'forceOutputNow' => true, 'logger' => Phake::mock(LoggerInterface::class) ] ] |
||||
|
]; |
||||
|
|
||||
|
foreach ($options as $o) { |
||||
|
// TestingHandler adds Handler::NOW to the output code to make |
||||
|
// testing with it in TestCatcher less of a pain, so it needs to be |
||||
|
// added here |
||||
|
$o[1] |= Handler::NOW; |
||||
|
yield $o; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static function provideLogTests(): iterable { |
||||
|
$options = [ |
||||
|
[ new Error('Ook!', \E_NOTICE, '/dev/null', 0, new \Error('Eek!')), 'notice' ], |
||||
|
[ new Error('Ook!', \E_USER_NOTICE, '/dev/null', 0), 'notice' ], |
||||
|
[ new Error('Ook!', \E_STRICT, '/dev/null', 0, new \Error('Eek!')), 'notice' ], |
||||
|
[ new Error('Ook!', \E_WARNING, '/dev/null', 0), 'warning' ], |
||||
|
[ new Error('Ook!', \E_COMPILE_WARNING, '/dev/null', 0, new \Error('Eek!')), 'warning' ], |
||||
|
[ new Error('Ook!', \E_USER_WARNING, '/dev/null', 0), 'warning' ], |
||||
|
[ new Error('Ook!', \E_DEPRECATED, '/dev/null', 0, new \Error('Eek!')), 'warning' ], |
||||
|
[ new Error('Ook!', \E_USER_DEPRECATED, '/dev/null', 0), 'warning' ], |
||||
|
[ new Error('Ook!', \E_PARSE, '/dev/null', 0, new \Error('Eek!')), 'critical' ], |
||||
|
[ new Error('Ook!', \E_CORE_ERROR, '/dev/null', 0), 'critical' ], |
||||
|
[ new Error('Ook!', \E_COMPILE_ERROR, '/dev/null', 0, new \Error('Eek!')), 'critical' ], |
||||
|
[ new Error('Ook!', \E_ERROR, '/dev/null', 0), 'error' ], |
||||
|
[ new Error('Ook!', \E_USER_ERROR, '/dev/null', 0, new \Error('Eek!')), 'error' ], |
||||
|
[ new \PharException('Ook!'), 'alert' ], |
||||
|
[ new \Exception('Ook!'), 'critical' ], |
||||
|
]; |
||||
|
|
||||
|
foreach ($options as $o) { |
||||
|
yield $o; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static function provideNonFatalErrorTests(): iterable { |
||||
|
$iterable = [ |
||||
|
[ |
||||
|
\E_USER_WARNING, |
||||
|
'Undefined option in ' . TestingHandler::class . ': ook', |
||||
|
function (Handler $h): void { |
||||
|
$h->getOption('ook'); |
||||
|
} |
||||
|
], |
||||
|
[ |
||||
|
\E_USER_WARNING, |
||||
|
'Undefined option in ' . TestingHandler::class . ': ook', |
||||
|
function (Handler $h): void { |
||||
|
$h->setOption('ook', 'eek'); |
||||
|
} |
||||
|
] |
||||
|
]; |
||||
|
|
||||
|
foreach ($iterable as $i) { |
||||
|
yield $i; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static function provideOptionsTests(): iterable { |
||||
|
$options = [ |
||||
|
[ 'backtraceArgFrameLimit', 42 ], |
||||
|
[ 'bubbles', false ], |
||||
|
[ 'charset', 'UTF-16' ], |
||||
|
[ 'forceExit', true ], |
||||
|
[ 'forceOutputNow', true ], |
||||
|
[ 'httpCode', 200 ], |
||||
|
[ 'httpCode', 400 ], |
||||
|
[ 'httpCode', 502 ], |
||||
|
[ 'logger', Phake::mock(LoggerInterface::class) ], |
||||
|
[ 'logWhenSilent', false ], |
||||
|
[ 'outputBacktrace', true ], |
||||
|
[ 'outputPrevious', false ], |
||||
|
[ 'outputTime', false ], |
||||
|
[ 'outputToStderr', false ], |
||||
|
[ 'silent', true ], |
||||
|
[ 'timeFormat', 'Y-m-d' ], |
||||
|
[ 'varExporter', fn(mixed $value): string|bool => var_export($value, true) ] |
||||
|
]; |
||||
|
|
||||
|
foreach ($options as $o) { |
||||
|
yield $o; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @license MIT |
||||
|
* Copyright 2022 Dustin Wilson, et al. |
||||
|
* See LICENSE and AUTHORS files for details |
||||
|
*/ |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
namespace MensBeam\Catcher\Test; |
||||
|
|
||||
|
class Error extends \Error {} |
@ -0,0 +1,31 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @license MIT |
||||
|
* Copyright 2022 Dustin Wilson, et al. |
||||
|
* See LICENSE and AUTHORS files for details |
||||
|
*/ |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
namespace MensBeam\Catcher\Test; |
||||
|
|
||||
|
|
||||
|
class ErrorHandlingTestCase extends \PHPUnit\Framework\TestCase { |
||||
|
protected ?Error $lastError = null; |
||||
|
|
||||
|
|
||||
|
public function setUp(): void { |
||||
|
set_error_handler([ $this, 'handleError' ]); |
||||
|
} |
||||
|
|
||||
|
public function tearDown(): void { |
||||
|
restore_error_handler(); |
||||
|
} |
||||
|
|
||||
|
public function handleError(int $code, string $message, string $file, int $line): void { |
||||
|
$e = new Error($message, $code); |
||||
|
$this->lastError = $e; |
||||
|
if (in_array($code, [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR, \E_RECOVERABLE_ERROR ])) { |
||||
|
throw $e; |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue