409 lines
16 KiB
PHP
409 lines
16 KiB
PHP
|
<?php
|
||
|
|
||
|
/*
|
||
|
* This file is part of the Symfony package.
|
||
|
*
|
||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||
|
*
|
||
|
* For the full copyright and license information, please view the LICENSE
|
||
|
* file that was distributed with this source code.
|
||
|
*/
|
||
|
|
||
|
namespace Symfony\Component\Debug\Tests\Exception;
|
||
|
|
||
|
use PHPUnit\Framework\TestCase;
|
||
|
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||
|
use Symfony\Component\Debug\Exception\FlattenException;
|
||
|
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||
|
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||
|
|
||
|
/**
|
||
|
* @group legacy
|
||
|
*/
|
||
|
class FlattenExceptionTest extends TestCase
|
||
|
{
|
||
|
public function testStatusCode()
|
||
|
{
|
||
|
$flattened = FlattenException::create(new \RuntimeException(), 403);
|
||
|
$this->assertEquals('403', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new \RuntimeException());
|
||
|
$this->assertEquals('500', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::createFromThrowable(new \DivisionByZeroError(), 403);
|
||
|
$this->assertEquals('403', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::createFromThrowable(new \DivisionByZeroError());
|
||
|
$this->assertEquals('500', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new NotFoundHttpException());
|
||
|
$this->assertEquals('404', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"'));
|
||
|
$this->assertEquals('401', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new BadRequestHttpException());
|
||
|
$this->assertEquals('400', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new NotAcceptableHttpException());
|
||
|
$this->assertEquals('406', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new ConflictHttpException());
|
||
|
$this->assertEquals('409', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new MethodNotAllowedHttpException(['POST']));
|
||
|
$this->assertEquals('405', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new AccessDeniedHttpException());
|
||
|
$this->assertEquals('403', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new GoneHttpException());
|
||
|
$this->assertEquals('410', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new LengthRequiredHttpException());
|
||
|
$this->assertEquals('411', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new PreconditionFailedHttpException());
|
||
|
$this->assertEquals('412', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new PreconditionRequiredHttpException());
|
||
|
$this->assertEquals('428', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new ServiceUnavailableHttpException());
|
||
|
$this->assertEquals('503', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new TooManyRequestsHttpException());
|
||
|
$this->assertEquals('429', $flattened->getStatusCode());
|
||
|
|
||
|
$flattened = FlattenException::create(new UnsupportedMediaTypeHttpException());
|
||
|
$this->assertEquals('415', $flattened->getStatusCode());
|
||
|
|
||
|
if (class_exists(SuspiciousOperationException::class)) {
|
||
|
$flattened = FlattenException::create(new SuspiciousOperationException());
|
||
|
$this->assertEquals('400', $flattened->getStatusCode());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function testHeadersForHttpException()
|
||
|
{
|
||
|
$flattened = FlattenException::create(new MethodNotAllowedHttpException(['POST']));
|
||
|
$this->assertEquals(['Allow' => 'POST'], $flattened->getHeaders());
|
||
|
|
||
|
$flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"'));
|
||
|
$this->assertEquals(['WWW-Authenticate' => 'Basic realm="My Realm"'], $flattened->getHeaders());
|
||
|
|
||
|
$flattened = FlattenException::create(new ServiceUnavailableHttpException('Fri, 31 Dec 1999 23:59:59 GMT'));
|
||
|
$this->assertEquals(['Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'], $flattened->getHeaders());
|
||
|
|
||
|
$flattened = FlattenException::create(new ServiceUnavailableHttpException(120));
|
||
|
$this->assertEquals(['Retry-After' => 120], $flattened->getHeaders());
|
||
|
|
||
|
$flattened = FlattenException::create(new TooManyRequestsHttpException('Fri, 31 Dec 1999 23:59:59 GMT'));
|
||
|
$this->assertEquals(['Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'], $flattened->getHeaders());
|
||
|
|
||
|
$flattened = FlattenException::create(new TooManyRequestsHttpException(120));
|
||
|
$this->assertEquals(['Retry-After' => 120], $flattened->getHeaders());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dataProvider flattenDataProvider
|
||
|
*/
|
||
|
public function testFlattenHttpException(\Throwable $exception)
|
||
|
{
|
||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||
|
$flattened2 = FlattenException::createFromThrowable($exception);
|
||
|
|
||
|
$flattened->setPrevious($flattened2);
|
||
|
|
||
|
$this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.');
|
||
|
$this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.');
|
||
|
$this->assertInstanceOf($flattened->getClass(), $exception, 'The class is set to the class of the original exception');
|
||
|
}
|
||
|
|
||
|
public function testWrappedThrowable()
|
||
|
{
|
||
|
$exception = new FatalThrowableError(new \DivisionByZeroError('Ouch', 42));
|
||
|
$flattened = FlattenException::create($exception);
|
||
|
|
||
|
$this->assertSame('Ouch', $flattened->getMessage(), 'The message is copied from the original error.');
|
||
|
$this->assertSame(42, $flattened->getCode(), 'The code is copied from the original error.');
|
||
|
$this->assertSame('DivisionByZeroError', $flattened->getClass(), 'The class is set to the class of the original error');
|
||
|
}
|
||
|
|
||
|
public function testThrowable()
|
||
|
{
|
||
|
$error = new \DivisionByZeroError('Ouch', 42);
|
||
|
$flattened = FlattenException::createFromThrowable($error);
|
||
|
|
||
|
$this->assertSame('Ouch', $flattened->getMessage(), 'The message is copied from the original error.');
|
||
|
$this->assertSame(42, $flattened->getCode(), 'The code is copied from the original error.');
|
||
|
$this->assertSame('DivisionByZeroError', $flattened->getClass(), 'The class is set to the class of the original error');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dataProvider flattenDataProvider
|
||
|
*/
|
||
|
public function testPrevious(\Throwable $exception)
|
||
|
{
|
||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||
|
$flattened2 = FlattenException::createFromThrowable($exception);
|
||
|
|
||
|
$flattened->setPrevious($flattened2);
|
||
|
|
||
|
$this->assertSame($flattened2, $flattened->getPrevious());
|
||
|
|
||
|
$this->assertSame([$flattened2], $flattened->getAllPrevious());
|
||
|
}
|
||
|
|
||
|
public function testPreviousError()
|
||
|
{
|
||
|
$exception = new \Exception('test', 123, new \ParseError('Oh noes!', 42));
|
||
|
|
||
|
$flattened = FlattenException::create($exception)->getPrevious();
|
||
|
|
||
|
$this->assertEquals('Oh noes!', $flattened->getMessage(), 'The message is copied from the original exception.');
|
||
|
$this->assertEquals(42, $flattened->getCode(), 'The code is copied from the original exception.');
|
||
|
$this->assertEquals('ParseError', $flattened->getClass(), 'The class is set to the class of the original exception');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dataProvider flattenDataProvider
|
||
|
*/
|
||
|
public function testLine(\Throwable $exception)
|
||
|
{
|
||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||
|
$this->assertSame($exception->getLine(), $flattened->getLine());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dataProvider flattenDataProvider
|
||
|
*/
|
||
|
public function testFile(\Throwable $exception)
|
||
|
{
|
||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||
|
$this->assertSame($exception->getFile(), $flattened->getFile());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dataProvider flattenDataProvider
|
||
|
*/
|
||
|
public function testToArray(\Throwable $exception, string $expectedClass)
|
||
|
{
|
||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||
|
$flattened->setTrace([], 'foo.php', 123);
|
||
|
|
||
|
$this->assertEquals([
|
||
|
[
|
||
|
'message' => 'test',
|
||
|
'class' => $expectedClass,
|
||
|
'trace' => [[
|
||
|
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123,
|
||
|
'args' => [],
|
||
|
]],
|
||
|
],
|
||
|
], $flattened->toArray());
|
||
|
}
|
||
|
|
||
|
public function testCreate()
|
||
|
{
|
||
|
$exception = new NotFoundHttpException(
|
||
|
'test',
|
||
|
new \RuntimeException('previous', 123)
|
||
|
);
|
||
|
|
||
|
$this->assertSame(
|
||
|
FlattenException::createFromThrowable($exception)->toArray(),
|
||
|
FlattenException::create($exception)->toArray()
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public function flattenDataProvider()
|
||
|
{
|
||
|
return [
|
||
|
[new \Exception('test', 123), 'Exception'],
|
||
|
[new \Error('test', 123), 'Error'],
|
||
|
];
|
||
|
}
|
||
|
|
||
|
public function testArguments()
|
||
|
{
|
||
|
if (\PHP_VERSION_ID >= 70400) {
|
||
|
$this->markTestSkipped('PHP 7.4 removes arguments from exception traces.');
|
||
|
}
|
||
|
|
||
|
$dh = opendir(__DIR__);
|
||
|
$fh = tmpfile();
|
||
|
|
||
|
$incomplete = unserialize('O:14:"BogusTestClass":0:{}');
|
||
|
|
||
|
$exception = $this->createException([
|
||
|
(object) ['foo' => 1],
|
||
|
new NotFoundHttpException(),
|
||
|
$incomplete,
|
||
|
$dh,
|
||
|
$fh,
|
||
|
function () {},
|
||
|
[1, 2],
|
||
|
['foo' => 123],
|
||
|
null,
|
||
|
true,
|
||
|
false,
|
||
|
0,
|
||
|
0.0,
|
||
|
'0',
|
||
|
'',
|
||
|
\INF,
|
||
|
\NAN,
|
||
|
]);
|
||
|
|
||
|
$flattened = FlattenException::create($exception);
|
||
|
$trace = $flattened->getTrace();
|
||
|
$args = $trace[1]['args'];
|
||
|
$array = $args[0][1];
|
||
|
|
||
|
closedir($dh);
|
||
|
fclose($fh);
|
||
|
|
||
|
$i = 0;
|
||
|
$this->assertSame(['object', 'stdClass'], $array[$i++]);
|
||
|
$this->assertSame(['object', 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException'], $array[$i++]);
|
||
|
$this->assertSame(['incomplete-object', 'BogusTestClass'], $array[$i++]);
|
||
|
$this->assertSame(['resource', 'stream'], $array[$i++]);
|
||
|
$this->assertSame(['resource', 'stream'], $array[$i++]);
|
||
|
|
||
|
$args = $array[$i++];
|
||
|
$this->assertSame($args[0], 'object');
|
||
|
$this->assertTrue('Closure' === $args[1] || is_subclass_of($args[1], \Closure::class), 'Expect object class name to be Closure or a subclass of Closure.');
|
||
|
|
||
|
$this->assertSame(['array', [['integer', 1], ['integer', 2]]], $array[$i++]);
|
||
|
$this->assertSame(['array', ['foo' => ['integer', 123]]], $array[$i++]);
|
||
|
$this->assertSame(['null', null], $array[$i++]);
|
||
|
$this->assertSame(['boolean', true], $array[$i++]);
|
||
|
$this->assertSame(['boolean', false], $array[$i++]);
|
||
|
$this->assertSame(['integer', 0], $array[$i++]);
|
||
|
$this->assertSame(['float', 0.0], $array[$i++]);
|
||
|
$this->assertSame(['string', '0'], $array[$i++]);
|
||
|
$this->assertSame(['string', ''], $array[$i++]);
|
||
|
$this->assertSame(['float', \INF], $array[$i++]);
|
||
|
|
||
|
// assertEquals() does not like NAN values.
|
||
|
$this->assertEquals('float', $array[$i][0]);
|
||
|
$this->assertNan($array[$i][1]);
|
||
|
}
|
||
|
|
||
|
public function testRecursionInArguments()
|
||
|
{
|
||
|
if (\PHP_VERSION_ID >= 70400) {
|
||
|
$this->markTestSkipped('PHP 7.4 removes arguments from exception traces.');
|
||
|
}
|
||
|
|
||
|
$a = null;
|
||
|
$a = ['foo', [2, &$a]];
|
||
|
$exception = $this->createException($a);
|
||
|
|
||
|
$flattened = FlattenException::create($exception);
|
||
|
$trace = $flattened->getTrace();
|
||
|
$this->assertStringContainsString('*DEEP NESTED ARRAY*', serialize($trace));
|
||
|
}
|
||
|
|
||
|
public function testTooBigArray()
|
||
|
{
|
||
|
if (\PHP_VERSION_ID >= 70400) {
|
||
|
$this->markTestSkipped('PHP 7.4 removes arguments from exception traces.');
|
||
|
}
|
||
|
|
||
|
$a = [];
|
||
|
for ($i = 0; $i < 20; ++$i) {
|
||
|
for ($j = 0; $j < 50; ++$j) {
|
||
|
for ($k = 0; $k < 10; ++$k) {
|
||
|
$a[$i][$j][$k] = 'value';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$a[20] = 'value';
|
||
|
$a[21] = 'value1';
|
||
|
$exception = $this->createException($a);
|
||
|
|
||
|
$flattened = FlattenException::create($exception);
|
||
|
$trace = $flattened->getTrace();
|
||
|
|
||
|
$this->assertSame($trace[1]['args'][0], ['array', ['array', '*SKIPPED over 10000 entries*']]);
|
||
|
|
||
|
$serializeTrace = serialize($trace);
|
||
|
|
||
|
$this->assertStringContainsString('*SKIPPED over 10000 entries*', $serializeTrace);
|
||
|
$this->assertStringNotContainsString('*value1*', $serializeTrace);
|
||
|
}
|
||
|
|
||
|
public function testAnonymousClass()
|
||
|
{
|
||
|
$flattened = FlattenException::create(new class() extends \RuntimeException {
|
||
|
});
|
||
|
|
||
|
$this->assertSame('RuntimeException@anonymous', $flattened->getClass());
|
||
|
|
||
|
$flattened->setClass(\get_class(new class('Oops') extends NotFoundHttpException {
|
||
|
}));
|
||
|
|
||
|
$this->assertSame('Symfony\Component\HttpKernel\Exception\NotFoundHttpException@anonymous', $flattened->getClass());
|
||
|
|
||
|
$flattened = FlattenException::create(new \Exception(sprintf('Class "%s" blah.', \get_class(new class() extends \RuntimeException {
|
||
|
}))));
|
||
|
|
||
|
$this->assertSame('Class "RuntimeException@anonymous" blah.', $flattened->getMessage());
|
||
|
}
|
||
|
|
||
|
public function testToStringEmptyMessage()
|
||
|
{
|
||
|
$exception = new \RuntimeException();
|
||
|
|
||
|
$flattened = FlattenException::create($exception);
|
||
|
|
||
|
$this->assertSame($exception->getTraceAsString(), $flattened->getTraceAsString());
|
||
|
$this->assertSame($exception->__toString(), $flattened->getAsString());
|
||
|
}
|
||
|
|
||
|
public function testToString()
|
||
|
{
|
||
|
$test = function ($a, $b, $c, $d) {
|
||
|
return new \RuntimeException('This is a test message');
|
||
|
};
|
||
|
|
||
|
$exception = $test('foo123', 1, null, 1.5);
|
||
|
|
||
|
$flattened = FlattenException::create($exception);
|
||
|
|
||
|
$this->assertSame($exception->getTraceAsString(), $flattened->getTraceAsString());
|
||
|
$this->assertSame($exception->__toString(), $flattened->getAsString());
|
||
|
}
|
||
|
|
||
|
public function testToStringParent()
|
||
|
{
|
||
|
$exception = new \LogicException('This is message 1');
|
||
|
$exception = new \RuntimeException('This is messsage 2', 500, $exception);
|
||
|
|
||
|
$flattened = FlattenException::create($exception);
|
||
|
|
||
|
$this->assertSame($exception->getTraceAsString(), $flattened->getTraceAsString());
|
||
|
$this->assertSame($exception->__toString(), $flattened->getAsString());
|
||
|
}
|
||
|
|
||
|
private function createException($foo)
|
||
|
{
|
||
|
return new \Exception();
|
||
|
}
|
||
|
}
|