Browse Source

HTMLHandler 100% coverage

2.1.0
Dustin Wilson 1 year ago
parent
commit
d2f358139f
  1. 1
      composer.json
  2. 76
      composer.lock
  3. 17
      lib/Catcher.php
  4. 188
      lib/Catcher/HTMLHandler.php
  5. 21
      lib/Catcher/Handler.php
  6. 10
      lib/Catcher/PlainTextHandler.php
  7. 4
      tests/bootstrap.php
  8. 53
      tests/cases/TestCatcher.php
  9. 97
      tests/cases/TestHTMLHandler.php
  10. 70
      tests/cases/TestHandler.php
  11. 20
      tests/cases/TestPlainTextHandler.php
  12. 9
      tests/cases/TestThrowableController.php
  13. 1
      tests/phpunit.dist.xml

1
composer.json

@ -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

@ -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
lib/Catcher.php

@ -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 ])) {

188
lib/Catcher/HTMLHandler.php

@ -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;
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);
$ol->appendChild($li);
$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);
$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);
}
$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(' '));
$i = $this->_document->createElement('i');
$i->appendChild($this->_document->createTextNode($frame['file']));
$t->appendChild($i);
$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) {
$li->appendChild($t);
$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);
}
}
}

21
lib/Catcher/Handler.php

@ -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;

10
lib/Catcher/PlainTextHandler.php

@ -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.

4
tests/bootstrap.php

@ -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;
}

53
tests/cases/TestCatcher.php

@ -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

@ -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();
}
}

70
tests/cases/TestHandler.php

@ -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();

20
tests/cases/TestPlainTextHandler.php

@ -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);

9
tests/cases/TestThrowableController.php

@ -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();

1
tests/phpunit.dist.xml

@ -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…
Cancel
Save