Skip to content

Commit

Permalink
Initial work on error handler refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianbergmann committed Jun 21, 2019
1 parent 3199b5e commit db1dce3
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 102 deletions.
1 change: 0 additions & 1 deletion src/Framework/Error/Deprecation.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@
*/
final class Deprecation extends Error
{
public static $enabled = true;
}
1 change: 0 additions & 1 deletion src/Framework/Error/Notice.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@
*/
final class Notice extends Error
{
public static $enabled = true;
}
1 change: 0 additions & 1 deletion src/Framework/Error/Warning.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@
*/
final class Warning extends Error
{
public static $enabled = true;
}
108 changes: 95 additions & 13 deletions src/Framework/TestResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,31 @@ final class TestResult implements Countable
*/
private $codeCoverage;

/**
* @var bool
*/
private $registerErrorHandler = true;

/**
* @var bool
*/
private $convertDeprecationsToExceptions = true;

/**
* @var bool
*/
private $convertErrorsToExceptions = true;

/**
* @var bool
*/
private $convertNoticesToExceptions = true;

/**
* @var bool
*/
private $convertWarningsToExceptions = true;

/**
* @var bool
*/
Expand Down Expand Up @@ -612,19 +632,15 @@ public function run(Test $test): void

$this->startTest($test);

$errorHandlerSet = false;

if ($this->convertErrorsToExceptions) {
$oldErrorHandler = \set_error_handler(
[ErrorHandler::class, 'handleError'],
\E_ALL | \E_STRICT
if ($this->registerErrorHandler && ($this->convertDeprecationsToExceptions || $this->convertErrorsToExceptions || $this->convertNoticesToExceptions || $this->convertWarningsToExceptions)) {
$errorHandler = new ErrorHandler(
$this->convertDeprecationsToExceptions,
$this->convertErrorsToExceptions,
$this->convertNoticesToExceptions,
$this->convertWarningsToExceptions
);

if ($oldErrorHandler === null) {
$errorHandlerSet = true;
} else {
\restore_error_handler();
}
$errorHandler->register();
}

$collectCodeCoverage = $this->codeCoverage !== null &&
Expand Down Expand Up @@ -831,8 +847,10 @@ public function run(Test $test): void
}
}

if ($errorHandlerSet) {
\restore_error_handler();
if (isset($errorHandler)) {
$errorHandler->unregister();

unset($errorHandler);
}

if ($error) {
Expand Down Expand Up @@ -960,6 +978,38 @@ public function setCodeCoverage(CodeCoverage $codeCoverage): void
$this->codeCoverage = $codeCoverage;
}

/**
* Enables or disables the registration of PHPUnit's error handler.
*/
public function registerErrorHandler(bool $flag): void
{
$this->registerErrorHandler = $flag;
}

/**
* Returns whether PHPUnit's error handler is to be registered.
*/
public function getRegisterErrorHandler(): bool
{
return $this->registerErrorHandler;
}

/**
* Enables or disables the deprecation-to-exception conversion.
*/
public function convertDeprecationsToExceptions(bool $flag): void
{
$this->convertDeprecationsToExceptions = $flag;
}

/**
* Returns the deprecation-to-exception conversion setting.
*/
public function getConvertDeprecationsToExceptions(): bool
{
return $this->convertDeprecationsToExceptions;
}

/**
* Enables or disables the error-to-exception conversion.
*/
Expand All @@ -976,6 +1026,38 @@ public function getConvertErrorsToExceptions(): bool
return $this->convertErrorsToExceptions;
}

/**
* Enables or disables the notice-to-exception conversion.
*/
public function convertNoticesToExceptions(bool $flag): void
{
$this->convertNoticesToExceptions = $flag;
}

/**
* Returns the notice-to-exception conversion setting.
*/
public function getConvertNoticesToExceptions(): bool
{
return $this->convertNoticesToExceptions;
}

/**
* Enables or disables the warning-to-exception conversion.
*/
public function convertWarningsToExceptions(bool $flag): void
{
$this->convertWarningsToExceptions = $flag;
}

/**
* Returns the warning-to-exception conversion setting.
*/
public function getConvertWarningsToExceptions(): bool
{
return $this->convertWarningsToExceptions;
}

/**
* Enables or disables the stopping when an error occurs.
*/
Expand Down
15 changes: 6 additions & 9 deletions src/TextUI/TestRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
*/
namespace PHPUnit\TextUI;

use PHPUnit\Framework\Error\Deprecation;
use PHPUnit\Framework\Error\Notice;
use PHPUnit\Framework\Error\Warning;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -223,20 +220,20 @@ public function doRun(Test $suite, array $arguments = [], bool $exit = true): Te

unset($listener, $listenerNeeded);

if (!$arguments['convertErrorsToExceptions']) {
$result->convertErrorsToExceptions(false);
if (!$arguments['convertDeprecationsToExceptions']) {
$result->convertDeprecationsToExceptions(false);
}

if (!$arguments['convertDeprecationsToExceptions']) {
Deprecation::$enabled = false;
if (!$arguments['convertErrorsToExceptions']) {
$result->convertErrorsToExceptions(false);
}

if (!$arguments['convertNoticesToExceptions']) {
Notice::$enabled = false;
$result->convertNoticesToExceptions(false);
}

if (!$arguments['convertWarningsToExceptions']) {
Warning::$enabled = false;
$result->convertWarningsToExceptions(false);
}

if ($arguments['stopOnError']) {
Expand Down
146 changes: 74 additions & 72 deletions src/Util/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,99 +14,101 @@
use PHPUnit\Framework\Error\Notice;
use PHPUnit\Framework\Error\Warning;

/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class ErrorHandler
{
private static $errorStack = [];
/**
* @var bool
*/
private $convertDeprecationsToExceptions;

/**
* Returns the error stack.
* @var bool
*/
public static function getErrorStack(): array
{
return self::$errorStack;
}
private $convertErrorsToExceptions;

/**
* @throws \PHPUnit\Framework\Error\Deprecation
* @throws \PHPUnit\Framework\Error\Error
* @throws \PHPUnit\Framework\Error\Notice
* @throws \PHPUnit\Framework\Error\Warning
* @var bool
*/
public static function handleError(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool
private $convertNoticesToExceptions;

/**
* @var bool
*/
private $convertWarningsToExceptions;

/**
* @var bool
*/
private $registered = false;

public function __construct(bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions)
{
if (!($errorNumber & \error_reporting())) {
return false;
}
$this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions;
$this->convertErrorsToExceptions = $convertErrorsToExceptions;
$this->convertNoticesToExceptions = $convertNoticesToExceptions;
$this->convertWarningsToExceptions = $convertWarningsToExceptions;
}

self::$errorStack[] = [$errorNumber, $errorString, $errorFile, $errorLine];
public function __invoke(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool
{
switch ($errorNumber) {
case \E_NOTICE:
case \E_USER_NOTICE:
case \E_STRICT:
if (!$this->convertNoticesToExceptions) {
return false;
}

$trace = \debug_backtrace();
\array_shift($trace);
throw new Notice($errorString, $errorNumber, $errorFile, $errorLine);

foreach ($trace as $frame) {
if ($frame['function'] === '__toString') {
return false;
}
}
case \E_WARNING:
case \E_USER_WARNING:
if (!$this->convertWarningsToExceptions) {
return false;
}

if ($errorNumber === \E_NOTICE || $errorNumber === \E_USER_NOTICE || $errorNumber === \E_STRICT) {
if (!Notice::$enabled) {
return false;
}

$exception = Notice::class;
} elseif ($errorNumber === \E_WARNING || $errorNumber === \E_USER_WARNING) {
if (!Warning::$enabled) {
return false;
}

$exception = Warning::class;
} elseif ($errorNumber === \E_DEPRECATED || $errorNumber === \E_USER_DEPRECATED) {
if (!Deprecation::$enabled) {
return false;
}

$exception = Deprecation::class;
} else {
$exception = Error::class;
}
throw new Warning($errorString, $errorNumber, $errorFile, $errorLine);

case \E_DEPRECATED:
case \E_USER_DEPRECATED:
if (!$this->convertDeprecationsToExceptions) {
return false;
}

throw new Deprecation($errorString, $errorNumber, $errorFile, $errorLine);

throw new $exception($errorString, $errorNumber, $errorFile, $errorLine);
default:
if (!$this->convertErrorsToExceptions) {
return false;
}

throw new Error($errorString, $errorNumber, $errorFile, $errorLine);
}
}

/**
* Registers an error handler and returns a function that will restore
* the previous handler when invoked
*
* @param int $severity PHP predefined error constant
*
* @throws \Exception if event of specified severity is emitted
*/
public static function handleErrorOnce($severity = \E_WARNING): callable
public function register(): void
{
$terminator = function () {
static $expired = false;
if ($this->registered) {
return;
}

if (!$expired) {
$expired = true;
$oldErrorHandler = \set_error_handler($this);

return \restore_error_handler();
}
};
if ($oldErrorHandler !== null) {
\restore_error_handler();

\set_error_handler(
function ($errorNumber, $errorString) use ($severity) {
if ($errorNumber === $severity) {
return;
}
return;
}

return false;
}
);
$this->registered = true;
}

public function unregister(): void
{
if (!$this->registered) {
return;
}

return $terminator;
\restore_error_handler();
}
}
Loading

0 comments on commit db1dce3

Please sign in to comment.