Browse Source

Catcher 100% coverage

2.1.0
Dustin Wilson 2 years ago
parent
commit
5606d91b33
  1. 4
      composer.json
  2. 239
      composer.lock
  3. 48
      lib/Catcher.php
  4. 16
      lib/Catcher/Handler.php
  5. 2
      lib/Error.php
  6. 3
      tests/bootstrap.php
  7. 160
      tests/cases/TestCatcher.php
  8. 3
      tests/phpunit.dist.xml

4
composer.json

@ -24,7 +24,7 @@
"require-dev": {
"mensbeam/html-dom": "^1.0",
"phpunit/phpunit": "^9.5",
"spatie/fork": "^1.1",
"nikic/php-parser": "^4.15"
"nikic/php-parser": "^4.15",
"eloquent/phony-phpunit": "^7.1"
}
}

239
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": "6e9aedba10b2f2d0dc2c92f4b391e116",
"content-hash": "276b1ae35ddd23e0ebc736f079ecf98f",
"packages": [
{
"name": "psr/log",
@ -128,6 +128,153 @@
],
"time": "2022-03-03T08:28:38+00:00"
},
{
"name": "eloquent/phony",
"version": "5.0.2",
"source": {
"type": "git",
"url": "https://github.com/eloquent/phony.git",
"reference": "f34d67d6db6b6f351ea7e8aa8066107e756ec26b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/eloquent/phony/zipball/f34d67d6db6b6f351ea7e8aa8066107e756ec26b",
"reference": "f34d67d6db6b6f351ea7e8aa8066107e756ec26b",
"shasum": ""
},
"require": {
"php": "^7.3 || ^8"
},
"require-dev": {
"eloquent/code-style": "^1.0",
"eloquent/phpstan-phony": "^0.7",
"errors/exceptions": "^0.2",
"ext-pdo": "*",
"friendsofphp/php-cs-fixer": "^2",
"hamcrest/hamcrest-php": "^2",
"phpstan/extension-installer": "^1",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpunit/phpunit": "^9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.1.x-dev"
}
},
"autoload": {
"files": [
"src/initialize.php",
"src/functions.php"
],
"psr-4": {
"Eloquent\\Phony\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Erin Millard",
"email": "ezzatron@gmail.com",
"homepage": "http://ezzatron.com/"
}
],
"description": "Mocks, stubs, and spies for PHP.",
"homepage": "http://eloquent-software.com/phony/",
"keywords": [
"Double",
"Dummy",
"fake",
"mock",
"mocking",
"spy",
"stub",
"stubbing",
"test"
],
"support": {
"issues": "https://github.com/eloquent/phony/issues",
"source": "https://github.com/eloquent/phony/tree/5.0.2"
},
"time": "2021-02-17T01:45:10+00:00"
},
{
"name": "eloquent/phony-phpunit",
"version": "7.1.0",
"source": {
"type": "git",
"url": "https://github.com/eloquent/phony-phpunit.git",
"reference": "e77ff95ea6235211d4aae7e5f53488a5faebc2e0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/eloquent/phony-phpunit/zipball/e77ff95ea6235211d4aae7e5f53488a5faebc2e0",
"reference": "e77ff95ea6235211d4aae7e5f53488a5faebc2e0",
"shasum": ""
},
"require": {
"eloquent/phony": "^5",
"php": "^7.3 || ^8",
"phpunit/phpunit": "^9"
},
"require-dev": {
"eloquent/code-style": "^1",
"eloquent/phpstan-phony": "^0.7",
"errors/exceptions": "^0.2",
"friendsofphp/php-cs-fixer": "^2",
"phpstan/extension-installer": "^1",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-phpunit": "^0.12"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "7.2.x-dev"
}
},
"autoload": {
"files": [
"src/initialize.php",
"src/functions.php"
],
"psr-4": {
"Eloquent\\Phony\\Phpunit\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Erin Millard",
"email": "ezzatron@gmail.com",
"homepage": "http://ezzatron.com/"
}
],
"description": "Phony for PHPUnit.",
"homepage": "http://eloquent-software.com/phony/",
"keywords": [
"Double",
"Dummy",
"fake",
"mock",
"mocking",
"spy",
"stub",
"stubbing",
"test"
],
"support": {
"issues": "https://github.com/eloquent/phony-phpunit/issues",
"source": "https://github.com/eloquent/phony-phpunit/tree/7.1.0"
},
"time": "2020-12-21T09:36:47+00:00"
},
{
"name": "mensbeam/framework",
"version": "1.0.5",
@ -167,7 +314,7 @@
"homepage": "https://dustinwilson.com/"
}
],
"description": "Common classes and traits used in many MensBeam projects",
"description": "Common classes and traits used in many Mensbeam projects",
"support": {
"issues": "https://github.com/mensbeam/Framework/issues",
"source": "https://github.com/mensbeam/Framework/tree/1.0.5"
@ -637,16 +784,16 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.17",
"version": "9.2.18",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8"
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8",
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a",
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a",
"shasum": ""
},
"require": {
@ -702,7 +849,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18"
},
"funding": [
{
@ -710,7 +857,7 @@
"type": "github"
}
],
"time": "2022-08-30T12:24:04+00:00"
"time": "2022-10-27T13:35:33+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -955,16 +1102,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.5.25",
"version": "9.5.26",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d"
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d",
"reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2",
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2",
"shasum": ""
},
"require": {
@ -1037,7 +1184,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26"
},
"funding": [
{
@ -1053,7 +1200,7 @@
"type": "tidelift"
}
],
"time": "2022-09-25T03:44:45+00:00"
"time": "2022-10-28T06:00:21+00:00"
},
{
"name": "psr/http-message",
@ -2072,70 +2219,6 @@
],
"time": "2020-09-28T06:39:44+00:00"
},
{
"name": "spatie/fork",
"version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/spatie/fork.git",
"reference": "d0232e94926c89e3bba7abbff8385140619bd826"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/fork/zipball/d0232e94926c89e3bba7abbff8385140619bd826",
"reference": "d0232e94926c89e3bba7abbff8385140619bd826",
"shasum": ""
},
"require": {
"ext-pcntl": "*",
"ext-sockets": "*",
"php": "^8.0"
},
"require-dev": {
"nesbot/carbon": "^2.47",
"phpunit/phpunit": "^9.5",
"spatie/ray": "^1.10"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\Fork\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brent Roose",
"email": "brent@spatie.be",
"role": "Developer"
},
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"role": "Developer"
}
],
"description": "A lightweight solution for running code concurrently in PHP",
"homepage": "https://github.com/spatie/fork",
"keywords": [
"fork",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/fork/issues",
"source": "https://github.com/spatie/fork/tree/1.1.2"
},
"funding": [
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2022-10-03T13:44:48+00:00"
},
{
"name": "symfony/css-selector",
"version": "v5.4.11",

48
lib/Catcher.php

@ -15,6 +15,9 @@ use MensBeam\Foundation\Catcher\{
class Catcher {
/** When set to true Catcher won't exit when instructed */
public static $preventExit = false;
/**
* Array of handlers the exceptions are passed to
*
@ -46,7 +49,7 @@ class Catcher {
return $this->handlers;
}
public function getLastThrowable(): \Throwable {
public function getLastThrowable(): ?\Throwable {
return $this->lastThrowable;
}
@ -150,17 +153,12 @@ class Catcher {
*/
public function handleError(int $code, string $message, ?string $file = null, ?int $line = null): bool {
if ($code !== 0 && error_reporting()) {
$error = new Error($message, $code, $file, $line);
if ($this->isShuttingDown) {
throw $error;
} else {
$this->handleThrowable($error);
}
$this->handleThrowable(new Error($message, $code, $file, $line));
return true;
}
return false;
// If preventing exit we don't want a false here to halt processing
return (self::$preventExit);
}
/**
@ -177,14 +175,14 @@ class Catcher {
}
$controlCode = $output->controlCode;
if ($controlCode !== Handler::CONTINUE) {
if ($controlCode & Handler::BREAK) {
break;
}
}
if (
$this->isShuttingDown ||
$controlCode === Handler::EXIT ||
$controlCode & Handler::EXIT ||
$throwable instanceof \Exception ||
($throwable instanceof Error && in_array($throwable->getCode(), [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR ])) ||
(!$throwable instanceof Error && $throwable instanceof \Error)
@ -193,7 +191,13 @@ class Catcher {
$h->dispatch();
}
exit($throwable->getCode());
$this->lastThrowable = $throwable;
// Don't want to exit here when shutting down so any shutdown functions further
// down the stack still run.
if (!self::$preventExit && !$this->isShuttingDown) {
$this->exit($throwable->getCode());
}
}
$this->lastThrowable = $throwable;
@ -210,8 +214,22 @@ class Catcher {
}
$this->isShuttingDown = true;
if ($error = error_get_last() && in_array($error['type'], [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_CORE_WARNING, \E_COMPILE_ERROR, \E_COMPILE_WARNING ])) {
$this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
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 ])) {
$this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
}
}
}
/** Exists so the method may be replaced when mocking in tests */
protected function exit(int $status): void {
// This won't be shown as executed in code coverage
exit($status); //@codeCoverageIgnore
}
/** Exists so the method may be replaced when mocking in tests */
protected function getLastError(): ?array {
return error_get_last();
}
}

16
lib/Catcher/Handler.php

@ -15,7 +15,8 @@ abstract class Handler {
// Control constants
public const CONTINUE = 1;
public const BREAK = 2;
public const EXIT = 4;
public const EXIT = 4; // What if this were a bitmask option like NOW?
public const STOP = 8;
// Output constants
public const OUTPUT = 16;
@ -45,8 +46,8 @@ abstract class Handler {
* an error occurred
*/
protected string $_charset = 'UTF-8';
/** If true the handler will continue onto the next handler regardless */
protected bool $_forceContinue = false;
/** If true the handler will force break the loop through the stack of handlers */
protected bool $_forceBreak = false;
/** If true the handler will force an exit */
protected bool $_forceExit = false;
/**
@ -150,11 +151,12 @@ abstract class Handler {
}*/
protected function getControlCode(): int {
$code = self::BREAK;
$code = self::CONTINUE;
if ($this->_forceBreak) {
$code = self::BREAK;
}
if ($this->_forceExit) {
$code = self::EXIT;
} elseif ($this->_forceContinue) {
$code = self::CONTINUE;
$code |= self::EXIT;
}
return $code;

2
lib/Error.php

@ -9,7 +9,7 @@ declare(strict_types=1);
namespace MensBeam\Foundation;
class Error extends \Error {
public function __construct(string $message = '', int $code = 0, ?string $file = null, ?int $line = line, ?\Throwable $previous = null) {
public function __construct(string $message = '', int $code = 0, string $file = '', int $line = 0, ?\Throwable $previous = null) {
parent::__construct($message, $code, $previous);
$this->file = $file;
$this->line = $line;

3
tests/bootstrap.php

@ -5,6 +5,7 @@
declare(strict_types=1);
namespace MensBeam\Foundation;
use MensBeam\Foundation\Catcher;
ini_set('memory_limit', '-1');
ini_set('zend.assertions', '1');
@ -22,3 +23,5 @@ if (function_exists('xdebug_set_filter')) {
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, [ "$cwd/lib/" ]);
}
}
Catcher::$preventExit = true;

160
tests/cases/TestCatcher.php

@ -7,13 +7,16 @@
declare(strict_types=1);
namespace MensBeam\Foundation\Catcher\TestCase;
use MensBeam\Foundation\Catcher;
use MensBeam\Foundation\{
Catcher,
Error
};
use MensBeam\Foundation\Catcher\{
PlainTextHandler,
HTMLHandler,
JSONHandler
};
use Spatie\Fork\Fork;
use Eloquent\Phony\Phpunit\Phony;
class TestCatcher extends \PHPUnit\Framework\TestCase {
@ -284,16 +287,153 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
}
/**
* @covers \MensBeam\Foundation\Catcher::handleError()
* @covers \MensBeam\Foundation\Catcher::handleError
*
* @covers \MensBeam\Foundation\Catcher::__construct()
* @covers \MensBeam\Foundation\Catcher::handleThrowable()
* @covers \MensBeam\Foundation\Catcher::register()
* @covers \MensBeam\Foundation\Catcher\Handler::__construct()
* @covers \MensBeam\Foundation\Catcher::__construct
* @covers \MensBeam\Foundation\Catcher::getLastThrowable
* @covers \MensBeam\Foundation\Catcher::exit
* @covers \MensBeam\Foundation\Catcher::handleThrowable
* @covers \MensBeam\Foundation\Catcher::pushHandler
* @covers \MensBeam\Foundation\Catcher::register
* @covers \MensBeam\Foundation\Catcher::unregister
* @covers \MensBeam\Foundation\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::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
*/
/*public function testMethod_handleError(): void {
public function testMethod_handleError(): void {
$c = new Catcher(new PlainTextHandler([ 'silent' => true ]));
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);
$this->assertEquals(\E_USER_WARNING, $c->getLastThrowable()->getCode());
}*/
$t = $c->getLastThrowable();
$this->assertSame(Error::class, $t::class);
$this->assertSame(\E_USER_WARNING, $t->getCode());
trigger_error('Ook!', \E_USER_ERROR);
$t = $c->getLastThrowable();
$this->assertSame(Error::class, $t::class);
$this->assertSame(\E_USER_ERROR, $t->getCode());
$er = error_reporting();
error_reporting(0);
trigger_error('Ook!', \E_USER_ERROR);
error_reporting($er);
$c->unregister();
}
/**
* @covers \MensBeam\Foundation\Catcher::handleThrowable
*
* @covers \MensBeam\Foundation\Catcher::__construct
* @covers \MensBeam\Foundation\Catcher::getLastThrowable
* @covers \MensBeam\Foundation\Catcher::handleError
* @covers \MensBeam\Foundation\Catcher::pushHandler
* @covers \MensBeam\Foundation\Catcher::register
* @covers \MensBeam\Foundation\Catcher::unregister
* @covers \MensBeam\Foundation\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::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
*/
public function testMethod_handleThrowable(): void {
$c = new Catcher(new PlainTextHandler([ 'silent' => true, 'forceBreak' => true ]));
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();
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;
}
/**
* @covers \MensBeam\Foundation\Catcher::handleShutdown
*
* @covers \MensBeam\Foundation\Catcher::__construct
* @covers \MensBeam\Foundation\Catcher::getLastError
* @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\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::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
*/
public function testMethod_handleShutdown(): void {
$c = new Catcher();
$c->handleShutdown();
$p = new \ReflectionProperty($c, 'isShuttingDown');
$p->setAccessible(true);
$this->assertTrue($p->getValue($c));
$c->unregister();
$c = new Catcher();
$c->unregister();
$c->handleShutdown();
$p = new \ReflectionProperty($c, 'isShuttingDown');
$p->setAccessible(true);
$this->assertFalse($p->getValue($c));
$h = Phony::partialMock(Catcher::class, [ new PlainTextHandler([ 'silent' => true ]) ]);
$h->getLastError->returns([
'type' => \E_ERROR,
'message' => 'Ook!',
'file' => '/dev/null',
'line' => 2112
]);
$c = $h->get();
$c->handleShutdown();
$h->handleError->called();
$h->handleThrowable->called();
$c->unregister();
}
}

3
tests/phpunit.dist.xml

@ -8,7 +8,8 @@
convertNoticesToExceptions="false"
convertWarningsToExceptions="false"
beStrictAboutTestsThatDoNotTestAnything="true"
forceCoversAnnotation="true">
forceCoversAnnotation="true"
stopOnError="false">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../lib</directory>

Loading…
Cancel
Save