Compare commits

...

12 Commits

Author SHA1 Message Date
Nicolas Grekas
bc977cb268 minor #32619 [Debug][ExceptionHandler] Add tests for custom handlers (fancyweb)
This PR was merged into the 3.4 branch.

Discussion
----------

[Debug][ExceptionHandler] Add tests for custom handlers

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

In https://github.com/symfony/symfony/pull/31694 I mixed many things but the whole PR was closed. I wrote some tests for custom handlers + the handle tests don't use mock anymore

I think they are useful even if the `ExceptionHandler` will disappear in the new component because it will still exists in 4.4 for the next 3 years.

Commits
-------

c53e25332a [Debug][ExceptionHandler] Add tests for custom handlers
2019-07-23 10:39:19 +02:00
Nicolas Grekas
dabd21d13b minor #32623 Remove dead tests fixtures (fancyweb)
This PR was merged into the 3.4 branch.

Discussion
----------

Remove dead tests fixtures

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Once this is merged up to 4.2, I will check 4.2.

Commits
-------

f7e24c2c80 Remove dead tests fixtures
2019-07-19 14:05:10 +02:00
Thomas Calvet
b6f28caaf2 Remove dead tests fixtures 2019-07-19 13:52:08 +02:00
Thomas Calvet
5bf4824968 [Debug][ExceptionHandler] Add tests for custom handlers 2019-07-19 10:43:44 +02:00
Nicolas Grekas
dff676526c minor #32612 [Debug] Fix 3.4 tests (yceruto)
This PR was merged into the 3.4 branch.

Discussion
----------

[Debug] Fix 3.4 tests

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT

Commits
-------

77fa283091 Fix Debug component tests
2019-07-19 10:32:24 +02:00
Yonel Ceruto
b49ea98332 Fix Debug component tests 2019-07-18 18:35:50 -04:00
Grégoire Paris
a808f15333 Use mocks before replacing the error handler
We want the bridge to mute the deprecations triggered when building mocks.
2019-07-18 08:26:12 +02:00
Thomas Calvet
32d260af46 [Debug][DebugClassLoader] Don't check class if the included file don't exist 2019-07-12 10:40:08 +02:00
Thomas Calvet
740602e8b0 [Debug][DebugClassLoader] Include found files instead of requiring them 2019-07-11 20:09:53 +02:00
Christian Flothmann
d58c0d65d7 fix Debug component dependencies 2019-06-28 11:18:39 +02:00
Nicolas Grekas
1172dc1abe [Debug] workaround BC break in PHP 7.3 2019-06-18 23:26:03 +02:00
Fabien Potencier
7e1a4ec082 fixed CS 2019-06-13 12:34:15 +02:00
6 changed files with 112 additions and 100 deletions

View File

@@ -149,11 +149,11 @@ class DebugClassLoader
if (!$file = $this->classLoader[0]->findFile($class) ?: false) {
// no-op
} elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) {
require $file;
include $file;
return;
} else {
require $file;
} elseif (false === include $file) {
return;
}
} else {
\call_user_func($this->classLoader, $class);
@@ -168,7 +168,7 @@ class DebugClassLoader
private function checkClass($class, $file = null)
{
$exists = null === $file || \class_exists($class, false) || \interface_exists($class, false) || \trait_exists($class, false);
$exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
if (null !== $file && $class && '\\' === $class[0]) {
$class = substr($class, 1);
@@ -186,13 +186,13 @@ class DebugClassLoader
}
$name = $refl->getName();
if ($name !== $class && 0 === \strcasecmp($name, $class)) {
if ($name !== $class && 0 === strcasecmp($name, $class)) {
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
}
$deprecations = $this->checkAnnotations($refl, $name);
if (isset(self::$php7Reserved[\strtolower($refl->getShortName())])) {
if (isset(self::$php7Reserved[strtolower($refl->getShortName())])) {
$deprecations[] = sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName());
}
@@ -223,23 +223,23 @@ class DebugClassLoader
$deprecations = [];
// Don't trigger deprecations for classes in the same vendor
if (2 > $len = 1 + (\strpos($class, '\\') ?: \strpos($class, '_'))) {
if (2 > $len = 1 + (strpos($class, '\\') ?: strpos($class, '_'))) {
$len = 0;
$ns = '';
} else {
$ns = \str_replace('_', '\\', \substr($class, 0, $len));
$ns = str_replace('_', '\\', substr($class, 0, $len));
}
// Detect annotations on the class
if (false !== $doc = $refl->getDocComment()) {
foreach (['final', 'deprecated', 'internal'] as $annotation) {
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
}
}
}
$parent = \get_parent_class($class);
$parent = get_parent_class($class);
$parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent);
if ($parent) {
$parentAndOwnInterfaces[$parent] = $parent;
@@ -254,22 +254,22 @@ class DebugClassLoader
}
// Detect if the parent is annotated
foreach ($parentAndOwnInterfaces + \class_uses($class, false) as $use) {
foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) {
if (!isset(self::$checkedClasses[$use])) {
$this->checkClass($use);
}
if (isset(self::$deprecated[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len) && !isset(self::$deprecated[$class])) {
if (isset(self::$deprecated[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len) && !isset(self::$deprecated[$class])) {
$type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
$verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
$deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]);
}
if (isset(self::$internal[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len)) {
if (isset(self::$internal[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len)) {
$deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class);
}
}
if (\trait_exists($class)) {
if (trait_exists($class)) {
return $deprecations;
}
@@ -296,7 +296,7 @@ class DebugClassLoader
if (isset(self::$internalMethods[$class][$method->name])) {
list($declaringClass, $message) = self::$internalMethods[$class][$method->name];
if (\strncmp($ns, $declaringClass, $len)) {
if (strncmp($ns, $declaringClass, $len)) {
$deprecations[] = sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class);
}
}
@@ -307,7 +307,7 @@ class DebugClassLoader
}
foreach (['final', 'internal'] as $annotation) {
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
$message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message];
}

View File

@@ -382,6 +382,11 @@ class ErrorHandler
*/
public function handleError($type, $message, $file, $line)
{
// @deprecated to be removed in Symfony 5.0
if (\PHP_VERSION_ID >= 70300 && $message && '"' === $message[0] && 0 === strpos($message, '"continue') && preg_match('/^"continue(?: \d++)?" targeting switch is equivalent to "break(?: \d++)?"\. Did you mean to use "continue(?: \d++)?"\?$/', $message)) {
$type = E_DEPRECATED;
}
// Level is the current error reporting level to manage silent error.
$level = error_reporting();
$silenced = 0 === ($level & $type);

View File

@@ -70,8 +70,8 @@ class ErrorHandlerTest extends TestCase
public function testErrorGetLast()
{
$handler = ErrorHandler::register();
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$handler = ErrorHandler::register();
$handler->setDefaultLogger($logger);
$handler->screamAt(E_ALL);
@@ -143,9 +143,8 @@ class ErrorHandlerTest extends TestCase
public function testDefaultLogger()
{
try {
$handler = ErrorHandler::register();
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$handler = ErrorHandler::register();
$handler->setDefaultLogger($logger, E_NOTICE);
$handler->setDefaultLogger($logger, [E_USER_NOTICE => LogLevel::CRITICAL]);
@@ -334,12 +333,11 @@ class ErrorHandlerTest extends TestCase
public function testHandleException()
{
try {
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$handler = ErrorHandler::register();
$exception = new \Exception('foo');
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$logArgCheck = function ($level, $message, $context) {
$this->assertSame('Uncaught Exception: foo', $message);
$this->assertArrayHasKey('exception', $context);
@@ -483,6 +481,7 @@ class ErrorHandlerTest extends TestCase
public function testHandleFatalError()
{
try {
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$handler = ErrorHandler::register();
$error = [
@@ -492,8 +491,6 @@ class ErrorHandlerTest extends TestCase
'line' => 123,
];
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$logArgCheck = function ($level, $message, $context) {
$this->assertEquals('Fatal Parse Error: foo', $message);
$this->assertArrayHasKey('exception', $context);

View File

@@ -76,7 +76,7 @@ class ExceptionHandlerTest extends TestCase
ob_start();
$handler->sendPhpResponse(new MethodNotAllowedHttpException(['POST']));
$response = ob_get_clean();
ob_get_clean();
$expectedHeaders = [
['HTTP/1.0 405', true, null],
@@ -99,35 +99,65 @@ class ExceptionHandlerTest extends TestCase
public function testHandle()
{
$exception = new \Exception('foo');
$handler = new ExceptionHandler(true);
ob_start();
$handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
$handler
->expects($this->exactly(2))
->method('sendPhpResponse');
$handler->handle(new \Exception('foo'));
$handler->handle($exception);
$this->assertThatTheExceptionWasOutput(ob_get_clean(), \Exception::class, 'Exception', 'foo');
}
$handler->setHandler(function ($e) use ($exception) {
$this->assertSame($exception, $e);
public function testHandleWithACustomHandlerThatOutputsSomething()
{
$handler = new ExceptionHandler(true);
ob_start();
$handler->setHandler(function () {
echo 'ccc';
});
$handler->handle($exception);
$handler->handle(new \Exception());
ob_end_flush(); // Necessary because of this PHP bug : https://bugs.php.net/bug.php?id=76563
$this->assertSame('ccc', ob_get_clean());
}
public function testHandleWithACustomHandlerThatOutputsNothing()
{
$handler = new ExceptionHandler(true);
$handler->setHandler(function () {});
$handler->handle(new \Exception('ccc'));
$this->assertThatTheExceptionWasOutput(ob_get_clean(), \Exception::class, 'Exception', 'ccc');
}
public function testHandleWithACustomHandlerThatFails()
{
$handler = new ExceptionHandler(true);
$handler->setHandler(function () {
throw new \RuntimeException();
});
$handler->handle(new \Exception('ccc'));
$this->assertThatTheExceptionWasOutput(ob_get_clean(), \Exception::class, 'Exception', 'ccc');
}
public function testHandleOutOfMemoryException()
{
$exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
$handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
$handler
->expects($this->once())
->method('sendPhpResponse');
$handler->setHandler(function ($e) {
$handler = new ExceptionHandler(true);
ob_start();
$handler->setHandler(function () {
$this->fail('OutOfMemoryException should bypass the handler');
});
$handler->handle($exception);
$handler->handle(new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__));
$this->assertThatTheExceptionWasOutput(ob_get_clean(), OutOfMemoryException::class, 'OutOfMemoryException', 'foo');
}
private function assertThatTheExceptionWasOutput($content, $expectedClass, $expectedTitle, $expectedMessage)
{
$this->assertContains(sprintf('<span class="exception_title"><abbr title="%s">%s</abbr></span>', $expectedClass, $expectedTitle), $content);
$this->assertContains(sprintf('<p class="break-long-words trace-message">%s</p>', $expectedMessage), $content);
}
}

View File

@@ -61,7 +61,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
}
$this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
$this->assertSame($translatedMessage, $exception->getMessage());
$this->assertRegExp($translatedMessage, $exception->getMessage());
$this->assertSame($error['type'], $exception->getSeverity());
$this->assertSame($error['file'], $exception->getFile());
$this->assertSame($error['line'], $exception->getLine());
@@ -71,6 +71,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
{
$autoloader = new ComposerClassLoader();
$autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception'));
$autoloader->add('Symfony_Component_Debug_Tests_Fixtures', realpath(__DIR__.'/../../Tests/Fixtures'));
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
@@ -82,7 +83,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
'file' => 'foo.php',
'message' => 'Class \'WhizBangFactory\' not found',
],
"Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?",
"/^Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement\?$/",
],
[
[
@@ -91,7 +92,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found',
],
"Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
"/^Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for another namespace\?$/",
],
[
[
@@ -100,7 +101,8 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
'file' => 'foo.php',
'message' => 'Class \'UndefinedFunctionException\' not found',
],
"Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
"/^Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for .*\"Symfony\\\\Component\\\\Debug\\\\Exception\\\\UndefinedFunctionException\"\?$/",
[$debugClassLoader, 'loadClass'],
],
[
[
@@ -109,35 +111,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
'file' => 'foo.php',
'message' => 'Class \'PEARClass\' not found',
],
"Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?",
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
],
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
],
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
[$autoloader, 'loadClass'],
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
],
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
"/^Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"\?$/",
[$debugClassLoader, 'loadClass'],
],
[
@@ -147,7 +121,37 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
],
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
"/^Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for .*\"Symfony\\\\Component\\\\Debug\\\\Exception\\\\UndefinedFunctionException\"\?$/",
[$debugClassLoader, 'loadClass'],
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
],
"/^Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for \"Symfony\\\\Component\\\\Debug\\\\Exception\\\\UndefinedFunctionException\"\?$/",
[$autoloader, 'loadClass'],
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
],
"/^Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for \"Symfony\\\\Component\\\\Debug\\\\Exception\\\\UndefinedFunctionException\"\?/",
[$debugClassLoader, 'loadClass'],
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
],
"/^Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for another namespace\?$/",
function ($className) { /* do nothing here */ },
],
];

View File

@@ -1,24 +0,0 @@
<?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;
use Symfony\Component\Debug\ExceptionHandler;
class MockExceptionHandler extends ExceptionHandler
{
public $e;
public function handle(\Exception $e)
{
$this->e = $e;
}
}