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 |
<?php |
||||
/** @license MIT |
|
||||
* Copyright 2017 , Dustin Wilson, J. King et al. |
|
||||
* See LICENSE and AUTHORS files for details */ |
|
||||
|
|
||||
declare(strict_types=1); |
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('zend.assertions', '1'); |
||||
ini_set('assert.exception', 'true'); |
ini_set('assert.exception', 'true'); |
||||
error_reporting(\E_ALL); |
error_reporting(\E_ALL); |
||||
|
define('CWD', dirname(__DIR__)); |
||||
$cwd = dirname(__DIR__); |
require_once CWD . '/vendor/autoload.php'; |
||||
require_once "$cwd/vendor/autoload.php"; |
|
||||
|
|
||||
if (function_exists('xdebug_set_filter')) { |
if (function_exists('xdebug_set_filter')) { |
||||
if (defined('XDEBUG_PATH_INCLUDE')) { |
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 { |
} 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 |
<?php |
||||
/** |
/** |
||||
* @license MIT |
* @license MIT |
||||
* Copyright 2022 Dustin Wilson, et al. |
* Copyright 2022 Dustin Wilson, et al. |
||||
* See LICENSE and AUTHORS files for details |
* See LICENSE and AUTHORS files for details |
||||
*/ |
*/ |
||||
|
|
||||
declare(strict_types=1); |
declare(strict_types=1); |
||||
namespace MensBeam\Catcher\Test; |
namespace MensBeam\Catcher\Test; |
||||
use MensBeam\Catcher; |
use MensBeam\Catcher, |
||||
|
Phake, |
||||
|
Phake\IMock; |
||||
use MensBeam\Catcher\{ |
use MensBeam\Catcher\{ |
||||
|
ArgumentCountError, |
||||
Error, |
Error, |
||||
HTMLHandler, |
PlainTextHandler, |
||||
JSONHandler, |
UnderflowException |
||||
PlainTextHandler |
|
||||
}; |
}; |
||||
use Eloquent\Phony\Phpunit\Phony; |
|
||||
|
|
||||
|
|
||||
|
/** @covers \MensBeam\Catcher */ |
||||
class TestCatcher extends \PHPUnit\Framework\TestCase { |
class TestCatcher extends \PHPUnit\Framework\TestCase { |
||||
/** |
protected ?Catcher $catcher = null; |
||||
* @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(); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* @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(); |
|
||||
} |
|
||||
|
|
||||
/** |
public function setUp(): void { |
||||
* @covers \MensBeam\Catcher::pushHandler |
if ($this->catcher !== null) { |
||||
* |
$this->catcher->unregister(); |
||||
* @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); |
|
||||
} |
} |
||||
} |
$this->catcher = new Catcher(); |
||||
|
$this->catcher->preventExit = true; |
||||
|
|
||||
/** |
// Do this instead of specifying the option in the constructor for coverage |
||||
* @covers \MensBeam\Catcher::popHandler |
// purposes... |
||||
* |
$handlers = $this->catcher->getHandlers(); |
||||
* @covers \MensBeam\Catcher::__construct |
$handlers[0]->setOption('silent', true); |
||||
* @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); |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
/** |
public function tearDown(): void { |
||||
* @covers \MensBeam\Catcher::isRegistered |
$this->catcher->unregister(); |
||||
* |
$this->catcher = null; |
||||
* @covers \MensBeam\Catcher::__construct |
error_reporting(\E_ALL); |
||||
* @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()); |
|
||||
} |
} |
||||
|
|
||||
/** |
|
||||
* @covers \MensBeam\Catcher::setHandlers |
public function testConstructor(): void { |
||||
* |
$h = $this->catcher->getHandlers(); |
||||
* @covers \MensBeam\Catcher::__construct |
$this->assertEquals(1, count($h)); |
||||
* @covers \MensBeam\Catcher::getHandlers |
$this->assertInstanceOf(PlainTextHandler::class, $h[0]); |
||||
* @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(); |
|
||||
} |
} |
||||
|
|
||||
/** |
/** @dataProvider provideErrorHandlingTests */ |
||||
* @covers \MensBeam\Catcher::shiftHandler |
public function testErrorHandling(int $code): void { |
||||
* |
$t = null; |
||||
* @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; |
|
||||
try { |
try { |
||||
$c->shiftHandler(); |
trigger_error('Ook!', $code); |
||||
} catch (\Throwable $t) { |
} catch (\Throwable $t) {} finally { |
||||
$e = $t::class; |
$t = ($t === null) ? $this->catcher->getLastThrowable() : $t; |
||||
} finally { |
$this->assertSame(Error::class, $t::class); |
||||
$this->assertSame(\Exception::class, $e); |
$this->assertSame($code, $t->getCode()); |
||||
|
$this->assertSame($t, $this->catcher->getLastThrowable()); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
/** |
public function testExit(): void { |
||||
* @covers \MensBeam\Catcher::unregister |
$this->catcher->unregister(); |
||||
* |
$h = Phake::partialMock(TestingHandler::class); |
||||
* @covers \MensBeam\Catcher::__construct |
$this->catcher = $m = Phake::partialMock(Catcher::class, $h); |
||||
* @covers \MensBeam\Catcher::pushHandler |
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; |
||||
* @covers \MensBeam\Catcher::register |
Phake::when($m)->exit->thenReturn(null); |
||||
* @covers \MensBeam\Catcher\Handler::__construct |
Phake::when($m)->handleShutdown()->thenReturn(null); |
||||
*/ |
|
||||
public function testMethod_unregister(): void { |
|
||||
$c = new Catcher(); |
|
||||
$c->preventExit = true; |
|
||||
$c->throwErrors = false; |
|
||||
$c->unregister(); |
|
||||
$this->assertFalse($c->unregister()); |
|
||||
} |
|
||||
|
|
||||
/** |
trigger_error('Ook!', \E_USER_ERROR); |
||||
* @covers \MensBeam\Catcher::unshiftHandler |
|
||||
* |
Phake::verify($h, Phake::times(1))->invokeCallback(); |
||||
* @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); |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
/** |
public function testHandlerBubbling(): void { |
||||
* @covers \MensBeam\Catcher::handleError |
$this->catcher->unregister(); |
||||
* |
|
||||
* @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()); |
|
||||
|
|
||||
trigger_error('Ook!', \E_USER_ERROR); |
$h1 = Phake::partialMock(TestingHandler::class, [ 'bubbles' => false ]); |
||||
$t = $c->getLastThrowable(); |
$h2 = Phake::partialMock(TestingHandler::class); |
||||
$this->assertSame(Error::class, $t::class); |
$this->catcher = $m = Phake::partialMock(Catcher::class, $h1, $h2); |
||||
$this->assertSame(\E_USER_ERROR, $t->getCode()); |
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; |
||||
|
$m->preventExit = true; |
||||
|
|
||||
$er = error_reporting(); |
|
||||
error_reporting(0); |
|
||||
trigger_error('Ook!', \E_USER_ERROR); |
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 ] ]); |
trigger_error('Ook', \E_USER_ERROR); |
||||
$h2 = Phony::partialMock(HTMLHandler::class, [ [ 'silent' => true ] ]); |
$this->assertSame(Error::class, $this->catcher->getLastThrowable()::class); |
||||
$h3 = Phony::partialMock(JSONHandler::class, [ [ 'silent' => true ] ]); |
} |
||||
|
|
||||
$h = Phony::partialMock(Catcher::class, [ |
public function testRegistration(): void { |
||||
$h1->get(), |
$this->assertTrue($this->catcher->isRegistered()); |
||||
$h2->get(), |
$this->assertFalse($this->catcher->register()); |
||||
$h3->get() |
$this->assertTrue($this->catcher->unregister()); |
||||
]); |
$this->assertFalse($this->catcher->unregister()); |
||||
$c = $h->get(); |
$this->assertFalse($this->catcher->isRegistered()); |
||||
$c->preventExit = true; |
} |
||||
$c->throwErrors = false; |
|
||||
|
|
||||
trigger_error('Ook!', \E_USER_ERROR); |
/** @dataProvider provideShutdownTests */ |
||||
|
public function testShutdownHandling(\Closure $closure): void { |
||||
|
$this->catcher->unregister(); |
||||
|
|
||||
$h1->dispatch->called(); |
$h1 = Phake::partialMock(TestingHandler::class); |
||||
$h2->dispatch->called(); |
$this->catcher = $m = Phake::partialMock(Catcher::class, $h1); |
||||
$h3->dispatch->called(); |
$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 { |
try { |
||||
trigger_error('Ook!', \E_USER_WARNING); |
trigger_error('Ook!', \E_USER_WARNING); |
||||
} catch (\Throwable $t) { |
} catch (\Throwable $t) {} finally { |
||||
$this->assertInstanceOf(Error::class, $t); |
$this->assertNull($t); |
||||
$this->assertSame(\E_USER_WARNING, $t->getCode()); |
$this->assertNull($this->catcher->getLastThrowable()); |
||||
} |
} |
||||
|
} |
||||
|
|
||||
try { |
|
||||
trigger_error('Ook!', \E_USER_ERROR); |
/** @dataProvider provideFatalErrorTests */ |
||||
} catch (\Throwable $t) { |
public function testFatalErrors(string $throwableClassName, \Closure $closure): void { |
||||
$this->assertInstanceOf(Error::class, $t); |
$this->expectException($throwableClassName); |
||||
$this->assertSame(\E_USER_ERROR, $t->getCode()); |
$closure($this->catcher); |
||||
} |
|
||||
|
|
||||
$c->unregister(); |
|
||||
$c->throwErrors = false; |
|
||||
} |
} |
||||
|
|
||||
/** |
|
||||
* @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 ]) ]); |
public static function provideFatalErrorTests(): iterable { |
||||
$h->exit->returns(); |
$iterable = [ |
||||
$c = $h->get(); |
[ |
||||
$c->preventExit = false; |
UnderflowException::class, |
||||
$c->throwErrors = false; |
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); |
foreach ($iterable as $i) { |
||||
$t = $c->getLastThrowable(); |
yield $i; |
||||
$this->assertSame(Error::class, $t::class); |
} |
||||
$this->assertSame(\E_USER_ERROR, $t->getCode()); |
|
||||
$c->unregister(); |
|
||||
} |
} |
||||
|
|
||||
/** |
public static function provideErrorHandlingTests(): iterable { |
||||
* @covers \MensBeam\Catcher::handleShutdown |
foreach ([ \E_USER_NOTICE, \E_USER_DEPRECATED, \E_USER_WARNING, \E_USER_ERROR ] as $i) { |
||||
* |
yield [ $i ]; |
||||
* @covers \MensBeam\Catcher::__construct |
} |
||||
* @covers \MensBeam\Catcher::getLastError |
} |
||||
* @covers \MensBeam\Catcher::handleError |
|
||||
* @covers \MensBeam\Catcher::isErrorFatal |
public static function provideShutdownTests(): iterable { |
||||
* @covers \MensBeam\Catcher::handleThrowable |
$iterable = [ |
||||
* @covers \MensBeam\Catcher::pushHandler |
[ |
||||
* @covers \MensBeam\Catcher::register |
function (IMock $m, IMock $h): void { |
||||
* @covers \MensBeam\Catcher::unregister |
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; |
||||
* @covers \MensBeam\Catcher\Error::__construct |
Phake::when($m)->getLastError()->thenReturn([ |
||||
* @covers \MensBeam\Catcher\Handler::__construct |
'type' => \E_ERROR, |
||||
* @covers \MensBeam\Catcher\Handler::buildOutputArray |
'message' => 'Ook!', |
||||
* @covers \MensBeam\Catcher\Handler::dispatch |
'file' => '/dev/null', |
||||
* @covers \MensBeam\Catcher\Handler::handle |
'line' => 2112 |
||||
* @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback |
]); |
||||
* @covers \MensBeam\Catcher\PlainTextHandler::handleCallback |
$m->handleShutdown(); |
||||
* @covers \MensBeam\Catcher\ThrowableController::__construct |
Phake::verify($m)->getLastError(); |
||||
* @covers \MensBeam\Catcher\ThrowableController::getPrevious |
Phake::verify($m)->handleError(\E_ERROR, 'Ook!', '/dev/null', 2112); |
||||
* @covers \MensBeam\Catcher\ThrowableController::getThrowable |
Phake::verify($h, Phake::times(1))->invokeCallback(); |
||||
*/ |
} |
||||
public function testMethod_handleShutdown(): void { |
], |
||||
$c = new Catcher(); |
[ |
||||
$c->preventExit = true; |
function (IMock $m, IMock $h): void { |
||||
$c->throwErrors = false; |
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; |
||||
$c->handleShutdown(); |
Phake::when($m)->getLastError()->thenReturn([ |
||||
$p = new \ReflectionProperty($c, 'isShuttingDown'); |
'type' => \E_USER_ERROR, |
||||
$p->setAccessible(true); |
'message' => 'Ook!', |
||||
$this->assertTrue($p->getValue($c)); |
'file' => '/dev/null', |
||||
$c->unregister(); |
'line' => 2112 |
||||
|
]); |
||||
$c = new Catcher(); |
$m->handleShutdown(); |
||||
$c->preventExit = true; |
Phake::verify($m)->getLastError(); |
||||
$c->throwErrors = false; |
Phake::verify($m)->handleError(\E_USER_ERROR, 'Ook!', '/dev/null', 2112); |
||||
$c->unregister(); |
Phake::verify($h, Phake::times(1))->invokeCallback(); |
||||
$c->handleShutdown(); |
} |
||||
$p = new \ReflectionProperty($c, 'isShuttingDown'); |
], |
||||
$p->setAccessible(true); |
[ |
||||
$this->assertFalse($p->getValue($c)); |
function (IMock $m, IMock $h): void { |
||||
$c->unregister(); |
$m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; |
||||
|
Phake::when($m)->getLastError()->thenReturn([ |
||||
$h = Phony::partialMock(Catcher::class, [ new PlainTextHandler([ 'silent' => true ]) ]); |
'type' => \E_USER_ERROR, |
||||
$h->getLastError->returns([ |
'message' => 'Ook!', |
||||
'type' => \E_ERROR, |
'file' => '/dev/null', |
||||
'message' => 'Ook!', |
'line' => 2112 |
||||
'file' => '/dev/null', |
]); |
||||
'line' => 2112 |
$m->handleShutdown(); |
||||
]); |
Phake::verify($m)->getLastError(); |
||||
$c = $h->get(); |
Phake::verify($m)->handleError(\E_USER_ERROR, 'Ook!', '/dev/null', 2112); |
||||
$c->handleShutdown(); |
Phake::verify($h, Phake::times(1))->invokeCallback(); |
||||
$h->handleError->called(); |
} |
||||
$h->handleThrowable->called(); |
], |
||||
|
[ |
||||
|
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