Upstream filp/whoops state of the touched files before 5805bc7620

This commit is contained in:
upstream committed 2026-10-02 16:11:20 +02:00
1 parent 7f2cd8f154
commit a5b89f86ae
4 files changed
+1581

No files matched your search

+607
View File
@@ -0,0 +1,607 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops;
use InvalidArgumentException;
use Throwable;
use Whoops\Exception\ErrorException;
use Whoops\Handler\CallbackHandler;
use Whoops\Handler\Handler;
use Whoops\Handler\HandlerInterface;
use Whoops\Inspector\CallableInspectorFactory;
use Whoops\Inspector\InspectorFactory;
use Whoops\Inspector\InspectorFactoryInterface;
use Whoops\Inspector\InspectorInterface;
use Whoops\Util\Misc;
use Whoops\Util\SystemFacade;
final class Run implements RunInterface
{
/**
* @var bool
*/
private $isRegistered;
/**
* @var bool
*/
private $allowQuit = true;
/**
* @var bool
*/
private $sendOutput = true;
/**
* @var integer|false
*/
private $sendHttpCode = 500;
/**
* @var integer|false
*/
private $sendExitCode = 1;
/**
* @var HandlerInterface[]
*/
private $handlerStack = [];
/**
* @var array
* @psalm-var list<array{patterns: string, levels: int}>
*/
private $silencedPatterns = [];
/**
* @var SystemFacade
*/
private $system;
/**
* In certain scenarios, like in shutdown handler, we can not throw exceptions.
*
* @var bool
*/
private $canThrowExceptions = true;
/**
* The inspector factory to create inspectors.
*
* @var InspectorFactoryInterface
*/
private $inspectorFactory;
/**
* @var array<callable>
*/
private $frameFilters = [];
public function __construct(?SystemFacade $system = null)
{
$this->system = $system ?: new SystemFacade;
$this->inspectorFactory = new InspectorFactory();
}
public function __destruct()
{
$this->unregister();
}
/**
* Explicitly request your handler runs as the last of all currently registered handlers.
*
* @param callable|HandlerInterface $handler
*
* @return Run
*/
public function appendHandler($handler)
{
array_unshift($this->handlerStack, $this->resolveHandler($handler));
return $this;
}
/**
* Explicitly request your handler runs as the first of all currently registered handlers.
*
* @param callable|HandlerInterface $handler
*
* @return Run
*/
public function prependHandler($handler)
{
return $this->pushHandler($handler);
}
/**
* Register your handler as the last of all currently registered handlers (to be executed first).
* Prefer using appendHandler and prependHandler for clarity.
*
* @param callable|HandlerInterface $handler
*
* @return Run
*
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface.
*/
public function pushHandler($handler)
{
$this->handlerStack[] = $this->resolveHandler($handler);
return $this;
}
/**
* Removes and returns the last handler pushed to the handler stack.
*
* @see Run::removeFirstHandler(), Run::removeLastHandler()
*
* @return HandlerInterface|null
*/
public function popHandler()
{
return array_pop($this->handlerStack);
}
/**
* Removes the first handler.
*
* @return void
*/
public function removeFirstHandler()
{
array_pop($this->handlerStack);
}
/**
* Removes the last handler.
*
* @return void
*/
public function removeLastHandler()
{
array_shift($this->handlerStack);
}
/**
* Returns an array with all handlers, in the order they were added to the stack.
*
* @return array
*/
public function getHandlers()
{
return $this->handlerStack;
}
/**
* Clears all handlers in the handlerStack, including the default PrettyPage handler.
*
* @return Run
*/
public function clearHandlers()
{
$this->handlerStack = [];
return $this;
}
public function getFrameFilters()
{
return $this->frameFilters;
}
public function clearFrameFilters()
{
$this->frameFilters = [];
return $this;
}
/**
* Registers this instance as an error handler.
*
* @return Run
*/
public function register()
{
if (!$this->isRegistered) {
// Workaround PHP bug 42098
// https://bugs.php.net/bug.php?id=42098
class_exists("\\Whoops\\Exception\\ErrorException");
class_exists("\\Whoops\\Exception\\FrameCollection");
class_exists("\\Whoops\\Exception\\Frame");
class_exists("\\Whoops\\Exception\\Inspector");
class_exists("\\Whoops\\Inspector\\InspectorFactory");
$this->system->setErrorHandler([$this, self::ERROR_HANDLER]);
$this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]);
$this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]);
$this->isRegistered = true;
}
return $this;
}
/**
* Unregisters all handlers registered by this Whoops\Run instance.
*
* @return Run
*/
public function unregister()
{
if ($this->isRegistered) {
$this->system->restoreExceptionHandler();
$this->system->restoreErrorHandler();
$this->isRegistered = false;
}
return $this;
}
/**
* Should Whoops allow Handlers to force the script to quit?
*
* @param bool|int $exit
*
* @return bool
*/
public function allowQuit($exit = null)
{
if (func_num_args() == 0) {
return $this->allowQuit;
}
return $this->allowQuit = (bool) $exit;
}
/**
* Silence particular errors in particular files.
*
* @param array|string $patterns List or a single regex pattern to match.
* @param int $levels Defaults to E_STRICT | E_DEPRECATED.
*
* @return Run
*/
public function silenceErrorsInPaths($patterns, $levels = 10240)
{
$this->silencedPatterns = array_merge(
$this->silencedPatterns,
array_map(
function ($pattern) use ($levels) {
return [
"pattern" => $pattern,
"levels" => $levels,
];
},
(array) $patterns
)
);
return $this;
}
/**
* Returns an array with silent errors in path configuration.
*
* @return array
*/
public function getSilenceErrorsInPaths()
{
return $this->silencedPatterns;
}
/**
* Should Whoops send HTTP error code to the browser if possible?
* Whoops will by default send HTTP code 500, but you may wish to
* use 502, 503, or another 5xx family code.
*
* @param bool|int $code
*
* @return int|false
*
* @throws InvalidArgumentException
*/
public function sendHttpCode($code = null)
{
if (func_num_args() == 0) {
return $this->sendHttpCode;
}
if (!$code) {
return $this->sendHttpCode = false;
}
if ($code === true) {
$code = 500;
}
if ($code < 400 || 600 <= $code) {
throw new InvalidArgumentException(
"Invalid status code '$code', must be 4xx or 5xx"
);
}
return $this->sendHttpCode = $code;
}
/**
* Should Whoops exit with a specific code on the CLI if possible?
* Whoops will exit with 1 by default, but you can specify something else.
*
* @param int $code
*
* @return int
*
* @throws InvalidArgumentException
*/
public function sendExitCode($code = null)
{
if (func_num_args() == 0) {
return $this->sendExitCode;
}
if ($code < 0 || 255 <= $code) {
throw new InvalidArgumentException(
"Invalid status code '$code', must be between 0 and 254"
);
}
return $this->sendExitCode = (int) $code;
}
/**
* Should Whoops push output directly to the client?
* If this is false, output will be returned by handleException.
*
* @param bool|int $send
*
* @return bool
*/
public function writeToOutput($send = null)
{
if (func_num_args() == 0) {
return $this->sendOutput;
}
return $this->sendOutput = (bool) $send;
}
/**
* Handles an exception, ultimately generating a Whoops error page.
*
* @param Throwable $exception
*
* @return string Output generated by handlers.
*/
public function handleException($exception)
{
// Walk the registered handlers in the reverse order
// they were registered, and pass off the exception
$inspector = $this->getInspector($exception);
// Capture output produced while handling the exception,
// we might want to send it straight away to the client,
// or return it silently.
$this->system->startOutputBuffering();
// Just in case there are no handlers:
$handlerResponse = null;
$handlerContentType = null;
try {
foreach (array_reverse($this->handlerStack) as $handler) {
$handler->setRun($this);
$handler->setInspector($inspector);
$handler->setException($exception);
// The HandlerInterface does not require an Exception passed to handle()
// and neither of our bundled handlers use it.
// However, 3rd party handlers may have already relied on this parameter,
// and removing it would be possibly breaking for users.
$handlerResponse = $handler->handle($exception);
// Collect the content type for possible sending in the headers.
$handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null;
if (in_array($handlerResponse, [Handler::LAST_HANDLER, Handler::QUIT])) {
// The Handler has handled the exception in some way, and
// wishes to quit execution (Handler::QUIT), or skip any
// other handlers (Handler::LAST_HANDLER). If $this->allowQuit
// is false, Handler::QUIT behaves like Handler::LAST_HANDLER
break;
}
}
$willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
} finally {
$output = $this->system->cleanOutputBuffer();
}
// If we're allowed to, send output generated by handlers directly
// to the output, otherwise, and if the script doesn't quit, return
// it so that it may be used by the caller
if ($this->writeToOutput()) {
// @todo Might be able to clean this up a bit better
if ($willQuit) {
// Cleanup all other output buffers before sending our output:
while ($this->system->getOutputBufferLevel() > 0) {
$this->system->endOutputBuffering();
}
// Send any headers if needed:
if (Misc::canSendHeaders() && $handlerContentType) {
header("Content-Type: {$handlerContentType}");
}
}
$this->writeToOutputNow($output);
}
if ($willQuit) {
// HHVM fix for https://github.com/facebook/hhvm/issues/4055
$this->system->flushOutputBuffer();
$this->system->stopExecution(
$this->sendExitCode()
);
}
return $output;
}
/**
* Converts generic PHP errors to \ErrorException instances, before passing them off to be handled.
*
* This method MUST be compatible with set_error_handler.
*
* @param int $level
* @param string $message
* @param string|null $file
* @param int|null $line
*
* @return bool
*
* @throws ErrorException
*/
public function handleError($level, $message, $file = null, $line = null)
{
if ($level & $this->system->getErrorReportingLevel()) {
foreach ($this->silencedPatterns as $entry) {
$pathMatches = (bool) preg_match($entry["pattern"], $file);
$levelMatches = $level & $entry["levels"];
if ($pathMatches && $levelMatches) {
// Ignore the error, abort handling
// See https://github.com/filp/whoops/issues/418
return true;
}
}
// XXX we pass $level for the "code" param only for BC reasons.
// see https://github.com/filp/whoops/issues/267
$exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line);
if ($this->canThrowExceptions) {
throw $exception;
} else {
$this->handleException($exception);
}
// Do not propagate errors which were already handled by Whoops.
return true;
}
// Propagate error to the next handler, allows error_get_last() to
// work on silenced errors.
return false;
}
/**
* Special case to deal with Fatal errors and the like.
*
* @return void
*/
public function handleShutdown()
{
// If we reached this step, we are in shutdown handler.
// An exception thrown in a shutdown handler will not be propagated
// to the exception handler. Pass that information along.
$this->canThrowExceptions = false;
// If we are not currently registered, we should not do anything
if (!$this->isRegistered) {
return;
}
$error = $this->system->getLastError();
if ($error && Misc::isLevelFatal($error['type'])) {
// If there was a fatal error,
// it was not handled in handleError yet.
$this->allowQuit = false;
$this->handleError(
$error['type'],
$error['message'],
$error['file'],
$error['line']
);
}
}
/**
* @param InspectorFactoryInterface $factory
*
* @return void
*/
public function setInspectorFactory(InspectorFactoryInterface $factory)
{
$this->inspectorFactory = $factory;
}
public function addFrameFilter($filterCallback)
{
if (!is_callable($filterCallback)) {
throw new \InvalidArgumentException(sprintf(
"A frame filter must be of type callable, %s type given.",
gettype($filterCallback)
));
}
$this->frameFilters[] = $filterCallback;
return $this;
}
/**
* @param Throwable $exception
*
* @return InspectorInterface
*/
private function getInspector($exception)
{
return $this->inspectorFactory->create($exception);
}
/**
* Resolves the giving handler.
*
* @param callable|HandlerInterface $handler
*
* @return HandlerInterface
*
* @throws InvalidArgumentException
*/
private function resolveHandler($handler)
{
if (is_callable($handler)) {
$handler = new CallbackHandler($handler);
}
if (!$handler instanceof HandlerInterface) {
throw new InvalidArgumentException(
"Handler must be a callable, or instance of "
. "Whoops\\Handler\\HandlerInterface"
);
}
return $handler;
}
/**
* Echo something to the browser.
*
* @param string $output
*
* @return Run
*/
private function writeToOutputNow($output)
{
if ($this->sendHttpCode() && Misc::canSendHeaders()) {
$this->system->setHttpResponseCode(
$this->sendHttpCode()
);
}
echo $output;
return $this;
}
}
+144
View File
@@ -0,0 +1,144 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
class SystemFacade
{
/**
* Turns on output buffering.
*
* @return bool
*/
public function startOutputBuffering()
{
return ob_start();
}
/**
* @param callable $handler
* @param int $types
*
* @return callable|null
*/
public function setErrorHandler(callable $handler, $types = 'use-php-defaults')
{
// Since PHP 5.4 the constant E_ALL contains all errors (even E_STRICT)
if ($types === 'use-php-defaults') {
$types = E_ALL;
}
return set_error_handler($handler, $types);
}
/**
* @param callable $handler
*
* @return callable|null
*/
public function setExceptionHandler(callable $handler)
{
return set_exception_handler($handler);
}
/**
* @return void
*/
public function restoreExceptionHandler()
{
restore_exception_handler();
}
/**
* @return void
*/
public function restoreErrorHandler()
{
restore_error_handler();
}
/**
* @param callable $function
*
* @return void
*/
public function registerShutdownFunction(callable $function)
{
register_shutdown_function($function);
}
/**
* @return string|false
*/
public function cleanOutputBuffer()
{
return ob_get_clean();
}
/**
* @return int
*/
public function getOutputBufferLevel()
{
return ob_get_level();
}
/**
* @return bool
*/
public function endOutputBuffering()
{
return ob_end_clean();
}
/**
* @return void
*/
public function flushOutputBuffer()
{
flush();
}
/**
* @return int
*/
public function getErrorReportingLevel()
{
return error_reporting();
}
/**
* @return array|null
*/
public function getLastError()
{
return error_get_last();
}
/**
* @param int $httpCode
*
* @return int
*/
public function setHttpResponseCode($httpCode)
{
if (!headers_sent()) {
// Ensure that no 'location' header is present as otherwise this
// will override the HTTP code being set here, and mask the
// expected error page.
header_remove('location');
}
return http_response_code($httpCode);
}
/**
* @param int $exitStatus
*/
public function stopExecution($exitStatus)
{
exit($exitStatus);
}
}
+609
View File
@@ -0,0 +1,609 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops;
use ArrayObject;
use Exception;
use InvalidArgumentException;
use Mockery as m;
use RuntimeException;
use Whoops\Handler\Handler;
use Whoops\Handler\HandlerInterface;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Exception\Frame;
use Whoops\Util\SystemFacade;
class RunTest extends TestCase
{
public function testImplementsRunInterface()
{
$this->assertNotFalse(class_implements('Whoops\\Run', 'Whoops\\RunInterface'));
}
public function testConstantsAreAccessibleFromTheClass()
{
$this->assertEquals(RunInterface::ERROR_HANDLER, Run::ERROR_HANDLER);
$this->assertEquals(RunInterface::EXCEPTION_HANDLER, Run::EXCEPTION_HANDLER);
$this->assertEquals(RunInterface::SHUTDOWN_HANDLER, Run::SHUTDOWN_HANDLER);
}
/**
* @param string $message
* @return Exception
*/
protected function getException($message = "")
{
// HHVM does not support mocking exceptions
// Since we do not use any additional features of Mockery for exceptions,
// we can just use native Exceptions instead.
return new \Exception($message);
}
/**
* @return Handler
*/
protected function getHandler()
{
return m::mock('Whoops\\Handler\\Handler')
->shouldReceive('setRun')
->andReturn(null)
->mock()
->shouldReceive('setInspector')
->andReturn(null)
->mock()
->shouldReceive('setException')
->andReturn(null)
->mock();
}
/**
* @covers Whoops\Run::clearHandlers
*/
public function testClearHandlers()
{
$run = $this->getRunInstance();
$run->clearHandlers();
$handlers = $run->getHandlers();
$this->assertEmpty($handlers);
}
/**
* @covers Whoops\Run::pushHandler
*/
public function testPushHandler()
{
$run = $this->getRunInstance();
$run->clearHandlers();
$handlerOne = $this->getHandler();
$handlerTwo = $this->getHandler();
$run->pushHandler($handlerOne);
$run->pushHandler($handlerTwo);
$handlers = $run->getHandlers();
$this->assertCount(2, $handlers);
$this->assertContains($handlerOne, $handlers);
$this->assertContains($handlerTwo, $handlers);
}
/**
* @covers Whoops\Run::pushHandler
*/
public function testPushInvalidHandler()
{
$run = $this->getRunInstance();
$this->expectExceptionOfType('InvalidArgumentException');
$run->pushHandler('actually turnip');
}
/**
* @covers Whoops\Run::pushHandler
*/
public function testPushClosureBecomesHandler()
{
$run = $this->getRunInstance();
$run->pushHandler(function () {});
$this->assertInstanceOf('Whoops\\Handler\\CallbackHandler', $run->popHandler());
}
/**
* @covers Whoops\Run::popHandler
* @covers Whoops\Run::getHandlers
*/
public function testPopHandler()
{
$run = $this->getRunInstance();
$handlerOne = $this->getHandler();
$handlerTwo = $this->getHandler();
$handlerThree = $this->getHandler();
$run->pushHandler($handlerOne);
$run->pushHandler($handlerTwo);
$run->pushHandler($handlerThree);
$this->assertSame($handlerThree, $run->popHandler());
$this->assertSame($handlerTwo, $run->popHandler());
$this->assertSame($handlerOne, $run->popHandler());
// Should return null if there's nothing else in
// the stack
$this->assertNull($run->popHandler());
// Should be empty since we popped everything off
// the stack:
$this->assertEmpty($run->getHandlers());
}
/**
* @covers Whoops\Run::removeFirstHandler
* @covers Whoops\Run::removeLastHandler
* @covers Whoops\Run::getHandlers
*/
public function testRemoveHandler()
{
$run = $this->getRunInstance();
$handlerOne = $this->getHandler();
$handlerTwo = $this->getHandler();
$handlerThree = $this->getHandler();
$run->pushHandler($handlerOne);
$run->pushHandler($handlerTwo);
$run->pushHandler($handlerThree);
$run->removeLastHandler();
$this->assertSame($handlerTwo, $run->getHandlers()[0]);
$run->removeFirstHandler();
$this->assertSame($handlerTwo, $run->getHandlers()[0]);
$this->assertCount(1, $run->getHandlers());
}
/**
* @covers Whoops\Run::register
*/
public function testRegisterHandler()
{
// It is impossible to test the Run::register method using phpunit,
// as given how every test is always inside a giant try/catch block,
// any thrown exception will never hit a global exception handler.
// On the other hand, there is not much need in testing
// a call to a native PHP function.
$this->assertTrue(true);
}
/**
* @covers Whoops\Run::unregister
*/
public function testUnregisterHandler()
{
$run = $this->getRunInstance();
$run->register();
$handler = $this->getHandler();
$run->pushHandler($handler);
$run->unregister();
$this->expectExceptionOfType('Exception');
throw $this->getException("I'm not supposed to be caught!");
}
/**
* @covers Whoops\Run::pushHandler
* @covers Whoops\Run::getHandlers
*/
public function testHandlerHoldsOrder()
{
$run = $this->getRunInstance();
$handlerOne = $this->getHandler();
$handlerTwo = $this->getHandler();
$handlerThree = $this->getHandler();
$handlerFour = $this->getHandler();
$run->pushHandler($handlerOne);
$run->prependHandler($handlerTwo);
$run->appendHandler($handlerThree);
$run->appendHandler($handlerFour);
$handlers = $run->getHandlers();
$this->assertSame($handlers[0], $handlerFour);
$this->assertSame($handlers[1], $handlerThree);
$this->assertSame($handlers[2], $handlerOne);
$this->assertSame($handlers[3], $handlerTwo);
}
/**
* @todo possibly split this up a bit and move
* some of this test to Handler unit tests?
* @covers Whoops\Run::handleException
*/
public function testHandlersGonnaHandle()
{
$run = $this->getRunInstance();
$exception = $this->getException();
$order = new ArrayObject();
$handlerOne = $this->getHandler();
$handlerTwo = $this->getHandler();
$handlerThree = $this->getHandler();
$handlerOne->shouldReceive('handle')
->andReturnUsing(function () use ($order) { $order[] = 1; });
$handlerTwo->shouldReceive('handle')
->andReturnUsing(function () use ($order) { $order[] = 2; });
$handlerThree->shouldReceive('handle')
->andReturnUsing(function () use ($order) { $order[] = 3; });
$run->pushHandler($handlerOne);
$run->pushHandler($handlerTwo);
$run->pushHandler($handlerThree);
// Get an exception to be handled, and verify that the handlers
// are given the handler, and in the inverse order they were
// registered.
$run->handleException($exception);
$this->assertEquals((array) $order, [3, 2, 1]);
}
/**
* @covers Whoops\Run::handleException
*/
public function testLastHandler()
{
$run = $this->getRunInstance();
$handlerOne = $this->getHandler();
$handlerTwo = $this->getHandler();
$handlerThree = $this->getHandler();
$handlerFour = $this->getHandler();
$run->pushHandler($handlerOne);
$run->prependHandler($handlerTwo);
$run->appendHandler($handlerThree);
$run->appendHandler($handlerFour);
$test = $this;
$handlerFour
->shouldReceive('handle')
->andReturnUsing(function () use ($test) {
$test->fail('$handlerFour should not be called');
});
$handlerThree
->shouldReceive('handle')
->andReturn(Handler::LAST_HANDLER);
$twoRan = false;
$handlerOne
->shouldReceive('handle')
->andReturnUsing(function () use ($test, &$twoRan) {
$test->assertTrue($twoRan);
});
$handlerTwo
->shouldReceive('handle')
->andReturnUsing(function () use (&$twoRan) {
$twoRan = true;
});
$run->handleException($this->getException());
// Reached the end without errors
$this->assertTrue(true);
}
/**
* Test error suppression using @ operator.
*/
public function testErrorSuppression()
{
$run = $this->getRunInstance();
$run->register();
$handler = $this->getHandler();
$run->pushHandler($handler);
$test = $this;
$handler
->shouldReceive('handle')
->andReturnUsing(function () use ($test) {
$test->fail('$handler should not be called, error not suppressed');
});
@trigger_error("Test error suppression");
// Reached the end without errors
$this->assertTrue(true);
}
public function testErrorCatching()
{
$run = $this->getRunInstance();
$run->register();
$handler = $this->getHandler();
$run->pushHandler($handler);
$test = $this;
$handler
->shouldReceive('handle')
->andReturnUsing(function () use ($test) {
$test->fail('$handler should not be called error should be caught');
});
try {
trigger_error('foo', E_USER_NOTICE);
$this->fail('Should not continue after error thrown');
} catch (\ErrorException $e) {
// Do nothing
$this->assertTrue(true);
return;
}
$this->fail('Should not continue here, should have been caught.');
}
/**
* Test to make sure that error_reporting is respected.
*/
public function testErrorReporting()
{
$run = $this->getRunInstance();
$run->register();
$handler = $this->getHandler();
$run->pushHandler($handler);
$test = $this;
$handler
->shouldReceive('handle')
->andReturnUsing(function () use ($test) {
$test->fail('$handler should not be called, error_reporting not respected');
});
$oldLevel = error_reporting(E_ALL ^ E_USER_NOTICE);
trigger_error("Test error reporting", E_USER_NOTICE);
error_reporting($oldLevel);
// Reached the end without errors
$this->assertTrue(true);
}
/**
* @covers Whoops\Run::silenceErrorsInPaths
*/
public function testSilenceErrorsInPaths()
{
$run = $this->getRunInstance();
$run->register();
$handler = $this->getHandler();
$run->pushHandler($handler);
$test = $this;
$handler
->shouldReceive('handle')
->andReturnUsing(function () use ($test) {
$test->fail('$handler should not be called, silenceErrorsInPaths not respected');
});
$run->silenceErrorsInPaths('@^'.preg_quote(__FILE__, '@').'$@', E_USER_NOTICE);
trigger_error('Test', E_USER_NOTICE);
$this->assertTrue(true);
}
/**
* @covers Whoops\Run::handleError
* @requires PHP < 8
*/
public function testGetSilencedError()
{
$run = $this->getRunInstance();
$run->register();
$handler = $this->getHandler();
$run->pushHandler($handler);
@strpos();
$error = error_get_last();
$this->assertTrue($error && strpos($error['message'], 'strpos()') !== false);
}
/**
* @covers Whoops\Run::handleError
* @see https://github.com/filp/whoops/issues/267
*/
public function testErrorWrappedInException()
{
try {
$run = $this->getRunInstance();
$run->handleError(E_WARNING, 'my message', 'my file', 99);
$this->fail("missing expected exception");
} catch (\ErrorException $e) {
$this->assertSame(E_WARNING, $e->getSeverity());
$this->assertSame(E_WARNING, $e->getCode(), "For BC reasons getCode() should match getSeverity()");
$this->assertSame('my message', $e->getMessage());
$this->assertSame('my file', $e->getFile());
$this->assertSame(99, $e->getLine());
}
}
/**
* @covers Whoops\Run::handleException
* @covers Whoops\Run::writeToOutput
*/
public function testOutputIsSent()
{
$run = $this->getRunInstance();
$run->pushHandler(function () {
echo "hello there";
});
ob_start();
$run->handleException(new RuntimeException());
$this->assertEquals("hello there", ob_get_clean());
}
/**
* @covers Whoops\Run::handleException
* @covers Whoops\Run::writeToOutput
*/
public function testOutputIsNotSent()
{
$run = $this->getRunInstance();
$run->writeToOutput(false);
$run->pushHandler(function () {
echo "hello there";
});
ob_start();
$this->assertEquals("hello there", $run->handleException(new RuntimeException()));
$this->assertEquals("", ob_get_clean());
}
/**
* @covers Whoops\Run::sendHttpCode
*/
public function testSendHttpCode()
{
$run = $this->getRunInstance();
$run->sendHttpCode(true);
$this->assertEquals(500, $run->sendHttpCode());
}
/**
* @covers Whoops\Run::sendHttpCode
*/
public function testSendHttpCodeNullCode()
{
$run = $this->getRunInstance();
$this->assertEquals(false, $run->sendHttpCode(null));
}
/**
* @covers Whoops\Run::sendHttpCode
*/
public function testSendHttpCodeWrongCode()
{
$this->expectExceptionOfType('InvalidArgumentException');
$this->getRunInstance()->sendHttpCode(1337);
}
/**
* @covers Whoops\Run::sendHttpCode
*/
public function testSendExitCode()
{
$run = $this->getRunInstance();
$run->sendExitCode(42);
$this->assertEquals(42, $run->sendExitCode());
}
/**
* @covers Whoops\Run::sendExitCode
*/
public function testSendExitCodeDefaultCode()
{
$run = $this->getRunInstance();
$this->assertEquals(1, $run->sendExitCode());
}
/**
* @covers Whoops\Run::sendExitCode
*/
public function testSendExitCodeWrongCode()
{
$this->expectExceptionOfType('InvalidArgumentException');
$this->getRunInstance()->sendExitCode(255);
}
/**
* @covers Whoops\Run::addFrameFilter
* @covers Whoops\Run::getFrameFilters
*/
public function testAddFrameFilter()
{
$run = $this->getRunInstance();
$filterCallbackOne = function(Frame $frame) {};
$filterCallbackTwo = function(Frame $frame) {};
$run
->addFrameFilter($filterCallbackOne)
->addFrameFilter($filterCallbackTwo);
$frameFilters = $run->getFrameFilters();
$this->assertCount(2, $frameFilters);
$this->assertContains($filterCallbackOne, $frameFilters);
$this->assertContains($filterCallbackTwo, $frameFilters);
$this->assertInstanceOf("Whoops\\RunInterface", $run);
}
/**
* @covers Whoops\Run::clearFrameFilters
* @covers Whoops\Run::getFrameFilters
*/
public function testClearFrameFilters()
{
$run = $this->getRunInstance();
$run->addFrameFilter(function(Frame $frame) {});
$run = $run->clearFrameFilters();
$this->assertEmpty($run->getFrameFilters());
$this->assertInstanceOf("Whoops\\RunInterface", $run);
}
public function testShutdownHandlerDoesNotRunIfUnregistered()
{
$system = m::mock('Whoops\Util\SystemFacade');
$run = new Run($system);
$run->writeToOutput(false);
$run->allowQuit(false);
$fatalError = [
'type' => E_ERROR,
'message' => 'Simulated fatal error for shutdown',
'file' => 'somefile.php',
'line' => 10,
];
$system->shouldReceive('getLastError')->andReturn($fatalError)->byDefault();
$mockHandler = m::mock('Whoops\Handler\HandlerInterface');
$mockHandler->shouldNotReceive('handle');
$mockHandler->shouldNotReceive('setRun');
$mockHandler->shouldNotReceive('setInspector');
$mockHandler->shouldNotReceive('setException');
$run->pushHandler($mockHandler);
$run->unregister();
ob_start();
$run->handleShutdown();
$output = ob_get_clean();
$this->assertEquals('', $output, "Output buffer should be empty.");
}
}
+221
View File
@@ -0,0 +1,221 @@
<?php
namespace Whoops\Util;
use PHPUnit\Framework\TestCase;
class SystemFacadeTest extends TestCase
{
/**
* @var \Mockery\Mock
*/
public static $runtime;
/**
* @var SystemFacade
*/
private $facade;
public static function delegate($fn, array $args = [])
{
return self::$runtime
? call_user_func_array([self::$runtime, $fn], $args)
: call_user_func_array("\\$fn", $args);
}
/**
* @before
*/
public function getReady()
{
self::$runtime = \Mockery::mock(['ob_start' => true]);
$this->facade = new SystemFacade();
}
/**
* @after
*/
public function finishUp()
{
self::$runtime = null;
\Mockery::close();
}
public function test_it_delegates_output_buffering_to_the_native_implementation()
{
self::$runtime->shouldReceive('ob_start')->once();
$this->facade->startOutputBuffering();
}
public function test_it_delegates_cleaning_output_buffering_to_the_native_implementation()
{
self::$runtime->shouldReceive('ob_get_clean')->once();
$this->facade->cleanOutputBuffer();
}
public function test_it_delegates_getting_the_current_buffer_level_to_the_native_implementation()
{
self::$runtime->shouldReceive('ob_get_level')->once();
$this->facade->getOutputBufferLevel();
}
public function test_it_delegates_ending_the_current_buffer_to_the_native_implementation()
{
self::$runtime->shouldReceive('ob_end_clean')->once();
$this->facade->endOutputBuffering();
}
public function test_it_delegates_flushing_the_current_buffer_to_the_native_implementation()
{
self::$runtime->shouldReceive('flush')->once();
$this->facade->flushOutputBuffer();
}
public function test_it_delegates_error_handling_to_the_native_implementation()
{
self::$runtime->shouldReceive('set_error_handler')->once();
$this->facade->setErrorHandler(function(){});
}
public function test_it_delegates_error_handling_with_level_to_the_native_implementation()
{
self::$runtime->shouldReceive('set_error_handler')->once();
$this->facade->setErrorHandler(function(){}, E_CORE_ERROR);
}
public function test_it_delegates_exception_handling_to_the_native_implementation()
{
self::$runtime->shouldReceive('set_exception_handler')->once();
$this->facade->setExceptionHandler(function(){});
}
public function test_it_delegates_restoring_the_exception_handler_to_the_native_implementation()
{
self::$runtime->shouldReceive('restore_exception_handler')->once();
$this->facade->restoreExceptionHandler();
}
public function test_it_delegates_restoring_the_error_handler_to_the_native_implementation()
{
self::$runtime->shouldReceive('restore_error_handler')->once();
$this->facade->restoreErrorHandler();
}
public function test_it_delegates_registering_a_shutdown_function_to_the_native_implementation()
{
self::$runtime->shouldReceive('register_shutdown_function')->once();
$this->facade->registerShutdownFunction(function(){});
}
public function test_it_delegates_error_reporting_to_the_native_implementation()
{
self::$runtime->shouldReceive('error_reporting')->once()->withNoArgs();
$this->facade->getErrorReportingLevel();
}
public function test_it_delegates_getting_the_last_error_to_the_native_implementation()
{
self::$runtime->shouldReceive('error_get_last')->once()->withNoArgs();
$this->facade->getLastError();
}
public function test_it_delegates_sending_an_http_response_code_to_the_native_implementation()
{
self::$runtime->shouldReceive('headers_sent')->once()->withNoArgs();
self::$runtime->shouldReceive('header_remove')->once()->with('location');
self::$runtime->shouldReceive('http_response_code')->once()->with(230);
$this->facade->setHttpResponseCode(230);
}
}
function ob_start()
{
return SystemFacadeTest::delegate('ob_start');
}
function ob_get_clean()
{
return SystemFacadeTest::delegate('ob_get_clean');
}
function ob_get_level()
{
return SystemFacadeTest::delegate('ob_get_level');
}
function ob_end_clean()
{
return SystemFacadeTest::delegate('ob_end_clean');
}
function flush()
{
return SystemFacadeTest::delegate('flush');
}
function set_error_handler(callable $handler, $types = 'use-php-defaults')
{
// Workaround for PHP 5.5
if ($types === 'use-php-defaults') {
$types = E_ALL | E_STRICT;
}
return SystemFacadeTest::delegate('set_error_handler', func_get_args());
}
function set_exception_handler(callable $handler)
{
return SystemFacadeTest::delegate('set_exception_handler', func_get_args());
}
function restore_exception_handler()
{
return SystemFacadeTest::delegate('restore_exception_handler');
}
function restore_error_handler()
{
return SystemFacadeTest::delegate('restore_error_handler');
}
function register_shutdown_function()
{
return SystemFacadeTest::delegate('register_shutdown_function', func_get_args());
}
function error_reporting($level = null)
{
return SystemFacadeTest::delegate('error_reporting', func_get_args());
}
function error_get_last()
{
return SystemFacadeTest::delegate('error_get_last', func_get_args());
}
function header_remove($header = null)
{
return SystemFacadeTest::delegate('header_remove', func_get_args());
}
function headers_sent(&$filename = null, &$line = null)
{
return SystemFacadeTest::delegate('headers_sent', func_get_args());
}
function http_response_code($code = null)
{
return SystemFacadeTest::delegate('http_response_code', func_get_args());
}