HTMLHandler 100% coverage
This commit is contained in:
parent
b8243619d6
commit
d2f358139f
13 changed files with 473 additions and 100 deletions
|
@ -22,6 +22,7 @@
|
|||
"ext-dom": "For HTMLHandler"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-dom": "*",
|
||||
"mensbeam/html-dom": "^1.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"nikic/php-parser": "^4.15",
|
||||
|
|
76
composer.lock
generated
76
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "276b1ae35ddd23e0ebc736f079ecf98f",
|
||||
"content-hash": "a72b4696660d27f574ba5142eb76d7dd",
|
||||
"packages": [
|
||||
{
|
||||
"name": "psr/log",
|
||||
|
@ -2368,6 +2368,80 @@
|
|||
],
|
||||
"time": "2022-05-10T07:21:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-exporter",
|
||||
"version": "v6.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-exporter.git",
|
||||
"reference": "8a3f442d48567a5447e984ce9e86875ed768304a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/8a3f442d48567a5447e984ce9e86875ed768304a",
|
||||
"reference": "8a3f442d48567a5447e984ce9e86875ed768304a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/var-dumper": "^5.4|^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\VarExporter\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Allows exporting any serializable PHP data structure to plain PHP code",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"clone",
|
||||
"construct",
|
||||
"export",
|
||||
"hydrate",
|
||||
"instantiate",
|
||||
"lazy loading",
|
||||
"proxy",
|
||||
"serialize"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-exporter/tree/v6.2.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-03T22:32:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
"version": "1.2.1",
|
||||
|
|
|
@ -17,7 +17,9 @@ use MensBeam\Foundation\Catcher\{
|
|||
|
||||
class Catcher {
|
||||
/** When set to true Catcher won't exit when instructed */
|
||||
public static $preventExit = false;
|
||||
public bool $preventExit = false;
|
||||
/** When set to true Catcher will throw errors as throwables */
|
||||
public bool $throwErrors = true;
|
||||
|
||||
/**
|
||||
* Array of handlers the exceptions are passed to
|
||||
|
@ -154,12 +156,18 @@ class Catcher {
|
|||
*/
|
||||
public function handleError(int $code, string $message, ?string $file = null, ?int $line = null): bool {
|
||||
if ($code !== 0 && error_reporting()) {
|
||||
$this->handleThrowable(new Error($message, $code, $file, $line));
|
||||
$error = new Error($message, $code, $file, $line);
|
||||
if ($this->throwErrors) {
|
||||
throw $error;
|
||||
} else {
|
||||
$this->handleThrowable($error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If preventing exit we don't want a false here to halt processing
|
||||
return (self::$preventExit);
|
||||
return ($this->preventExit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,7 +204,7 @@ class Catcher {
|
|||
|
||||
// Don't want to exit here when shutting down so any shutdown functions further
|
||||
// down the stack still run.
|
||||
if (!self::$preventExit && !$this->isShuttingDown) {
|
||||
if (!$this->preventExit && !$this->isShuttingDown) {
|
||||
$this->exit($throwable->getCode());
|
||||
}
|
||||
}
|
||||
|
@ -214,6 +222,7 @@ class Catcher {
|
|||
return;
|
||||
}
|
||||
|
||||
$this->throwErrors = false;
|
||||
$this->isShuttingDown = true;
|
||||
if ($error = $this->getLastError()) {
|
||||
if (in_array($error['type'], [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_CORE_WARNING, \E_COMPILE_ERROR, \E_COMPILE_WARNING ])) {
|
||||
|
|
|
@ -12,14 +12,21 @@ namespace MensBeam\Foundation\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';
|
||||
/** If true the handler will output times to the output; defaults to true */
|
||||
protected bool $_outputTime = true;
|
||||
/** 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);
|
||||
|
@ -34,48 +41,50 @@ class HTMLHandler extends Handler {
|
|||
</html>
|
||||
HTML);
|
||||
}
|
||||
|
||||
$this->xpath = new \DOMXPath($this->_document);
|
||||
$location = $this->xpath->query($this->_errorPath);
|
||||
if (count($location) === 0 || !$location->item(0) instanceof \DOMElement) {
|
||||
throw new \InvalidArgumentException('Option "errorPath" must correspond to a location that is an instance of \DOMElement');
|
||||
}
|
||||
$this->errorLocation = $location->item(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected function buildThrowable(ThrowableController $controller): \DOMElement {
|
||||
protected function buildThrowable(ThrowableController $controller): \DOMDocumentFragment {
|
||||
$throwable = $controller->getThrowable();
|
||||
$p = $this->_document->createElement('p');
|
||||
$frag = $this->_document->createDocumentFragment();
|
||||
|
||||
$b = $this->_document->createElement('b');
|
||||
$type = $controller->getErrorType();
|
||||
$class = $throwable::class;
|
||||
if ($throwable instanceof \Error) {
|
||||
$type = $controller->getErrorType();
|
||||
if ($type !== null) {
|
||||
$b = $this->_document->createElement('b');
|
||||
$b->appendChild($this->_document->createTextNode($type));
|
||||
$p->appendChild($b);
|
||||
$p->appendChild($this->_document->createTextNode(' ('));
|
||||
$code = $this->_document->createElement('code');
|
||||
$code->appendChild($this->_document->createTextNode($throwable::class));
|
||||
$p->appendChild($code);
|
||||
$p->appendChild($this->_document->createTextNode(')'));
|
||||
} else {
|
||||
$code = $this->_document->createElement('code');
|
||||
$code->appendChild($this->_document->createTextNode($throwable::class));
|
||||
$p->appendChild($code);
|
||||
}
|
||||
$b->appendChild($this->_document->createTextNode($type ?? $class));
|
||||
if ($type !== null) {
|
||||
$b->firstChild->textContent .= ' ';
|
||||
$code = $this->_document->createElement('code');
|
||||
$code->appendChild($this->_document->createTextNode("($class)"));
|
||||
$b->appendChild($code);
|
||||
}
|
||||
$frag->appendChild($b);
|
||||
|
||||
$p->appendChild($this->_document->createTextNode(': '));
|
||||
$frag->appendChild($this->_document->createTextNode(': '));
|
||||
$i = $this->_document->createElement('i');
|
||||
$i->appendChild($this->_document->createTextNode($throwable->getMessage()));
|
||||
$p->appendChild($i);
|
||||
$p->appendChild($this->_document->createTextNode(' in file '));
|
||||
$frag->appendChild($i);
|
||||
$frag->appendChild($this->_document->createTextNode(' in file '));
|
||||
$code = $this->_document->createElement('code');
|
||||
$code->appendChild($this->_document->createTextNode($throwable->getFile()));
|
||||
$p->appendChild($code);
|
||||
$p->appendChild($this->_document->createTextNode(' on line ' . $throwable->getLine()));
|
||||
return $p;
|
||||
$frag->appendChild($code);
|
||||
$frag->appendChild($this->_document->createTextNode(' on line ' . $throwable->getLine()));
|
||||
return $frag;
|
||||
}
|
||||
|
||||
protected function dispatchCallback(): void {
|
||||
$body = $this->_document->getElementsByTagName('body')[0];
|
||||
$ul = $this->_document->createElement('ul');
|
||||
$this->errorLocation->appendChild($ul);
|
||||
|
||||
$allSilent = true;
|
||||
foreach ($this->outputBuffer as $o) {
|
||||
if ($o->outputCode & self::SILENT) {
|
||||
|
@ -83,7 +92,9 @@ class HTMLHandler extends Handler {
|
|||
}
|
||||
|
||||
$allSilent = false;
|
||||
$body->appendChild($o->output);
|
||||
$li = $this->_document->createElement('li');
|
||||
$li->appendChild($o->output);
|
||||
$ul->appendChild($li);
|
||||
}
|
||||
|
||||
if (!$allSilent) {
|
||||
|
@ -97,81 +108,102 @@ class HTMLHandler extends Handler {
|
|||
if ($this->_outputTime && $this->_timeFormat !== '') {
|
||||
$p = $this->_document->createElement('p');
|
||||
$time = $this->_document->createElement('time');
|
||||
$time->appendChild($this->_document->createTextNode((new \DateTime())->format($this->_timeFormat)));
|
||||
$now = new \DateTimeImmutable();
|
||||
$tz = $now->getTimezone()->getName();
|
||||
if ($tz !== 'UTC' || !in_array($this->_timeFormat, [ 'c', 'Y-m-d\TH:i:sO', 'Y-m-d\TH:i:sP', 'Y-m-d\TH:i:s\Z' ])) {
|
||||
$n = ($tz !== 'UTC') ? $now->setTimezone(new \DateTimeZone('UTC')) : $now;
|
||||
$time->setAttribute('datetime', $n->format('Y-m-d\TH:i:s\Z'));
|
||||
}
|
||||
$time->appendChild($this->_document->createTextNode($now->format($this->_timeFormat)));
|
||||
$p->appendChild($time);
|
||||
$frag->appendChild($p);
|
||||
|
||||
$ip = $this->_document->createElement('div');
|
||||
$frag->appendChild($ip);
|
||||
} else {
|
||||
$ip = $frag;
|
||||
}
|
||||
|
||||
$frag->appendChild($this->buildThrowable($controller));
|
||||
$p = $this->_document->createElement('p');
|
||||
$p->appendChild($this->buildThrowable($controller));
|
||||
$ip->appendChild($p);
|
||||
|
||||
if ($this->_outputPrevious) {
|
||||
$prevController = $controller->getPrevious();
|
||||
while ($prevController) {
|
||||
$p = $this->_document->createElement('p');
|
||||
$small = $this->_document->createElement('small');
|
||||
$small->appendChild($this->_document->createTextNode('Caused by ↴'));
|
||||
$p->appendChild($small);
|
||||
$frag->appendChild($p);
|
||||
$frag->appendChild($this->buildThrowable($prevController));
|
||||
$prevController = $prevController->getPrevious();
|
||||
$prev = $controller->getPrevious();
|
||||
if ($prev !== null) {
|
||||
$ul = $this->_document->createElement('ul');
|
||||
$ip->appendChild($ul);
|
||||
$f = null;
|
||||
while ($prev) {
|
||||
if ($f !== null) {
|
||||
$p = $this->_document->createElement('p');
|
||||
$p->appendChild($f);
|
||||
$li->appendChild($p);
|
||||
$ul = $this->_document->createElement('ul');
|
||||
$li->appendChild($ul);
|
||||
}
|
||||
|
||||
$li = $this->_document->createElement('li');
|
||||
$ul->appendChild($li);
|
||||
$f = $this->_document->createDocumentFragment();
|
||||
$span = $this->_document->createElement('span');
|
||||
$span->appendChild($this->_document->createTextNode('Caused by:'));
|
||||
$f->appendChild($span);
|
||||
$f->appendChild($this->_document->createTextNode(' '));
|
||||
$f->appendChild($this->buildThrowable($prev));
|
||||
|
||||
$prev = $prev->getPrevious();
|
||||
}
|
||||
|
||||
$li->appendChild($f);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_outputBacktrace) {
|
||||
$frames = $controller->getFrames();
|
||||
$p = $this->_document->createElement('p');
|
||||
$p->appendChild($this->_document->createTextNode('Stack trace:'));
|
||||
$frag->appendChild($p);
|
||||
|
||||
if (count($frames) > 0) {
|
||||
$p = $this->_document->createElement('p');
|
||||
$p->appendChild($this->_document->createTextNode('Stack trace:'));
|
||||
$ip->appendChild($p);
|
||||
|
||||
$ol = $this->_document->createElement('ol');
|
||||
$frag->appendChild($ol);
|
||||
$num = 1;
|
||||
$ip->appendChild($ol);
|
||||
|
||||
$num = 0;
|
||||
foreach ($frames as $frame) {
|
||||
$li = $this->_document->createElement('li');
|
||||
$args = (!empty($frame['args']) && $this->_backtraceArgFrameLimit >= $num);
|
||||
$t = ($args) ? $this->_document->createElement('p') : $li;
|
||||
$ol->appendChild($li);
|
||||
|
||||
if (!empty($frame['error'])) {
|
||||
$b = $this->_document->createElement('b');
|
||||
$b->appendChild($this->_document->createTextNode($frame['error']));
|
||||
$t->appendChild($b);
|
||||
$t->appendChild($this->_document->createTextNode(' ('));
|
||||
$code = $this->_document->createElement('code');
|
||||
$code->appendChild($this->_document->createTextNode($frame['class']));
|
||||
$t->appendChild($code);
|
||||
$t->appendChild($this->_document->createTextNode(')'));
|
||||
} elseif (!empty($frame['class'])) {
|
||||
$code = $this->_document->createElement('code');
|
||||
$code->appendChild($this->_document->createTextNode($frame['class']));
|
||||
$t->appendChild($code);
|
||||
}
|
||||
|
||||
$class = $frame['class'] ?? '';
|
||||
$function = $frame['function'] ?? '';
|
||||
if ($function) {
|
||||
if ($class) {
|
||||
$code->firstChild->textContent .= "::{$function}()";
|
||||
} else {
|
||||
$code = $this->_document->createElement('code');
|
||||
$code->appendChild($this->_document->createTextNode("{$function}()"));
|
||||
$t->appendChild($code);
|
||||
}
|
||||
}
|
||||
|
||||
$t->appendChild($this->_document->createTextNode(' '));
|
||||
$i = $this->_document->createElement('i');
|
||||
$i->appendChild($this->_document->createTextNode($frame['file']));
|
||||
$t->appendChild($i);
|
||||
$t->appendChild($this->_document->createTextNode(":{$frame['line']}"));
|
||||
|
||||
$args = (isset($frame['args']) && $this->_backtraceArgFrameLimit >= ++$num);
|
||||
if ($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);
|
||||
|
||||
$text = $frame['error'] ?? $frame['class'] ?? '';
|
||||
if (isset($frame['function'])) {
|
||||
$text = ((isset($frame['class'])) ? '::' : '') . "{$frame['function']}()";
|
||||
}
|
||||
$code->appendChild($this->_document->createTextNode($text));
|
||||
|
||||
$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 ($args) {
|
||||
$pre = $this->_document->createElement('pre');
|
||||
$pre->appendChild($this->_document->createTextNode(var_export($frame['args'], true)));
|
||||
$pre->appendChild($this->_document->createTextNode(print_r($frame['args'], true)));
|
||||
$li->appendChild($pre);
|
||||
}
|
||||
|
||||
$ol->appendChild($li);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,12 +99,33 @@ abstract class Handler {
|
|||
$this->outputBuffer = [];
|
||||
}
|
||||
|
||||
public function getOption(string $name): mixed {
|
||||
$class = get_class($this);
|
||||
if (!property_exists($class, "_$name")) {
|
||||
trigger_error(sprintf('Undefined option in %s: %s', $class, $name), \E_USER_WARNING);
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = "_$name";
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
public function handle(ThrowableController $controller): HandlerOutput {
|
||||
$output = $this->handleCallback($controller);
|
||||
$this->outputBuffer[] = $output;
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function setOption(string $name, mixed $value): void {
|
||||
$class = get_class($this);
|
||||
if (!property_exists($class, "_$name")) {
|
||||
trigger_error(sprintf('Undefined option in %s: %s', $class, $name), \E_USER_WARNING);
|
||||
}
|
||||
|
||||
$name = "_$name";
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
|
||||
abstract protected function dispatchCallback(): void;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
namespace MensBeam\Foundation\Catcher;
|
||||
use \Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
||||
class PlainTextHandler extends Handler {
|
||||
|
@ -37,15 +37,17 @@ class PlainTextHandler extends Handler {
|
|||
$output = $this->serializeThrowable($controller);
|
||||
if ($this->_outputPrevious) {
|
||||
$prevController = $controller->getPrevious();
|
||||
$indent = '';
|
||||
while ($prevController) {
|
||||
$output .= sprintf("\n\nCaused by ↴\n%s", $this->serializeThrowable($prevController));
|
||||
$output .= sprintf("\n%s↳ %s", $indent, $this->serializeThrowable($prevController));
|
||||
$prevController = $prevController->getPrevious();
|
||||
$indent .= ' ';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_outputBacktrace) {
|
||||
$frames = $controller->getFrames();
|
||||
$output .= "\nStack trace:";
|
||||
$output .= "\n\nStack trace:";
|
||||
|
||||
$num = 1;
|
||||
$maxDigits = strlen((string)count($frames));
|
||||
|
@ -75,6 +77,8 @@ class PlainTextHandler extends Handler {
|
|||
$args
|
||||
);
|
||||
}
|
||||
|
||||
$output = rtrim($output, "\n");
|
||||
}
|
||||
|
||||
// The logger will handle timestamps itself.
|
||||
|
|
|
@ -22,6 +22,4 @@ if (function_exists('xdebug_set_filter')) {
|
|||
} else {
|
||||
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, [ "$cwd/lib/" ]);
|
||||
}
|
||||
}
|
||||
|
||||
Catcher::$preventExit = true;
|
||||
}
|
|
@ -16,7 +16,10 @@ use MensBeam\Foundation\Catcher\{
|
|||
};
|
||||
use Eloquent\Phony\Phpunit\Phony;
|
||||
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class TestCatcher extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @covers \MensBeam\Foundation\Catcher::__construct
|
||||
|
@ -30,6 +33,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
public function testMethod___construct(): void {
|
||||
$c = new Catcher();
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
$this->assertSame('MensBeam\Foundation\Catcher', $c::class);
|
||||
$this->assertSame(1, count($c->getHandlers()));
|
||||
$this->assertSame(PlainTextHandler::class, $c->getHandlers()[0]::class);
|
||||
|
@ -40,6 +45,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
new HTMLHandler(),
|
||||
new JSONHandler()
|
||||
);
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
$this->assertSame('MensBeam\Foundation\Catcher', $c::class);
|
||||
$this->assertSame(3, count($c->getHandlers()));
|
||||
$h = $c->getHandlers();
|
||||
|
@ -74,6 +81,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
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();
|
||||
|
@ -95,11 +104,15 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
|
||||
$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);
|
||||
|
@ -136,6 +149,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
new JSONHandler()
|
||||
];
|
||||
$c = new Catcher(...$h);
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
$hh = $c->popHandler();
|
||||
$this->assertSame($h[2], $hh);
|
||||
$hh = $c->popHandler();
|
||||
|
@ -163,6 +178,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
public function testMethod_register(): void {
|
||||
$c = new Catcher();
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
$this->assertTrue($c->isRegistered());
|
||||
$this->assertFalse($c->register());
|
||||
$c->unregister();
|
||||
|
@ -181,6 +198,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
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));
|
||||
|
@ -205,6 +224,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
new JSONHandler()
|
||||
];
|
||||
$c = new Catcher(...$h);
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
$c->unregister();
|
||||
$hh = $c->shiftHandler();
|
||||
$this->assertSame($h[0], $hh);
|
||||
|
@ -231,6 +252,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
public function testMethod_unregister(): void {
|
||||
$c = new Catcher();
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
$c->unregister();
|
||||
$this->assertFalse($c->unregister());
|
||||
}
|
||||
|
@ -248,6 +271,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
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));
|
||||
|
@ -272,6 +297,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
$c->unregister();
|
||||
|
||||
$c = new Catcher();
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
$c->unregister();
|
||||
|
||||
$e = null;
|
||||
|
@ -310,6 +337,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
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();
|
||||
|
@ -348,6 +377,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
$h3->get()
|
||||
]);
|
||||
$c = $h->get();
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
|
||||
trigger_error('Ook!', \E_USER_ERROR);
|
||||
|
||||
|
@ -355,7 +386,16 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
$h2->dispatch->called();
|
||||
$h3->dispatch->called();
|
||||
|
||||
$c->throwErrors = true;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -383,22 +423,25 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
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();
|
||||
|
||||
Catcher::$preventExit = false;
|
||||
$h = Phony::partialMock(Catcher::class, [ new PlainTextHandler([ 'silent' => true ]) ]);
|
||||
$h->exit->returns();
|
||||
$c = $h->get();
|
||||
$c->preventExit = false;
|
||||
$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();
|
||||
Catcher::$preventExit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -427,6 +470,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
public function testMethod_handleShutdown(): void {
|
||||
$c = new Catcher();
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
$c->handleShutdown();
|
||||
$p = new \ReflectionProperty($c, 'isShuttingDown');
|
||||
$p->setAccessible(true);
|
||||
|
@ -434,6 +479,8 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
|
|||
$c->unregister();
|
||||
|
||||
$c = new Catcher();
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
$c->unregister();
|
||||
$c->handleShutdown();
|
||||
$p = new \ReflectionProperty($c, 'isShuttingDown');
|
||||
|
|
97
tests/cases/TestHTMLHandler.php
Normal file
97
tests/cases/TestHTMLHandler.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
/**
|
||||
* @license MIT
|
||||
* Copyright 2022 Dustin Wilson, et al.
|
||||
* See LICENSE and AUTHORS files for details
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace MensBeam\Foundation\Catcher\Test;
|
||||
use MensBeam\Foundation\Catcher;
|
||||
use MensBeam\Foundation\Catcher\{
|
||||
Error,
|
||||
Handler,
|
||||
HTMLHandler,
|
||||
ThrowableController
|
||||
};
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class TestHTMLHandler extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::__construct
|
||||
*
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
|
||||
*/
|
||||
public function testMethod___construct__exception(): void {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
new HTMLHandler([ 'errorPath' => '/html/body/fail' ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::buildThrowable
|
||||
*
|
||||
* @covers \MensBeam\Foundation\Catcher\Error::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::handle
|
||||
* @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::handleCallback
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getFrames
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
|
||||
*/
|
||||
public function testMethod_buildThrowable(): 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([
|
||||
'outputBacktrace' => true,
|
||||
'outputTime' => false
|
||||
]);
|
||||
$o = $h->handle($c);
|
||||
$this->assertSame(Handler::CONTINUE, $o->controlCode);
|
||||
$this->assertInstanceOf(\DOMDocumentFragment::class, $o->output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::dispatchCallback
|
||||
*
|
||||
* @covers \MensBeam\Foundation\Catcher\Error::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::handle
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::print
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::setOption
|
||||
* @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::buildThrowable
|
||||
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::handleCallback
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
|
||||
*/
|
||||
public function testMethod_dispatchCallback(): 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([
|
||||
'outputToStderr' => false
|
||||
]);
|
||||
$h->handle($c);
|
||||
|
||||
ob_start();
|
||||
$h->dispatch();
|
||||
$o = ob_get_clean();
|
||||
$this->assertNotNull($o);
|
||||
|
||||
$h->setOption('silent', true);
|
||||
$h->handle($c);
|
||||
$h->dispatch();
|
||||
}
|
||||
}
|
|
@ -14,15 +14,19 @@ use MensBeam\Foundation\Catcher\{
|
|||
JSONHandler,
|
||||
PlainTextHandler
|
||||
};
|
||||
use Eloquent\Phony\Phpunit\Phony;
|
||||
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class TestHandler extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
|
||||
*/
|
||||
public function testMethod___construct__exception(): void {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$c = new Catcher(new PlainTextHandler([ 'httpCode' => 42 ]));
|
||||
new PlainTextHandler([ 'httpCode' => 42 ]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,11 +55,69 @@ class TestHandler extends \PHPUnit\Framework\TestCase {
|
|||
public function testMethod__getControlCode(): void {
|
||||
// Just need to test forceExit for coverage purposes
|
||||
$c = new Catcher(new PlainTextHandler([ 'forceExit' => true, 'silent' => true ]));
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
trigger_error('Ook!', \E_USER_ERROR);
|
||||
$this->assertSame(\E_USER_ERROR, $c->getLastThrowable()->getCode());
|
||||
$c->unregister();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::getOption
|
||||
*
|
||||
* @covers \MensBeam\Foundation\Catcher::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher::handleError
|
||||
* @covers \MensBeam\Foundation\Catcher::handleThrowable
|
||||
* @covers \MensBeam\Foundation\Catcher::pushHandler
|
||||
* @covers \MensBeam\Foundation\Catcher::register
|
||||
* @covers \MensBeam\Foundation\Catcher::unregister
|
||||
* @covers \MensBeam\Foundation\Catcher\Error::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::handle
|
||||
* @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
|
||||
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
|
||||
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
|
||||
* @covers \MensBeam\Foundation\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();
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
//$h = Phony::partialMock(PlainTextHandler::class, [ [ 'silent' => true ] ]);
|
||||
$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\Foundation\Catcher\Handler::getOutputCode
|
||||
*
|
||||
|
@ -82,6 +144,8 @@ class TestHandler extends \PHPUnit\Framework\TestCase {
|
|||
public function testMethod__getOutputCode(): void {
|
||||
// Just need to test forceOutputNow for coverage purposes
|
||||
$c = new Catcher(new PlainTextHandler([ 'forceOutputNow' => true, 'silent' => true ]));
|
||||
$c->preventExit = true;
|
||||
$c->throwErrors = false;
|
||||
trigger_error('Ook!', \E_USER_ERROR);
|
||||
$this->assertSame(\E_USER_ERROR, $c->getLastThrowable()->getCode());
|
||||
$c->unregister();
|
||||
|
@ -114,6 +178,8 @@ class TestHandler extends \PHPUnit\Framework\TestCase {
|
|||
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();
|
||||
|
|
|
@ -17,7 +17,10 @@ use MensBeam\Foundation\Catcher\{
|
|||
use Eloquent\Phony\Phpunit\Phony,
|
||||
Psr\Log\LoggerInterface;
|
||||
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
|
||||
|
@ -45,13 +48,26 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
|
|||
$o = $h->handle($c);
|
||||
$this->assertSame(Handler::CONTINUE, $o->controlCode);
|
||||
$this->assertSame(Handler::OUTPUT | Handler::NOW, $o->outputCode);
|
||||
$this->assertStringContainsString('Caused by ↴', $o->output);
|
||||
$this->assertStringContainsString('↳', $o->output);
|
||||
$l->critical->called();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::log
|
||||
*
|
||||
* @covers \MensBeam\Foundation\Catcher\Error::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
|
||||
* @covers \MensBeam\Foundation\Catcher\Handler::handle
|
||||
* @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
|
||||
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
|
||||
*/
|
||||
public function testMethod_log(): void {
|
||||
$l = Phony::mock(LoggerInterface::class);
|
||||
|
|
|
@ -14,7 +14,10 @@ use MensBeam\Foundation\Catcher\{
|
|||
ThrowableController
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class TestThrowableController extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
|
||||
|
@ -43,6 +46,8 @@ class TestThrowableController extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
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();
|
||||
|
@ -50,6 +55,8 @@ class TestThrowableController extends \PHPUnit\Framework\TestCase {
|
|||
$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();
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<testsuite name="Main">
|
||||
<file>cases/TestCatcher.php</file>
|
||||
<file>cases/TestHandler.php</file>
|
||||
<file>cases/TestHTMLHandler.php</file>
|
||||
<file>cases/TestPlainTextHandler.php</file>
|
||||
<file>cases/TestThrowableController.php</file>
|
||||
</testsuite>
|
||||
|
|
Loading…
Reference in a new issue