Dustin Wilson
1 year ago
23 changed files with 806 additions and 2494 deletions
File diff suppressed because it is too large
@ -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 ArgumentCountError extends \ArgumentCountError {} |
@ -1,196 +0,0 @@ |
|||
<?php |
|||
/** |
|||
* @license MIT |
|||
* Copyright 2022 Dustin Wilson, et al. |
|||
* See LICENSE and AUTHORS files for details |
|||
*/ |
|||
|
|||
declare(strict_types=1); |
|||
namespace MensBeam\Catcher; |
|||
|
|||
|
|||
class HTMLHandler extends Handler { |
|||
public const CONTENT_TYPE = 'text/html'; |
|||
|
|||
|
|||
/** The DOMDocument errors should be inserted into */ |
|||
protected ?\DOMDocument $_document = null; |
|||
/** The XPath path to the element where the errors should be inserted */ |
|||
protected string $_errorPath = '/html/body'; |
|||
/** The PHP-standard date format which to use for times printed to output */ |
|||
protected string $_timeFormat = 'H:i:s'; |
|||
|
|||
protected \DOMXPath $xpath; |
|||
protected \DOMElement $errorLocation; |
|||
|
|||
|
|||
|
|||
|
|||
public function __construct(array $options = []) { |
|||
parent::__construct($options); |
|||
|
|||
if ($this->_document === null) { |
|||
$this->_document = new \DOMDocument(); |
|||
$this->_document->loadHTML(<<<HTML |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head><title>HTTP {$this->_httpCode}</title></head> |
|||
<body></body> |
|||
</html> |
|||
HTML); |
|||
} |
|||
|
|||
$this->xpath = new \DOMXPath($this->_document); |
|||
$location = $this->xpath->query($this->_errorPath); |
|||
if (count($location) === 0 || (!$location->item(0) instanceof \DOMElement && !$location->item(0) instanceof \DOMDocumentFragment)) { |
|||
throw new \InvalidArgumentException('Option "errorPath" must correspond to a location that is an instance of \DOMElement or \DOMDocumentFragment'); |
|||
} |
|||
$this->errorLocation = $location->item(0); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
protected function buildOutputThrowable(array $outputThrowable, bool $previous = false): \DOMDocumentFragment { |
|||
$frag = $this->_document->createDocumentFragment(); |
|||
$tFrag = $this->_document->createDocumentFragment(); |
|||
$ip = $frag; |
|||
$hasSiblings = false; |
|||
|
|||
if ($previous === false) { |
|||
if (isset($outputThrowable['time'])) { |
|||
$p = $this->_document->createElement('p'); |
|||
$time = $this->_document->createElement('time'); |
|||
$time->setAttribute('datetime', $outputThrowable['time']->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d\TH:i:s.vO')); |
|||
$time->appendChild($this->_document->createTextNode($outputThrowable['time']->format($this->_timeFormat))); |
|||
$p->appendChild($time); |
|||
$frag->appendChild($p); |
|||
|
|||
$div = $this->_document->createElement('div'); |
|||
$frag->appendChild($div); |
|||
$ip = $div; |
|||
} |
|||
} else { |
|||
$span = $this->_document->createElement('span'); |
|||
$span->appendChild($this->_document->createTextNode('Caused by:')); |
|||
$tFrag->appendChild($span); |
|||
$tFrag->appendChild($this->_document->createTextNode(' ')); |
|||
} |
|||
|
|||
$b = $this->_document->createElement('b'); |
|||
$code = $this->_document->createElement('code'); |
|||
$code->appendChild($this->_document->createTextNode($outputThrowable['class'])); |
|||
$b->appendChild($code); |
|||
if (isset($outputThrowable['errorType'])) { |
|||
$b->insertBefore($this->_document->createTextNode("{$outputThrowable['errorType']} ("), $code); |
|||
$b->appendChild($this->_document->createTextNode(')')); |
|||
} |
|||
$tFrag->appendChild($b); |
|||
$tFrag->appendChild($this->_document->createTextNode(': ')); |
|||
$i = $this->_document->createElement('i'); |
|||
$i->appendChild($this->_document->createTextNode($outputThrowable['message'])); |
|||
$tFrag->appendChild($i); |
|||
$tFrag->appendChild($this->_document->createTextNode(' in file ')); |
|||
$code = $this->_document->createElement('code'); |
|||
$code->appendChild($this->_document->createTextNode($outputThrowable['file'])); |
|||
$tFrag->appendChild($code); |
|||
$tFrag->appendChild($this->_document->createTextNode(" on line {$outputThrowable['line']}")); |
|||
|
|||
if (isset($outputThrowable['previous'])) { |
|||
$ul = $this->_document->createElement('ul'); |
|||
$li = $this->_document->createElement('li'); |
|||
$li->appendChild($this->buildOutputThrowable($outputThrowable['previous'], true)); |
|||
$ul->appendChild($li); |
|||
$ip->appendChild($ul); |
|||
$hasSiblings = true; |
|||
} |
|||
|
|||
if ($previous === false && isset($outputThrowable['frames'])) { |
|||
$p = $this->_document->createElement('p'); |
|||
$p->appendChild($this->_document->createTextNode('Stack trace:')); |
|||
$ip->appendChild($p); |
|||
|
|||
$ol = $this->_document->createElement('ol'); |
|||
$ip->appendChild($ol); |
|||
foreach ($outputThrowable['frames'] as $frame) { |
|||
$li = $this->_document->createElement('li'); |
|||
$ol->appendChild($li); |
|||
if (isset($frame['args'])) { |
|||
$t = $this->_document->createElement('p'); |
|||
$li->appendChild($t); |
|||
} else { |
|||
$t = $li; |
|||
} |
|||
$b = $this->_document->createElement('b'); |
|||
$code = $this->_document->createElement('code'); |
|||
$b->appendChild($code); |
|||
$t->appendChild($b); |
|||
|
|||
if (isset($frame['class'])) { |
|||
$code->appendChild($this->_document->createTextNode($frame['class'])); |
|||
|
|||
if (isset($frame['errorType'])) { |
|||
$b->insertBefore($this->_document->createTextNode("{$frame['errorType']} ("), $code); |
|||
$b->appendChild($this->_document->createTextNode(')')); |
|||
} elseif (isset($frame['function'])) { |
|||
$code->firstChild->appendData("::{$frame['function']}"); |
|||
} |
|||
} elseif (!empty($frame['function'])) { |
|||
$code->appendChild($this->_document->createTextNode($frame['function'])); |
|||
} |
|||
|
|||
$t->appendChild($this->_document->createTextNode("\u{00a0}\u{00a0}")); |
|||
$code = $this->_document->createElement('code'); |
|||
$code->appendChild($this->_document->createTextNode($frame['file'])); |
|||
$t->appendChild($code); |
|||
$t->appendChild($this->_document->createTextNode(":{$frame['line']}")); |
|||
|
|||
if (isset($frame['args'])) { |
|||
$varExporter = $this->_varExporter; |
|||
$pre = $this->_document->createElement('pre'); |
|||
$pre->appendChild($this->_document->createTextNode(trim($varExporter($frame['args'])))); |
|||
$li->appendChild($pre); |
|||
} |
|||
} |
|||
|
|||
$hasSiblings = true; |
|||
} |
|||
|
|||
if ($hasSiblings) { |
|||
$p = $this->_document->createElement('p'); |
|||
$p->appendChild($tFrag); |
|||
$ip->insertBefore($p, $ip->firstChild); |
|||
} else { |
|||
$ip->appendChild($tFrag); |
|||
} |
|||
|
|||
return $frag; |
|||
} |
|||
|
|||
protected function dispatchCallback(): void { |
|||
$frag = $this->_document->createDocumentFragment(); |
|||
$allSilent = true; |
|||
foreach ($this->outputBuffer as $o) { |
|||
if ($o['outputCode'] & self::SILENT) { |
|||
continue; |
|||
} |
|||
|
|||
$li = $this->_document->createElement('li'); |
|||
$li->appendChild($this->buildOutputThrowable($o)); |
|||
$frag->appendChild($li); |
|||
|
|||
$allSilent = false; |
|||
} |
|||
|
|||
if (!$allSilent) { |
|||
$ul = $this->_document->createElement('ul'); |
|||
$ul->appendChild($frag); |
|||
$this->errorLocation->appendChild($ul); |
|||
$this->print($this->serializeDocument()); |
|||
} |
|||
} |
|||
|
|||
protected function serializeDocument() { |
|||
return $this->_document->saveHTML(); |
|||
} |
|||
} |
@ -1,32 +0,0 @@ |
|||
<?php |
|||
/** |
|||
* @license MIT |
|||
* Copyright 2022 Dustin Wilson, et al. |
|||
* See LICENSE and AUTHORS files for details |
|||
*/ |
|||
|
|||
declare(strict_types=1); |
|||
namespace MensBeam\Catcher; |
|||
|
|||
|
|||
class JSONHandler extends Handler { |
|||
public const CONTENT_TYPE = 'application/json'; |
|||
|
|||
|
|||
protected function dispatchCallback(): void { |
|||
foreach ($this->outputBuffer as $key => $value) { |
|||
if ($value['outputCode'] & self::SILENT) { |
|||
unset($this->outputBuffer[$key]); |
|||
continue; |
|||
} |
|||
|
|||
$this->outputBuffer[$key] = $this->cleanOutputThrowable($this->outputBuffer[$key]); |
|||
} |
|||
|
|||
if (count($this->outputBuffer) > 0) { |
|||
$this->print(json_encode([ |
|||
'errors' => $this->outputBuffer |
|||
], \JSON_INVALID_UTF8_SUBSTITUTE | \JSON_PARTIAL_OUTPUT_ON_ERROR | \JSON_UNESCAPED_SLASHES)); |
|||
} |
|||
} |
|||
} |
@ -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 UnderflowException extends \UnderflowException {} |
@ -1,100 +0,0 @@ |
|||
#!/usr/bin/env php |
|||
<?php |
|||
|
|||
$cwd = __DIR__; |
|||
$codeDir = "$cwd/lib"; |
|||
$testDir = "$cwd/tests"; |
|||
|
|||
function help($error = true): void { |
|||
$help = <<<USAGE |
|||
Usage: |
|||
run test [additional_phpunit_options] |
|||
run --help |
|||
|
|||
USAGE; |
|||
|
|||
if ($error) { |
|||
fprintf(\STDERR, $help); |
|||
} else { |
|||
echo $help; |
|||
} |
|||
|
|||
exit((int)$error); |
|||
} |
|||
|
|||
function error(string $message): void { |
|||
fprintf(\STDERR, "ERROR: $message\n"); |
|||
exit(1); |
|||
} |
|||
|
|||
if (count($argv) === 1) { |
|||
help(); |
|||
} |
|||
|
|||
switch ($argv[1]) { |
|||
case 'test': |
|||
$opts = [ |
|||
'--colors', |
|||
'--coverage-html='.escapeshellarg("$testDir/coverage") |
|||
]; |
|||
|
|||
if (isset($argv[2])) { |
|||
$opts = [ ...$opts, array_slice($argv, 2) ]; |
|||
} |
|||
|
|||
$opts = implode(' ', $opts); |
|||
break; |
|||
case '-h': |
|||
case '--help': |
|||
help(false); |
|||
break; |
|||
default: |
|||
help(); |
|||
} |
|||
|
|||
$phpunitPath = escapeshellarg("$cwd/vendor/bin/phpunit"); |
|||
$confPath = "$testDir/phpunit.dist.xml"; |
|||
if (!file_exists($confPath)) { |
|||
$confPath = "$testDir/phpunit.xml"; |
|||
if (!file_exists($confPath)) { |
|||
error('A phpunit configuration must be present at "tests/phpunit.dist.xml" or "tests/phpunit.xml"; aborting'); |
|||
} |
|||
} |
|||
$confPath = escapeshellarg($confPath); |
|||
|
|||
$cmd = [ |
|||
escapeshellarg(\PHP_BINARY), |
|||
'-d opcache.enable_cli=0', |
|||
'-d zend.assertions=1' |
|||
]; |
|||
if (!extension_loaded('xdebug')) { |
|||
$extDir = rtrim(ini_get("extension_dir"), "/"); |
|||
if (file_exists("$extDir/xdebug.so")) { |
|||
$cmd[] = '-d zend_extension=xdebug.so'; |
|||
} else { |
|||
error('Xdebug is not installed on your system; aborting'); |
|||
} |
|||
} |
|||
$cmd[] = '-d xdebug.mode=coverage,develop,trace'; |
|||
$cmd = implode(' ', $cmd); |
|||
|
|||
$process = proc_open("$cmd $phpunitPath -c $confPath $opts", [ |
|||
1 => ['pipe', 'w'], |
|||
2 => ['pipe', 'w'] |
|||
], $pipes); |
|||
|
|||
if ($process === false) { |
|||
error('Failed to execute phpunit'); |
|||
} |
|||
|
|||
$stderr = trim(stream_get_contents($pipes[2])); |
|||
$output = trim(stream_get_contents($pipes[1])); |
|||
|
|||
fclose($pipes[1]); |
|||
fclose($pipes[2]); |
|||
proc_close($process); |
|||
|
|||
echo "$output\n"; |
|||
if ($stderr !== '') { |
|||
error($stderr); |
|||
} |
@ -0,0 +1,37 @@ |
|||
#!/usr/bin/env php |
|||
<?php |
|||
/** |
|||
* @license MIT |
|||
* Copyright 2022 Dustin Wilson, et al. |
|||
* See LICENSE and AUTHORS files for details |
|||
*/ |
|||
|
|||
$dir = ini_get('extension_dir'); |
|||
$php = escapeshellarg(\PHP_BINARY); |
|||
$code = escapeshellarg(__DIR__ . '/lib'); |
|||
|
|||
|
|||
array_shift($argv); |
|||
foreach ($argv as $k => $v) { |
|||
if (in_array($v, ['--coverage', '--coverage-html'])) { |
|||
$argv[$k] = '--coverage-html tests/coverage'; |
|||
} |
|||
} |
|||
|
|||
$cmd = [ |
|||
$php, |
|||
'-d opcache.enable_cli=0', |
|||
]; |
|||
|
|||
if (!extension_loaded('xdebug')) { |
|||
$cmd[] = '-d zend_extension=xdebug.so'; |
|||
} |
|||
|
|||
$cmd = implode(' ', [ |
|||
...$cmd, |
|||
'-d xdebug.mode=coverage,develop,trace', |
|||
escapeshellarg(__DIR__ . '/vendor/bin/phpunit'), |
|||
'--configuration tests/phpunit.xml', |
|||
...$argv |
|||
]); |
|||
passthru($cmd); |
@ -1,23 +1,18 @@ |
|||
<?php |
|||
/** @license MIT |
|||
* Copyright 2017 , Dustin Wilson, J. King et al. |
|||
* See LICENSE and AUTHORS files for details */ |
|||
|
|||
declare(strict_types=1); |
|||
namespace MensBeam; |
|||
namespace MensBeam\Logger\Test; |
|||
|
|||
ini_set('memory_limit', '-1'); |
|||
ini_set('memory_limit', '2G'); |
|||
ini_set('zend.assertions', '1'); |
|||
ini_set('assert.exception', 'true'); |
|||
error_reporting(\E_ALL); |
|||
|
|||
$cwd = dirname(__DIR__); |
|||
require_once "$cwd/vendor/autoload.php"; |
|||
define('CWD', dirname(__DIR__)); |
|||
require_once CWD . '/vendor/autoload.php'; |
|||
|
|||
if (function_exists('xdebug_set_filter')) { |
|||
if (defined('XDEBUG_PATH_INCLUDE')) { |
|||
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_INCLUDE, [ "$cwd/lib/" ]); |
|||
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_INCLUDE, [ CWD . '/lib/' ]); |
|||
} else { |
|||
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, [ "$cwd/lib/" ]); |
|||
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, [ CWD . '/lib/' ]); |
|||
} |
|||
} |
@ -1,499 +1,280 @@ |
|||
<?php |
|||
/** |
|||
/** |
|||
* @license MIT |
|||
* Copyright 2022 Dustin Wilson, et al. |
|||
* See LICENSE and AUTHORS files for details |
|||
* See LICENSE and AUTHORS files for details |
|||
*/ |
|||
|
|||
declare(strict_types=1); |
|||
namespace MensBeam\Catcher\Test; |
|||
use MensBeam\Catcher; |
|||
use MensBeam\Catcher, |
|||
Phake, |
|||
Phake\IMock; |
|||
use MensBeam\Catcher\{ |
|||
ArgumentCountError, |
|||
Error, |
|||
HTMLHandler, |
|||
JSONHandler, |
|||
PlainTextHandler |
|||
PlainTextHandler, |
|||
UnderflowException |
|||
}; |
|||
use Eloquent\Phony\Phpunit\Phony; |
|||
|
|||
|
|||
/** @covers \MensBeam\Catcher */ |
|||
class TestCatcher extends \PHPUnit\Framework\TestCase { |
|||
/** |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* |
|||
* @covers \MensBeam\Catcher::getHandlers |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\HTMLHandler::__construct |
|||
*/ |
|||
public function testMethod___construct(): void { |
|||
$c = new Catcher(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$this->assertSame(1, count($c->getHandlers())); |
|||
$this->assertSame(PlainTextHandler::class, $c->getHandlers()[0]::class); |
|||
$c->unregister(); |
|||
|
|||
$c = new Catcher( |
|||
new PlainTextHandler(), |
|||
new HTMLHandler(), |
|||
new JSONHandler() |
|||
); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$this->assertSame('MensBeam\Catcher', $c::class); |
|||
$this->assertSame(3, count($c->getHandlers())); |
|||
$h = $c->getHandlers(); |
|||
$this->assertSame(PlainTextHandler::class, $h[0]::class); |
|||
$this->assertSame(HTMLHandler::class, $h[1]::class); |
|||
$this->assertSame(JSONHandler::class, $h[2]::class); |
|||
$c->unregister(); |
|||
} |
|||
protected ?Catcher $catcher = null; |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::getLastThrowable |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::handleError |
|||
* @covers \MensBeam\Catcher::handleThrowable |
|||
* @covers \MensBeam\Catcher::isErrorFatal |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_getLastThrowable(): void { |
|||
$c = new Catcher(new PlainTextHandler([ 'silent' => true ])); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
trigger_error('Ook!', \E_USER_WARNING); |
|||
$this->assertSame(\E_USER_WARNING, $c->getLastThrowable()->getCode()); |
|||
$c->unregister(); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
*/ |
|||
public function testMethod_pushHandler(): void { |
|||
$e = null; |
|||
set_error_handler(function($errno) use (&$e) { |
|||
$e = $errno; |
|||
}); |
|||
|
|||
$h = new PlainTextHandler(); |
|||
$c = new Catcher($h, $h); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$c->unregister(); |
|||
$this->assertSame(\E_USER_WARNING, $e); |
|||
$e = null; |
|||
|
|||
$c = new Catcher(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$c->unregister(); |
|||
$c->pushHandler($h, $h); |
|||
$this->assertSame(\E_USER_WARNING, $e); |
|||
|
|||
restore_error_handler(); |
|||
|
|||
$c = new Catcher(); |
|||
$c->unregister(); |
|||
|
|||
$e = null; |
|||
try { |
|||
$c->pushHandler(); |
|||
} catch (\Throwable $t) { |
|||
$e = $t::class; |
|||
} finally { |
|||
$this->assertSame(\ArgumentCountError::class, $e); |
|||
public function setUp(): void { |
|||
if ($this->catcher !== null) { |
|||
$this->catcher->unregister(); |
|||
} |
|||
} |
|||
$this->catcher = new Catcher(); |
|||
$this->catcher->preventExit = true; |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::popHandler |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\HTMLHandler::__construct |
|||
*/ |
|||
public function testMethod_popHandler(): void { |
|||
$h = [ |
|||
new HTMLHandler(), |
|||
new PlainTextHandler(), |
|||
new JSONHandler() |
|||
]; |
|||
$c = new Catcher(...$h); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$hh = $c->popHandler(); |
|||
$this->assertSame($h[2], $hh); |
|||
$hh = $c->popHandler(); |
|||
$this->assertSame($h[1], $hh); |
|||
|
|||
$e = null; |
|||
try { |
|||
$c->popHandler(); |
|||
} catch (\Throwable $t) { |
|||
$e = $t::class; |
|||
} finally { |
|||
$c->unregister(); |
|||
$this->assertSame(\Exception::class, $e); |
|||
} |
|||
// Do this instead of specifying the option in the constructor for coverage |
|||
// purposes... |
|||
$handlers = $this->catcher->getHandlers(); |
|||
$handlers[0]->setOption('silent', true); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::isRegistered |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
*/ |
|||
public function testMethod_register(): void { |
|||
$c = new Catcher(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$this->assertTrue($c->isRegistered()); |
|||
$this->assertFalse($c->register()); |
|||
$c->unregister(); |
|||
$this->assertFalse($c->isRegistered()); |
|||
public function tearDown(): void { |
|||
$this->catcher->unregister(); |
|||
$this->catcher = null; |
|||
error_reporting(\E_ALL); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::setHandlers |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::getHandlers |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
*/ |
|||
public function testMethod_setHandlers(): void { |
|||
$c = new Catcher(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$c->setHandlers(new PlainTextHandler()); |
|||
$h = $c->getHandlers(); |
|||
$this->assertSame(1, count($h)); |
|||
$this->assertSame(PlainTextHandler::class, $h[0]::class); |
|||
$c->unregister(); |
|||
|
|||
public function testConstructor(): void { |
|||
$h = $this->catcher->getHandlers(); |
|||
$this->assertEquals(1, count($h)); |
|||
$this->assertInstanceOf(PlainTextHandler::class, $h[0]); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::shiftHandler |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\HTMLHandler::__construct |
|||
*/ |
|||
public function testMethod_shiftHandler(): void { |
|||
$h = [ |
|||
new HTMLHandler(), |
|||
new PlainTextHandler(), |
|||
new JSONHandler() |
|||
]; |
|||
$c = new Catcher(...$h); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$c->unregister(); |
|||
$hh = $c->shiftHandler(); |
|||
$this->assertSame($h[0], $hh); |
|||
$hh = $c->shiftHandler(); |
|||
$this->assertSame($h[1], $hh); |
|||
|
|||
$e = null; |
|||
/** @dataProvider provideErrorHandlingTests */ |
|||
public function testErrorHandling(int $code): void { |
|||
$t = null; |
|||
try { |
|||
$c->shiftHandler(); |
|||
} catch (\Throwable $t) { |
|||
$e = $t::class; |
|||
} finally { |
|||
$this->assertSame(\Exception::class, $e); |
|||
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()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
*/ |
|||
public function testMethod_unregister(): void { |
|||
$c = new Catcher(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$c->unregister(); |
|||
$this->assertFalse($c->unregister()); |
|||
} |
|||
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); |
|||
Phake::when($m)->handleShutdown()->thenReturn(null); |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::unshiftHandler |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::getHandlers |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\HTMLHandler::__construct |
|||
*/ |
|||
public function testMethod_unshiftHandler(): void { |
|||
$c = new Catcher(new PlainTextHandler()); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$c->unshiftHandler(new JSONHandler(), new HTMLHandler(), new PlainTextHandler()); |
|||
$h = $c->getHandlers(); |
|||
$this->assertSame(4, count($h)); |
|||
$this->assertSame(JSONHandler::class, $h[0]::class); |
|||
$this->assertSame(HTMLHandler::class, $h[1]::class); |
|||
$this->assertSame(PlainTextHandler::class, $h[2]::class); |
|||
$this->assertSame(PlainTextHandler::class, $h[3]::class); |
|||
|
|||
$e = null; |
|||
set_error_handler(function($errno) use (&$e) { |
|||
$e = $errno; |
|||
}); |
|||
|
|||
$c->unshiftHandler($h[0]); |
|||
$this->assertSame(\E_USER_WARNING, $e); |
|||
$e = null; |
|||
$h = new PlainTextHandler(); |
|||
$c->unshiftHandler($h, $h); |
|||
$this->assertSame(\E_USER_WARNING, $e); |
|||
|
|||
restore_error_handler(); |
|||
$c->unregister(); |
|||
|
|||
$c = new Catcher(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$c->unregister(); |
|||
|
|||
$e = null; |
|||
try { |
|||
$c->unshiftHandler(); |
|||
} catch (\Throwable $t) { |
|||
$e = $t::class; |
|||
} finally { |
|||
$this->assertSame(\ArgumentCountError::class, $e); |
|||
} |
|||
trigger_error('Ook!', \E_USER_ERROR); |
|||
|
|||
Phake::verify($h, Phake::times(1))->invokeCallback(); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::handleError |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::getLastThrowable |
|||
* @covers \MensBeam\Catcher::handleThrowable |
|||
* @covers \MensBeam\Catcher::isErrorFatal |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_handleError(): void { |
|||
$c = new Catcher(new PlainTextHandler([ 'silent' => true ])); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
|
|||
trigger_error('Ook!', \E_USER_NOTICE); |
|||
$t = $c->getLastThrowable(); |
|||
$this->assertSame(Error::class, $t::class); |
|||
$this->assertSame(\E_USER_NOTICE, $t->getCode()); |
|||
|
|||
trigger_error('Ook!', \E_USER_DEPRECATED); |
|||
$t = $c->getLastThrowable(); |
|||
$this->assertSame(Error::class, $t::class); |
|||
$this->assertSame(\E_USER_DEPRECATED, $t->getCode()); |
|||
|
|||
trigger_error('Ook!', \E_USER_WARNING); |
|||
$t = $c->getLastThrowable(); |
|||
$this->assertSame(Error::class, $t::class); |
|||
$this->assertSame(\E_USER_WARNING, $t->getCode()); |
|||
public function testHandlerBubbling(): void { |
|||
$this->catcher->unregister(); |
|||
|
|||
trigger_error('Ook!', \E_USER_ERROR); |
|||
$t = $c->getLastThrowable(); |
|||
$this->assertSame(Error::class, $t::class); |
|||
$this->assertSame(\E_USER_ERROR, $t->getCode()); |
|||
$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; |
|||
|
|||
$er = error_reporting(); |
|||
error_reporting(0); |
|||
trigger_error('Ook!', \E_USER_ERROR); |
|||
error_reporting($er); |
|||
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(); |
|||
} |
|||
|
|||
$c->unregister(); |
|||
public function testHandlerForceExiting(): void { |
|||
$this->catcher->setHandlers(new TestingHandler([ 'forceExit' => true ])); |
|||
$this->catcher->errorHandlingMethod = Catcher::THROW_NO_ERRORS; |
|||
$this->catcher->preventExit = true; |
|||
|
|||
$h1 = Phony::partialMock(PlainTextHandler::class, [ [ 'silent' => true ] ]); |
|||
$h2 = Phony::partialMock(HTMLHandler::class, [ [ 'silent' => true ] ]); |
|||
$h3 = Phony::partialMock(JSONHandler::class, [ [ 'silent' => true ] ]); |
|||
trigger_error('Ook', \E_USER_ERROR); |
|||
$this->assertSame(Error::class, $this->catcher->getLastThrowable()::class); |
|||
} |
|||
|
|||
$h = Phony::partialMock(Catcher::class, [ |
|||
$h1->get(), |
|||
$h2->get(), |
|||
$h3->get() |
|||
]); |
|||
$c = $h->get(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
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()); |
|||
} |
|||
|
|||
trigger_error('Ook!', \E_USER_ERROR); |
|||
/** @dataProvider provideShutdownTests */ |
|||
public function testShutdownHandling(\Closure $closure): void { |
|||
$this->catcher->unregister(); |
|||
|
|||
$h1->dispatch->called(); |
|||
$h2->dispatch->called(); |
|||
$h3->dispatch->called(); |
|||
$h1 = Phake::partialMock(TestingHandler::class); |
|||
$this->catcher = $m = Phake::partialMock(Catcher::class, $h1); |
|||
$closure($m, $h1); |
|||
} |
|||
|
|||
$c->throwErrors = true; |
|||
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())); |
|||
} |
|||
|
|||
public function testWeirdErrorReporting(): void { |
|||
error_reporting(\E_ERROR); |
|||
$t = null; |
|||
try { |
|||
trigger_error('Ook!', \E_USER_WARNING); |
|||
} catch (\Throwable $t) { |
|||
$this->assertInstanceOf(Error::class, $t); |
|||
$this->assertSame(\E_USER_WARNING, $t->getCode()); |
|||
} catch (\Throwable $t) {} finally { |
|||
$this->assertNull($t); |
|||
$this->assertNull($this->catcher->getLastThrowable()); |
|||
} |
|||
} |
|||
|
|||
try { |
|||
trigger_error('Ook!', \E_USER_ERROR); |
|||
} catch (\Throwable $t) { |
|||
$this->assertInstanceOf(Error::class, $t); |
|||
$this->assertSame(\E_USER_ERROR, $t->getCode()); |
|||
} |
|||
|
|||
$c->unregister(); |
|||
$c->throwErrors = false; |
|||
|
|||
/** @dataProvider provideFatalErrorTests */ |
|||
public function testFatalErrors(string $throwableClassName, \Closure $closure): void { |
|||
$this->expectException($throwableClassName); |
|||
$closure($this->catcher); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::handleThrowable |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::getLastThrowable |
|||
* @covers \MensBeam\Catcher::handleError |
|||
* @covers \MensBeam\Catcher::isErrorFatal |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_handleThrowable(): void { |
|||
$c = new Catcher(new PlainTextHandler([ 'silent' => true, 'forceBreak' => true ])); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
trigger_error('Ook!', \E_USER_ERROR); |
|||
$t = $c->getLastThrowable(); |
|||
$this->assertSame(Error::class, $t::class); |
|||
$this->assertSame(\E_USER_ERROR, $t->getCode()); |
|||
$c->unregister(); |
|||
|
|||
$h = Phony::partialMock(Catcher::class, [ new PlainTextHandler([ 'silent' => true ]) ]); |
|||
$h->exit->returns(); |
|||
$c = $h->get(); |
|||
$c->preventExit = false; |
|||
$c->throwErrors = false; |
|||
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(); |
|||
} |
|||
], |
|||
]; |
|||
|
|||
trigger_error('Ook!', \E_USER_ERROR); |
|||
$t = $c->getLastThrowable(); |
|||
$this->assertSame(Error::class, $t::class); |
|||
$this->assertSame(\E_USER_ERROR, $t->getCode()); |
|||
$c->unregister(); |
|||
foreach ($iterable as $i) { |
|||
yield $i; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher::handleShutdown |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::getLastError |
|||
* @covers \MensBeam\Catcher::handleError |
|||
* @covers \MensBeam\Catcher::isErrorFatal |
|||
* @covers \MensBeam\Catcher::handleThrowable |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_handleShutdown(): void { |
|||
$c = new Catcher(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$c->handleShutdown(); |
|||
$p = new \ReflectionProperty($c, 'isShuttingDown'); |
|||
$p->setAccessible(true); |
|||
$this->assertTrue($p->getValue($c)); |
|||
$c->unregister(); |
|||
|
|||
$c = new Catcher(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$c->unregister(); |
|||
$c->handleShutdown(); |
|||
$p = new \ReflectionProperty($c, 'isShuttingDown'); |
|||
$p->setAccessible(true); |
|||
$this->assertFalse($p->getValue($c)); |
|||
$c->unregister(); |
|||
|
|||
$h = Phony::partialMock(Catcher::class, [ new PlainTextHandler([ 'silent' => true ]) ]); |
|||
$h->getLastError->returns([ |
|||
'type' => \E_ERROR, |
|||
'message' => 'Ook!', |
|||
'file' => '/dev/null', |
|||
'line' => 2112 |
|||
]); |
|||
$c = $h->get(); |
|||
$c->handleShutdown(); |
|||
$h->handleError->called(); |
|||
$h->handleThrowable->called(); |
|||
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; |
|||
} |
|||
} |
|||
} |
@ -1,89 +0,0 @@ |
|||
<?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; |
|||
use MensBeam\Catcher\{ |
|||
Error, |
|||
Handler, |
|||
HTMLHandler, |
|||
ThrowableController |
|||
}; |
|||
|
|||
|
|||
class TestHTMLHandler extends \PHPUnit\Framework\TestCase { |
|||
/** |
|||
* @covers \MensBeam\Catcher\HTMLHandler::__construct |
|||
* |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
*/ |
|||
public function testMethod___construct__exception(): void { |
|||
$this->expectException(\InvalidArgumentException::class); |
|||
new HTMLHandler([ 'errorPath' => '/html/body/fail' ]); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher\HTMLHandler::buildOutputThrowable |
|||
* |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\HTMLHandler::__construct |
|||
* @covers \MensBeam\Catcher\HTMLHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\HTMLHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\HTMLHandler::serializeDocument |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getFrames |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_buildOutputThrowable(): void { |
|||
$c = new ThrowableController(new \Exception(message: 'Ook!', previous: new Error(message: 'Eek!', code: \E_USER_ERROR, previous: new Error(message: 'Ack!')))); |
|||
$h = new HTMLHandler([ |
|||
'backtraceArgFrameLimit' => 1, |
|||
'outputBacktrace' => true, |
|||
'outputToStderr' => false |
|||
]); |
|||
$o = $h->handle($c); |
|||
$this->assertSame(Handler::CONTINUE, $o['controlCode']); |
|||
|
|||
ob_start(); |
|||
$h->dispatch(); |
|||
ob_end_clean(); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher\HTMLHandler::dispatchCallback |
|||
* |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\Handler::handleCallback |
|||
* @covers \MensBeam\Catcher\HTMLHandler::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_dispatchCallback(): void { |
|||
$c = new ThrowableController(new \Exception(message: 'Ook!')); |
|||
$h = new HTMLHandler([ |
|||
'backtraceArgFrameLimit' => 1, |
|||
'outputToStderr' => false, |
|||
'silent' => true |
|||
]); |
|||
$h->handle($c); |
|||
|
|||
ob_start(); |
|||
$h->dispatch(); |
|||
$o = ob_get_clean(); |
|||
$this->assertEmpty($o); |
|||
} |
|||
} |
@ -1,173 +0,0 @@ |
|||
<?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; |
|||
use MensBeam\Catcher\{ |
|||
Error, |
|||
HTMLHandler, |
|||
PlainTextHandler, |
|||
JSONHandler, |
|||
ThrowableController |
|||
}; |
|||
use Eloquent\Phony\Phpunit\Phony; |
|||
|
|||
|
|||
class TestHandler extends \PHPUnit\Framework\TestCase { |
|||
/** |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
*/ |
|||
public function testMethod___construct__exception(): void { |
|||
$this->expectException(\RangeException::class); |
|||
new PlainTextHandler([ 'httpCode' => 42 ]); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher\Handler::cleanOutputThrowable |
|||
* |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\Handler::handleCallback |
|||
* @covers \MensBeam\Catcher\Handler::print |
|||
* @covers \MensBeam\Catcher\JSONHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getFrames |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod__cleanOutputThrowable(): void { |
|||
// Just need to test coverage here; TestJSONHandler covers this one thoroughly. |
|||
$c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error('Eek!'))); |
|||
$h = new JSONHandler([ |
|||
'outputBacktrace' => true, |
|||
'outputToStderr' => false |
|||
]); |
|||
$o = $h->handle($c); |
|||
|
|||
ob_start(); |
|||
$h->dispatch(); |
|||
ob_end_clean(); |
|||
|
|||
$this->assertTrue(isset($o['frames'])); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::handleCallback |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getFrames |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod__handle(): void { |
|||
// Just need to test backtrace handling. The rest has already been covered by prior tests. |
|||
$c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new Error(message: 'Eek!', code: \E_USER_ERROR)))); |
|||
$h = new HTMLHandler([ |
|||
'outputBacktrace' => true, |
|||
'outputToStderr' => true |
|||
]); |
|||
$this->assertTrue(isset($h->handle($c)['frames'])); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher\Handler::getOption |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::handleError |
|||
* @covers \MensBeam\Catcher::handleThrowable |
|||
* @covers \MensBeam\Catcher::isErrorFatal |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod__getOption(): void { |
|||
$h = new PlainTextHandler([ 'forceExit' => true, 'silent' => true ]); |
|||
$this->assertTrue($h->getOption('forceExit')); |
|||
|
|||
$c = new Catcher($h); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
$this->assertNull($h->getOption('ook')); |
|||
$c->unregister(); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher\Handler::setOption |
|||
* |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
*/ |
|||
public function testMethod__setOption(): void { |
|||
$h = new PlainTextHandler([ 'forceExit' => true, 'silent' => true ]); |
|||
$h->setOption('forceExit', false); |
|||
$r = new \ReflectionProperty($h, '_forceExit'); |
|||
$r->setAccessible(true); |
|||
$this->assertFalse($r->getValue($h)); |
|||
|
|||
$m = Phony::partialMock(Catcher::class, [ |
|||
$h |
|||
]); |
|||
$c = $m->get(); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
|
|||
$h->setOption('ook', 'FAIL'); |
|||
$m->handleError->called(); |
|||
|
|||
$c->unregister(); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher\Handler::print |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::handleError |
|||
* @covers \MensBeam\Catcher::isErrorFatal |
|||
* @covers \MensBeam\Catcher::handleThrowable |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::serializeOutputThrowable |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod__print(): void { |
|||
// Just need to test forceOutputNow for coverage purposes |
|||
$c = new Catcher(new PlainTextHandler([ 'forceOutputNow' => true, 'outputToStderr' => false ])); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
ob_start(); |
|||
trigger_error('Ook!', \E_USER_NOTICE); |
|||
ob_end_clean(); |
|||
$this->assertSame(\E_USER_NOTICE, $c->getLastThrowable()->getCode()); |
|||
$c->unregister(); |
|||
} |
|||
} |
@ -1,48 +0,0 @@ |
|||
<?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; |
|||
use MensBeam\Catcher\{ |
|||
Error, |
|||
Handler, |
|||
JSONHandler, |
|||
ThrowableController |
|||
}; |
|||
|
|||
|
|||
class TestJSONHandler extends \PHPUnit\Framework\TestCase { |
|||
/** |
|||
* @covers \MensBeam\Catcher\JSONHandler::dispatchCallback |
|||
* |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\Handler::handleCallback |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_dispatchCallback(): void { |
|||
// Not much left to cover; just need to test silent output |
|||
$c = new ThrowableController(new \Exception(message: 'Ook!', previous: new Error(message: 'Eek!', code: \E_USER_ERROR, previous: new Error(message: 'Ack!')))); |
|||
$h = new JSONHandler([ |
|||
'silent' => true, |
|||
'outputToStderr' => false |
|||
]); |
|||
$o = $h->handle($c); |
|||
|
|||
ob_start(); |
|||
$h->dispatch(); |
|||
$o = ob_get_clean(); |
|||
$this->assertEmpty($o); |
|||
} |
|||
} |
@ -1,145 +0,0 @@ |
|||
<?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; |
|||
use MensBeam\Catcher\{ |
|||
Error, |
|||
Handler, |
|||
PlainTextHandler, |
|||
ThrowableController |
|||
}; |
|||
use Eloquent\Phony\Phpunit\Phony, |
|||
Psr\Log\LoggerInterface; |
|||
|
|||
|
|||
class TestPlainTextHandler extends \PHPUnit\Framework\TestCase { |
|||
/** |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::log |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::serializeOutputThrowable |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getFrames |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_handleCallback(): void { |
|||
$c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new Error(message: 'Ack!', code: \E_USER_ERROR)))); |
|||
$l = Phony::mock(LoggerInterface::class); |
|||
$h = new PlainTextHandler([ |
|||
'logger' => $l->get(), |
|||
'outputBacktrace' => true, |
|||
'outputToStderr' => false |
|||
]); |
|||
$o = $h->handle($c); |
|||
$this->assertSame(Handler::CONTINUE, $o['controlCode']); |
|||
$this->assertSame(Handler::OUTPUT | Handler::NOW, $o['outputCode']); |
|||
$this->assertTrue(isset($o['previous'])); |
|||
|
|||
ob_start(); |
|||
$h->dispatch(); |
|||
ob_end_clean(); |
|||
|
|||
$l->critical->called(); |
|||
|
|||
$c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new Error(message: 'Ack!', code: \E_USER_ERROR)))); |
|||
$l = Phony::mock(LoggerInterface::class); |
|||
$h = new PlainTextHandler([ |
|||
'logger' => $l->get(), |
|||
'silent' => true |
|||
]); |
|||
$o = $h->handle($c); |
|||
$this->assertSame(Handler::CONTINUE, $o['controlCode']); |
|||
$this->assertSame(Handler::SILENT | Handler::NOW, $o['outputCode']); |
|||
$this->assertTrue(isset($o['previous'])); |
|||
|
|||
ob_start(); |
|||
$h->dispatch(); |
|||
ob_end_clean(); |
|||
|
|||
$l->critical->called(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::log |
|||
* |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_log(): void { |
|||
$l = Phony::mock(LoggerInterface::class); |
|||
$h = new PlainTextHandler([ |
|||
'logger' => $l->get(), |
|||
'outputToStderr' => false |
|||
]); |
|||
|
|||
$e = [ |
|||
'notice' => [ |
|||
\E_NOTICE, |
|||
\E_USER_NOTICE, |
|||
\E_STRICT |
|||
], |
|||
'warning' => [ |
|||
\E_WARNING, |
|||
\E_COMPILE_WARNING, |
|||
\E_USER_WARNING, |
|||
\E_DEPRECATED, |
|||
\E_USER_DEPRECATED |
|||
], |
|||
'error' => [ |
|||
\E_RECOVERABLE_ERROR |
|||
], |
|||
'alert' => [ |
|||
\E_PARSE, |
|||
\E_CORE_ERROR, |
|||
\E_COMPILE_ERROR |
|||
] |
|||
]; |
|||
|
|||
foreach ($e as $k => $v) { |
|||
foreach ($v as $vv) { |
|||
$h->handle(new ThrowableController(new Error('Ook!', $vv))); |
|||
|
|||
ob_start(); |
|||
$h->dispatch(); |
|||
ob_end_clean(); |
|||
|
|||
$l->$k->called(); |
|||
} |
|||
} |
|||
|
|||
$h->handle(new ThrowableController(new \PharException('Ook!'))); |
|||
ob_start(); |
|||
$h->dispatch(); |
|||
ob_end_clean(); |
|||
$l->alert->called(); |
|||
|
|||
$h->handle(new ThrowableController(new \RuntimeException('Ook!'))); |
|||
ob_start(); |
|||
$h->dispatch(); |
|||
ob_end_clean(); |
|||
$l->alert->called(); |
|||
} |
|||
} |
@ -1,189 +0,0 @@ |
|||
<?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; |
|||
use MensBeam\Catcher\{ |
|||
Error, |
|||
PlainTextHandler, |
|||
ThrowableController |
|||
}; |
|||
|
|||
|
|||
class TestThrowableController extends \PHPUnit\Framework\TestCase { |
|||
/** |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* |
|||
* @covers \MensBeam\Catcher::__construct |
|||
* @covers \MensBeam\Catcher::getLastThrowable |
|||
* @covers \MensBeam\Catcher::handleError |
|||
* @covers \MensBeam\Catcher::isErrorFatal |
|||
* @covers \MensBeam\Catcher::handleThrowable |
|||
* @covers \MensBeam\Catcher::pushHandler |
|||
* @covers \MensBeam\Catcher::register |
|||
* @covers \MensBeam\Catcher::unregister |
|||
* @covers \MensBeam\Catcher\Error::__construct |
|||
* @covers \MensBeam\Catcher\Handler::__construct |
|||
* @covers \MensBeam\Catcher\Handler::dispatch |
|||
* @covers \MensBeam\Catcher\Handler::handle |
|||
* @covers \MensBeam\Catcher\Handler::print |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
|||
* @covers \MensBeam\Catcher\PlainTextHandler::serializeOutputThrowable |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
|||
*/ |
|||
public function testMethod_getErrorType(): void { |
|||
$c = new Catcher(new PlainTextHandler([ 'outputToStderr' => false ])); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
ob_start(); |
|||
trigger_error('Ook!', \E_USER_DEPRECATED); |
|||
ob_end_clean(); |
|||
$this->assertSame(\E_USER_DEPRECATED, $c->getLastThrowable()->getCode()); |
|||
$c->unregister(); |
|||
|
|||
$c = new Catcher(new PlainTextHandler([ 'outputToStderr' => false ])); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
ob_start(); |
|||
trigger_error('Ook!', \E_USER_WARNING); |
|||
ob_end_clean(); |
|||
$this->assertSame(\E_USER_WARNING, $c->getLastThrowable()->getCode()); |
|||
$c->unregister(); |
|||
|
|||
$c = new Catcher(new PlainTextHandler([ 'outputToStderr' => false ])); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
ob_start(); |
|||
trigger_error('Ook!', \E_USER_NOTICE); |
|||
ob_end_clean(); |
|||
$this->assertSame(\E_USER_NOTICE, $c->getLastThrowable()->getCode()); |
|||
$c->unregister(); |
|||
|
|||
$c = new Catcher(new PlainTextHandler([ 'outputToStderr' => false ])); |
|||
$c->preventExit = true; |
|||
$c->throwErrors = false; |
|||
ob_start(); |
|||
trigger_error('Ook!', \E_USER_ERROR); |
|||
ob_end_clean(); |
|||
$this->assertSame(\E_USER_ERROR, $c->getLastThrowable()->getCode()); |
|||
$c->unregister(); |
|||
|
|||
// These others will be tested by invoking the method directly |
|||
$c = new ThrowableController(new Error('Ook!', \E_ERROR)); |
|||
$this->assertSame('PHP Fatal Error', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_WARNING)); |
|||
$this->assertSame('PHP Warning', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_PARSE)); |
|||
$this->assertSame('PHP Parsing Error', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_NOTICE)); |
|||
$this->assertSame('PHP Notice', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_DEPRECATED)); |
|||
$this->assertSame('Deprecated', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_CORE_ERROR)); |
|||
$this->assertSame('PHP Core Error', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_CORE_WARNING)); |
|||
$this->assertSame('PHP Core Warning', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_COMPILE_ERROR)); |
|||
$this->assertSame('Compile Error', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_COMPILE_WARNING)); |
|||
$this->assertSame('Compile Warning', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_STRICT)); |
|||
$this->assertSame('Runtime Notice', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!', \E_RECOVERABLE_ERROR)); |
|||
$this->assertSame('Recoverable Error', $c->getErrorType()); |
|||
$c = new ThrowableController(new Error('Ook!')); |
|||
$this->assertNull($c->getErrorType()); |
|||
$c = new ThrowableController(new \Exception('Ook!')); |
|||
$this->assertNull($c->getErrorType()); |
|||
|
|||
// For code coverage purposes. |
|||
$this->assertNull($c->getErrorType()); |
|||
} |
|||
|
|||
/** |
|||
* @covers \MensBeam\Catcher\ThrowableController::getFrames |
|||
* |
|||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
|||
* @covers \MensBeam\Catcher\ThrowableController::getErrorType |
|||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
|||
*/ |
|||
public function testMethod_getFrames(): void { |
|||
$f = false; |
|||
try { |
|||
throw new \Exception('Ook!'); |
|||
} catch (\Throwable $t) { |
|||
$c = new ThrowableController($t); |
|||
$f = $c->getFrames(); |
|||
} finally { |
|||
$this->assertSame(\Exception::class, $f[0]['class']); |
|||
} |
|||
|
|||
$f = false; |
|||
try { |
|||
throw new Error('Ook!', \E_ERROR); |
|||
} catch (\Throwable $t) { |
|||
$c = new ThrowableController($t); |
|||
$f = $c->getFrames(); |
|||
} finally { |
|||
$this->assertSame(Error::class, $f[0]['class']); |
|||
} |
|||
|
|||
$f = false; |
|||
try { |
|||
throw new \Exception(message: 'Ook!', previous: new Error(message: 'Ook!', code: \E_ERROR, previous: new \Exception('Ook!'))); |
|||
} catch (\Throwable $t) { |
|||
$c = new ThrowableController($t); |
|||
$f = $c->getFrames(); |
|||
} finally { |
|||
$this->assertSame(\Exception::class, $f[0]['class']); |
|||
$this->assertSame(Error::class, $f[count($f) - 2]['class']); |
|||
} |
|||
|
|||
$f = false; |
|||
try { |
|||
call_user_func_array(function () { |
|||
throw new \Exception('Ook!'); |
|||
}, []); |
|||
} catch (\Throwable $t) { |
|||
$c = new ThrowableController($t); |
|||
$f = $c->getFrames(); |
|||
} finally { |
|||
$this->assertSame(\Exception::class, $f[0]['class']); |
|||
$this->assertArrayHasKey('file', $f[2]); |
|||
$this->assertMatchesRegularExpression('/TestThrowableController\.php$/', $f[2]['file']); |
|||
$this->assertSame('call_user_func_array', $f[2]['function']); |
|||
$this->assertArrayHasKey('line', $f[2]); |
|||
$this->assertNotSame(0, $f[2]['line']); |
|||
} |
|||
|
|||
// This is mostly here for code coverage: to delete userland error handling from |
|||
// the backtrace |
|||
$f = false; |
|||
try { |
|||
function ook() {} |
|||
call_user_func('ook', []); |
|||
} catch (\Throwable $t) { |
|||
$c = new ThrowableController($t); |
|||
$f = $c->getFrames(); |
|||
} finally { |
|||
$this->assertSame(\TypeError::class, $f[0]['class']); |
|||
} |
|||
|
|||
// For code coverage purposes; should use the cached value instead of calculating |
|||
// the frames over again. |
|||
$f = $c->getFrames(); |
|||
|
|||
// Lastly test for a RangeException |
|||
$this->expectException(\RangeException::class); |
|||
$c = new ThrowableController(new \Exception('Ook!')); |
|||
$c->getFrames(-1); |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
<?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\Handler; |
|||
|
|||
|
|||
class TestingHandler extends Handler { |
|||
protected ?string $_name = null; |
|||
|
|||
|
|||
protected function handleCallback(array $output): array { |
|||
$output['code'] = (\PHP_SAPI === 'cli') ? $output['code'] | self::NOW : $output['code']; |
|||
return $output; |
|||
} |
|||
|
|||
protected function invokeCallback(): void { |
|||
foreach ($this->outputBuffer as $o) { |
|||
if (($o['code'] & self::OUTPUT) === 0) { |
|||
continue; |
|||
} |
|||
|
|||
if ($o['code'] & self::LOG) { |
|||
$this->log($o['controller']->getThrowable(), json_encode([ |
|||
'class' => $o['class'], |
|||
'code' => $o['code'], |
|||
'file' => $o['file'], |
|||
'line' => $o['line'], |
|||
'message' => $o['message'] |
|||
])); |
|||
} |
|||
|
|||
//$this->print($this->serializeOutputThrowable($o)); |
|||
} |
|||
} |
|||
} |
@ -1,28 +0,0 @@ |
|||
<?xml version="1.0"?> |
|||
<phpunit |
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" |
|||
colors="true" |
|||
bootstrap="bootstrap.php" |
|||
convertErrorsToExceptions="false" |
|||
convertNoticesToExceptions="false" |
|||
convertWarningsToExceptions="false" |
|||
beStrictAboutTestsThatDoNotTestAnything="true" |
|||
forceCoversAnnotation="true" |
|||
stopOnError="false"> |
|||
<coverage processUncoveredFiles="true"> |
|||
<include> |
|||
<directory suffix=".php">../lib</directory> |
|||
</include> |
|||
</coverage> |
|||
<testsuites> |
|||
<testsuite name="Main"> |
|||
<file>cases/TestCatcher.php</file> |
|||
<file>cases/TestHandler.php</file> |
|||
<file>cases/TestHTMLHandler.php</file> |
|||
<file>cases/TestJSONHandler.php</file> |
|||
<file>cases/TestPlainTextHandler.php</file> |
|||
<file>cases/TestThrowableController.php</file> |
|||
</testsuite> |
|||
</testsuites> |
|||
</phpunit> |
@ -0,0 +1,21 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" |
|||
beStrictAboutOutputDuringTests="true" |
|||
beStrictAboutTestsThatDoNotTestAnything="true" |
|||
bootstrap="bootstrap.php" |
|||
cacheDirectory=".phpunit.cache" |
|||
colors="true" |
|||
executionOrder="defects" |
|||
requireCoverageMetadata="true" |
|||
> |
|||
<testsuites> |
|||
<testsuite name="Main"> |
|||
<directory prefix="Test" suffix=".php">./cases</directory> |
|||
</testsuite> |
|||
</testsuites> |
|||
<coverage> |
|||
<include> |
|||
<directory suffix=".php">../lib</directory> |
|||
</include> |
|||
</coverage> |
|||
</phpunit> |
Loading…
Reference in new issue