2022-10-24 23:44:05 -04:00
|
|
|
<?php
|
2023-04-22 00:17:24 -04:00
|
|
|
/**
|
2022-10-24 23:44:05 -04:00
|
|
|
* @license MIT
|
|
|
|
* Copyright 2022 Dustin Wilson, et al.
|
2023-04-22 00:17:24 -04:00
|
|
|
* See LICENSE and AUTHORS files for details
|
2022-10-24 23:44:05 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2023-02-19 14:43:48 -05:00
|
|
|
namespace MensBeam\Catcher\Test;
|
2023-04-22 00:17:24 -04:00
|
|
|
use MensBeam\Catcher,
|
|
|
|
Phake,
|
|
|
|
Phake\IMock;
|
2023-02-19 14:43:48 -05:00
|
|
|
use MensBeam\Catcher\{
|
2023-04-22 00:17:24 -04:00
|
|
|
ArgumentCountError,
|
2022-11-13 23:36:41 -05:00
|
|
|
Error,
|
2023-04-22 00:17:24 -04:00
|
|
|
PlainTextHandler,
|
|
|
|
UnderflowException
|
2022-10-24 23:44:05 -04:00
|
|
|
};
|
|
|
|
|
2022-12-26 23:10:56 -05:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
/** @covers \MensBeam\Catcher */
|
2022-10-24 23:44:05 -04:00
|
|
|
class TestCatcher extends \PHPUnit\Framework\TestCase {
|
2023-04-22 00:17:24 -04:00
|
|
|
protected ?Catcher $catcher = null;
|
2022-10-24 23:44:05 -04:00
|
|
|
|
2022-10-30 22:51:27 -04:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
public function setUp(): void {
|
|
|
|
if ($this->catcher !== null) {
|
|
|
|
$this->catcher->unregister();
|
2022-10-30 22:51:27 -04:00
|
|
|
}
|
2023-04-22 00:17:24 -04:00
|
|
|
$this->catcher = new Catcher();
|
|
|
|
$this->catcher->preventExit = true;
|
2022-10-24 23:44:05 -04:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
// Do this instead of specifying the option in the constructor for coverage
|
|
|
|
// purposes...
|
|
|
|
$handlers = $this->catcher->getHandlers();
|
|
|
|
$handlers[0]->setOption('silent', true);
|
2022-10-24 23:44:05 -04:00
|
|
|
}
|
2022-10-26 23:22:30 -04:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
public function tearDown(): void {
|
|
|
|
$this->catcher->unregister();
|
|
|
|
$this->catcher = null;
|
|
|
|
error_reporting(\E_ALL);
|
2022-10-30 22:51:27 -04:00
|
|
|
}
|
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
|
|
|
|
public function testConstructor(): void {
|
|
|
|
$h = $this->catcher->getHandlers();
|
|
|
|
$this->assertEquals(1, count($h));
|
|
|
|
$this->assertInstanceOf(PlainTextHandler::class, $h[0]);
|
2022-10-26 23:22:30 -04:00
|
|
|
}
|
|
|
|
|
2023-04-23 14:12:21 -04:00
|
|
|
/**
|
|
|
|
* @dataProvider provideErrorHandlingTests
|
|
|
|
* @covers \MensBeam\Catcher\Error
|
|
|
|
*/
|
2023-04-22 00:17:24 -04:00
|
|
|
public function testErrorHandling(int $code): void {
|
|
|
|
$t = null;
|
2022-10-30 22:51:27 -04:00
|
|
|
try {
|
2023-04-22 00:17:24 -04:00
|
|
|
trigger_error('Ook!', $code);
|
|
|
|
} catch (\Throwable $t) {} finally {
|
|
|
|
$t = ($t === null) ? $this->catcher->getLastThrowable() : $t;
|
|
|
|
$this->assertSame(Error::class, $t::class);
|
|
|
|
$this->assertSame($code, $t->getCode());
|
|
|
|
$this->assertSame($t, $this->catcher->getLastThrowable());
|
2022-10-30 22:51:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-23 14:12:21 -04:00
|
|
|
/** @covers \MensBeam\Catcher\Error */
|
2023-04-22 00:17:24 -04:00
|
|
|
public function testExit(): void {
|
|
|
|
$this->catcher->unregister();
|
|
|
|
$h = Phake::partialMock(TestingHandler::class);
|
|
|
|
$this->catcher = $m = Phake::partialMock(Catcher::class, $h);
|
|
|
|
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS;
|
|
|
|
Phake::when($m)->exit->thenReturn(null);
|
2023-04-23 14:12:21 -04:00
|
|
|
Phake::when($m)->handleShutdown->thenReturn(null);
|
2022-10-30 22:51:27 -04:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
trigger_error('Ook!', \E_USER_ERROR);
|
|
|
|
|
|
|
|
Phake::verify($h, Phake::times(1))->invokeCallback();
|
2022-10-26 23:22:30 -04:00
|
|
|
}
|
2022-10-30 22:51:27 -04:00
|
|
|
|
2023-04-23 14:12:21 -04:00
|
|
|
/** @covers \MensBeam\Catcher\Error */
|
2023-04-22 00:17:24 -04:00
|
|
|
public function testHandlerBubbling(): void {
|
|
|
|
$this->catcher->unregister();
|
2022-11-06 13:36:00 -05:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
$h1 = Phake::partialMock(TestingHandler::class, [ 'bubbles' => false ]);
|
|
|
|
$h2 = Phake::partialMock(TestingHandler::class);
|
|
|
|
$this->catcher = $m = Phake::partialMock(Catcher::class, $h1, $h2);
|
|
|
|
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS;
|
|
|
|
$m->preventExit = true;
|
2022-11-06 13:36:00 -05:00
|
|
|
|
|
|
|
trigger_error('Ook!', \E_USER_ERROR);
|
2023-04-22 00:17:24 -04:00
|
|
|
Phake::verify($m)->handleError(\E_USER_ERROR, 'Ook!', __FILE__, __LINE__ - 1);
|
|
|
|
Phake::verify($m)->handleThrowable($m->getLastThrowable());
|
|
|
|
Phake::verify($h1)->invokeCallback();
|
|
|
|
Phake::verify($h2, Phake::never())->invokeCallback();
|
|
|
|
}
|
2022-11-06 13:36:00 -05:00
|
|
|
|
2023-04-23 14:12:21 -04:00
|
|
|
/** @covers \MensBeam\Catcher\Error */
|
2023-04-22 00:17:24 -04:00
|
|
|
public function testHandlerForceExiting(): void {
|
|
|
|
$this->catcher->setHandlers(new TestingHandler([ 'forceExit' => true ]));
|
|
|
|
$this->catcher->errorHandlingMethod = Catcher::THROW_NO_ERRORS;
|
|
|
|
$this->catcher->preventExit = true;
|
2022-11-13 23:36:41 -05:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
trigger_error('Ook', \E_USER_ERROR);
|
|
|
|
$this->assertSame(Error::class, $this->catcher->getLastThrowable()::class);
|
|
|
|
}
|
2022-11-13 23:36:41 -05:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
public function testRegistration(): void {
|
|
|
|
$this->assertTrue($this->catcher->isRegistered());
|
|
|
|
$this->assertFalse($this->catcher->register());
|
|
|
|
$this->assertTrue($this->catcher->unregister());
|
|
|
|
$this->assertFalse($this->catcher->unregister());
|
|
|
|
$this->assertFalse($this->catcher->isRegistered());
|
|
|
|
}
|
2022-11-13 23:36:41 -05:00
|
|
|
|
2023-04-23 14:12:21 -04:00
|
|
|
/**
|
|
|
|
* @dataProvider provideShutdownTests
|
|
|
|
* @covers \MensBeam\Catcher\Error
|
|
|
|
*/
|
2023-04-22 00:17:24 -04:00
|
|
|
public function testShutdownHandling(\Closure $closure): void {
|
|
|
|
$this->catcher->unregister();
|
2022-11-13 23:36:41 -05:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
$h1 = Phake::partialMock(TestingHandler::class);
|
|
|
|
$this->catcher = $m = Phake::partialMock(Catcher::class, $h1);
|
|
|
|
$closure($m, $h1);
|
|
|
|
}
|
2022-11-13 23:36:41 -05:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
public function testStackManipulation(): void {
|
|
|
|
$c = $this->catcher;
|
|
|
|
$c->pushHandler(new TestingHandler(options: [ 'name' => 'ook' ]), new TestingHandler(options: [ 'name' => 'eek' ]));
|
|
|
|
$h = $c->getHandlers();
|
|
|
|
$this->assertEquals(3, count($h));
|
|
|
|
$this->assertSame('ook', $h[1]->getOption('name'));
|
|
|
|
$this->assertSame('eek', $h[2]->getOption('name'));
|
|
|
|
|
|
|
|
$this->assertInstanceOf(PlainTextHandler::class, $c->shiftHandler());
|
|
|
|
$h = $c->getHandlers();
|
|
|
|
$this->assertEquals(2, count($h));
|
|
|
|
$this->assertSame('ook', $h[0]->getOption('name'));
|
|
|
|
$this->assertSame('eek', $h[1]->getOption('name'));
|
|
|
|
|
|
|
|
$p = $c->popHandler();
|
|
|
|
$this->assertInstanceOf(TestingHandler::class, $p);
|
|
|
|
$h = $c->getHandlers();
|
|
|
|
$this->assertEquals(1, count($h));
|
|
|
|
$this->assertSame('eek', $p->getOption('name'));
|
|
|
|
$this->assertSame('ook', $h[0]->getOption('name'));
|
|
|
|
|
|
|
|
$c->unshiftHandler($p);
|
|
|
|
$h = $c->getHandlers();
|
|
|
|
$this->assertEquals(2, count($h));
|
|
|
|
$this->assertSame('eek', $h[0]->getOption('name'));
|
|
|
|
$this->assertSame('ook', $h[1]->getOption('name'));
|
|
|
|
|
|
|
|
$c->setHandlers(new PlainTextHandler());
|
|
|
|
$this->assertEquals(1, count($c->getHandlers()));
|
|
|
|
}
|
|
|
|
|
2023-04-23 14:12:21 -04:00
|
|
|
/** @covers \MensBeam\Catcher\Error */
|
2023-04-22 00:17:24 -04:00
|
|
|
public function testWeirdErrorReporting(): void {
|
|
|
|
error_reporting(\E_ERROR);
|
|
|
|
$t = null;
|
2023-01-11 00:32:43 -05:00
|
|
|
try {
|
|
|
|
trigger_error('Ook!', \E_USER_WARNING);
|
2023-04-22 00:17:24 -04:00
|
|
|
} catch (\Throwable $t) {} finally {
|
|
|
|
$this->assertNull($t);
|
|
|
|
$this->assertNull($this->catcher->getLastThrowable());
|
2023-01-11 00:32:43 -05:00
|
|
|
}
|
2023-04-22 00:17:24 -04:00
|
|
|
}
|
2023-01-11 00:32:43 -05:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
|
|
|
|
/** @dataProvider provideFatalErrorTests */
|
|
|
|
public function testFatalErrors(string $throwableClassName, \Closure $closure): void {
|
|
|
|
$this->expectException($throwableClassName);
|
|
|
|
$closure($this->catcher);
|
2022-11-06 13:36:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
public static function provideFatalErrorTests(): iterable {
|
|
|
|
$iterable = [
|
|
|
|
[
|
|
|
|
UnderflowException::class,
|
|
|
|
function (Catcher $c): void {
|
|
|
|
$c->popHandler();
|
|
|
|
}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
ArgumentCountError::class,
|
|
|
|
function (Catcher $c): void {
|
|
|
|
$c->pushHandler();
|
|
|
|
}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
UnderflowException::class,
|
|
|
|
function (Catcher $c): void {
|
|
|
|
$c->shiftHandler();
|
|
|
|
}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
ArgumentCountError::class,
|
|
|
|
function (Catcher $c): void {
|
|
|
|
$c->unshiftHandler();
|
|
|
|
}
|
|
|
|
],
|
|
|
|
];
|
2022-12-18 00:18:29 -05:00
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
foreach ($iterable as $i) {
|
|
|
|
yield $i;
|
|
|
|
}
|
2022-11-06 13:36:00 -05:00
|
|
|
}
|
|
|
|
|
2023-04-22 00:17:24 -04:00
|
|
|
public static function provideErrorHandlingTests(): iterable {
|
|
|
|
foreach ([ \E_USER_NOTICE, \E_USER_DEPRECATED, \E_USER_WARNING, \E_USER_ERROR ] as $i) {
|
|
|
|
yield [ $i ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function provideShutdownTests(): iterable {
|
|
|
|
$iterable = [
|
|
|
|
[
|
|
|
|
function (IMock $m, IMock $h): void {
|
|
|
|
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS;
|
|
|
|
Phake::when($m)->getLastError()->thenReturn([
|
|
|
|
'type' => \E_ERROR,
|
|
|
|
'message' => 'Ook!',
|
|
|
|
'file' => '/dev/null',
|
|
|
|
'line' => 2112
|
|
|
|
]);
|
|
|
|
$m->handleShutdown();
|
|
|
|
Phake::verify($m)->getLastError();
|
|
|
|
Phake::verify($m)->handleError(\E_ERROR, 'Ook!', '/dev/null', 2112);
|
|
|
|
Phake::verify($h, Phake::times(1))->invokeCallback();
|
|
|
|
}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
function (IMock $m, IMock $h): void {
|
|
|
|
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS;
|
|
|
|
Phake::when($m)->getLastError()->thenReturn([
|
|
|
|
'type' => \E_USER_ERROR,
|
|
|
|
'message' => 'Ook!',
|
|
|
|
'file' => '/dev/null',
|
|
|
|
'line' => 2112
|
|
|
|
]);
|
|
|
|
$m->handleShutdown();
|
|
|
|
Phake::verify($m)->getLastError();
|
|
|
|
Phake::verify($m)->handleError(\E_USER_ERROR, 'Ook!', '/dev/null', 2112);
|
|
|
|
Phake::verify($h, Phake::times(1))->invokeCallback();
|
|
|
|
}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
function (IMock $m, IMock $h): void {
|
|
|
|
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS;
|
|
|
|
Phake::when($m)->getLastError()->thenReturn([
|
|
|
|
'type' => \E_USER_ERROR,
|
|
|
|
'message' => 'Ook!',
|
|
|
|
'file' => '/dev/null',
|
|
|
|
'line' => 2112
|
|
|
|
]);
|
|
|
|
$m->handleShutdown();
|
|
|
|
Phake::verify($m)->getLastError();
|
|
|
|
Phake::verify($m)->handleError(\E_USER_ERROR, 'Ook!', '/dev/null', 2112);
|
|
|
|
Phake::verify($h, Phake::times(1))->invokeCallback();
|
|
|
|
}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
function (IMock $m, IMock $h): void {
|
|
|
|
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS;
|
|
|
|
$m->handleShutdown();
|
|
|
|
Phake::verify($m)->getLastError();
|
|
|
|
// Handler wouldn't be invoked because there aren't any errors in the output buffer.
|
|
|
|
Phake::verify($h, Phake::never())->invokeCallback();
|
|
|
|
}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
function (IMock $m, IMock $h): void {
|
|
|
|
// Nothing in the shutdown handler runs if Catcher is unregistered
|
|
|
|
$m->unregister();
|
|
|
|
$m->handleShutdown();
|
|
|
|
Phake::verify($m, Phake::never())->getLastError();
|
|
|
|
Phake::verify($h, Phake::never())->invokeCallback();
|
|
|
|
}
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ($iterable as $i) {
|
|
|
|
yield $i;
|
|
|
|
}
|
2022-11-06 13:36:00 -05:00
|
|
|
}
|
2022-10-24 23:44:05 -04:00
|
|
|
}
|