4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Debug\Tests;
14 use PHPUnit\Framework\TestCase;
16 use Symfony\Component\Debug\BufferingLogger;
17 use Symfony\Component\Debug\ErrorHandler;
18 use Symfony\Component\Debug\Exception\SilencedErrorContext;
23 * @author Robert Schönthal <seroscho@googlemail.com>
24 * @author Nicolas Grekas <p@tchwork.com>
26 class ErrorHandlerTest extends TestCase
28 public function testRegister()
30 $handler = ErrorHandler::register();
33 $this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
34 $this->assertSame($handler, ErrorHandler::register());
36 $newHandler = new ErrorHandler();
38 $this->assertSame($handler, ErrorHandler::register($newHandler, false));
39 $h = set_error_handler('var_dump');
40 restore_error_handler();
41 $this->assertSame(array($handler, 'handleError'), $h);
44 $this->assertSame($newHandler, ErrorHandler::register($newHandler, true));
45 $h = set_error_handler('var_dump');
46 restore_error_handler();
47 $this->assertSame(array($newHandler, 'handleError'), $h);
48 } catch (\Exception $e) {
51 restore_error_handler();
52 restore_exception_handler();
57 } catch (\Exception $e) {
60 restore_error_handler();
61 restore_exception_handler();
68 public function testErrorGetLast()
70 $handler = ErrorHandler::register();
71 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
72 $handler->setDefaultLogger($logger);
73 $handler->screamAt(E_ALL);
76 @trigger_error('Hello', E_USER_WARNING);
78 'type' => E_USER_WARNING,
81 'line' => __LINE__ - 5,
83 $this->assertSame($expected, error_get_last());
84 } catch (\Exception $e) {
85 restore_error_handler();
86 restore_exception_handler();
92 public function testNotice()
94 ErrorHandler::register();
97 self::triggerNotice($this);
98 $this->fail('ErrorException expected');
99 } catch (\ErrorException $exception) {
100 // if an exception is thrown, the test passed
101 $this->assertEquals(E_NOTICE, $exception->getSeverity());
102 $this->assertEquals(__FILE__, $exception->getFile());
103 $this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
105 $trace = $exception->getTrace();
107 $this->assertEquals(__FILE__, $trace[0]['file']);
108 $this->assertEquals(__CLASS__, $trace[0]['class']);
109 $this->assertEquals('triggerNotice', $trace[0]['function']);
110 $this->assertEquals('::', $trace[0]['type']);
112 $this->assertEquals(__FILE__, $trace[0]['file']);
113 $this->assertEquals(__CLASS__, $trace[1]['class']);
114 $this->assertEquals(__FUNCTION__, $trace[1]['function']);
115 $this->assertEquals('->', $trace[1]['type']);
117 restore_error_handler();
118 restore_exception_handler();
122 // dummy function to test trace in error handler.
123 private static function triggerNotice($that)
125 $that->assertSame('', $foo.$foo.$bar);
128 public function testConstruct()
131 $handler = ErrorHandler::register();
132 $handler->throwAt(3, true);
133 $this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0));
135 restore_error_handler();
136 restore_exception_handler();
140 public function testDefaultLogger()
143 $handler = ErrorHandler::register();
145 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
147 $handler->setDefaultLogger($logger, E_NOTICE);
148 $handler->setDefaultLogger($logger, array(E_USER_NOTICE => LogLevel::CRITICAL));
151 E_DEPRECATED => array(null, LogLevel::INFO),
152 E_USER_DEPRECATED => array(null, LogLevel::INFO),
153 E_NOTICE => array($logger, LogLevel::WARNING),
154 E_USER_NOTICE => array($logger, LogLevel::CRITICAL),
155 E_STRICT => array(null, LogLevel::WARNING),
156 E_WARNING => array(null, LogLevel::WARNING),
157 E_USER_WARNING => array(null, LogLevel::WARNING),
158 E_COMPILE_WARNING => array(null, LogLevel::WARNING),
159 E_CORE_WARNING => array(null, LogLevel::WARNING),
160 E_USER_ERROR => array(null, LogLevel::CRITICAL),
161 E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL),
162 E_COMPILE_ERROR => array(null, LogLevel::CRITICAL),
163 E_PARSE => array(null, LogLevel::CRITICAL),
164 E_ERROR => array(null, LogLevel::CRITICAL),
165 E_CORE_ERROR => array(null, LogLevel::CRITICAL),
167 $this->assertSame($loggers, $handler->setLoggers(array()));
169 restore_error_handler();
170 restore_exception_handler();
174 public function testHandleError()
177 $handler = ErrorHandler::register();
178 $handler->throwAt(0, true);
179 $this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, array()));
181 restore_error_handler();
182 restore_exception_handler();
184 $handler = ErrorHandler::register();
185 $handler->throwAt(3, true);
186 $this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, array()));
188 restore_error_handler();
189 restore_exception_handler();
191 $handler = ErrorHandler::register();
192 $handler->throwAt(3, true);
194 $handler->handleError(4, 'foo', 'foo.php', 12, array());
195 } catch (\ErrorException $e) {
196 $this->assertSame('Parse Error: foo', $e->getMessage());
197 $this->assertSame(4, $e->getSeverity());
198 $this->assertSame('foo.php', $e->getFile());
199 $this->assertSame(12, $e->getLine());
202 restore_error_handler();
203 restore_exception_handler();
205 $handler = ErrorHandler::register();
206 $handler->throwAt(E_USER_DEPRECATED, true);
207 $this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
209 restore_error_handler();
210 restore_exception_handler();
212 $handler = ErrorHandler::register();
213 $handler->throwAt(E_DEPRECATED, true);
214 $this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
216 restore_error_handler();
217 restore_exception_handler();
219 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
221 $warnArgCheck = function ($logLevel, $message, $context) {
222 $this->assertEquals('info', $logLevel);
223 $this->assertEquals('User Deprecated: foo', $message);
224 $this->assertArrayHasKey('exception', $context);
225 $exception = $context['exception'];
226 $this->assertInstanceOf(\ErrorException::class, $exception);
227 $this->assertSame('User Deprecated: foo', $exception->getMessage());
228 $this->assertSame(E_USER_DEPRECATED, $exception->getSeverity());
232 ->expects($this->once())
234 ->will($this->returnCallback($warnArgCheck))
237 $handler = ErrorHandler::register();
238 $handler->setDefaultLogger($logger, E_USER_DEPRECATED);
239 $this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
241 restore_error_handler();
242 restore_exception_handler();
244 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
247 $logArgCheck = function ($level, $message, $context) use (&$line) {
248 $this->assertEquals('Notice: Undefined variable: undefVar', $message);
249 $this->assertArrayHasKey('exception', $context);
250 $exception = $context['exception'];
251 $this->assertInstanceOf(SilencedErrorContext::class, $exception);
252 $this->assertSame(E_NOTICE, $exception->getSeverity());
253 $this->assertSame(__FILE__, $exception->getFile());
254 $this->assertSame($line, $exception->getLine());
255 $this->assertNotEmpty($exception->getTrace());
256 $this->assertSame(1, $exception->count);
260 ->expects($this->once())
262 ->will($this->returnCallback($logArgCheck))
265 $handler = ErrorHandler::register();
266 $handler->setDefaultLogger($logger, E_NOTICE);
267 $handler->screamAt(E_NOTICE);
269 $line = __LINE__ + 1;
272 restore_error_handler();
273 restore_exception_handler();
274 } catch (\Exception $e) {
275 restore_error_handler();
276 restore_exception_handler();
282 public function testHandleUserError()
285 $handler = ErrorHandler::register();
286 $handler->throwAt(0, true);
289 $x = new \Exception('Foo');
292 $f = new Fixtures\ToStringThrower($x);
293 $f .= ''; // Trigger $f->__toString()
294 } catch (\Exception $e) {
297 $this->assertSame($x, $e);
299 restore_error_handler();
300 restore_exception_handler();
304 public function testHandleDeprecation()
306 $logArgCheck = function ($level, $message, $context) {
307 $this->assertEquals(LogLevel::INFO, $level);
308 $this->assertArrayHasKey('exception', $context);
309 $exception = $context['exception'];
310 $this->assertInstanceOf(\ErrorException::class, $exception);
311 $this->assertSame('User Deprecated: Foo deprecation', $exception->getMessage());
314 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
316 ->expects($this->once())
318 ->will($this->returnCallback($logArgCheck))
321 $handler = new ErrorHandler();
322 $handler->setDefaultLogger($logger);
323 @$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array());
329 public function testHandleException()
332 $handler = ErrorHandler::register();
334 $exception = new \Exception('foo');
336 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
338 $logArgCheck = function ($level, $message, $context) {
339 $this->assertSame('Uncaught Exception: foo', $message);
340 $this->assertArrayHasKey('exception', $context);
341 $this->assertInstanceOf(\Exception::class, $context['exception']);
345 ->expects($this->exactly(2))
347 ->will($this->returnCallback($logArgCheck))
350 $handler->setDefaultLogger($logger, E_ERROR);
353 $handler->handleException($exception);
354 $this->fail('Exception expected');
355 } catch (\Exception $e) {
356 $this->assertSame($exception, $e);
359 $handler->setExceptionHandler(function ($e) use ($exception) {
360 $this->assertSame($exception, $e);
363 $handler->handleException($exception);
365 restore_error_handler();
366 restore_exception_handler();
373 public function testErrorStacking()
376 $handler = ErrorHandler::register();
377 $handler->screamAt(E_USER_WARNING);
379 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
382 ->expects($this->exactly(2))
385 array($this->equalTo(LogLevel::WARNING), $this->equalTo('Dummy log')),
386 array($this->equalTo(LogLevel::DEBUG), $this->equalTo('User Warning: Silenced warning'))
390 $handler->setDefaultLogger($logger, array(E_USER_WARNING => LogLevel::WARNING));
392 ErrorHandler::stackErrors();
393 @trigger_error('Silenced warning', E_USER_WARNING);
394 $logger->log(LogLevel::WARNING, 'Dummy log');
395 ErrorHandler::unstackErrors();
397 restore_error_handler();
398 restore_exception_handler();
402 public function testBootstrappingLogger()
404 $bootLogger = new BufferingLogger();
405 $handler = new ErrorHandler($bootLogger);
408 E_DEPRECATED => array($bootLogger, LogLevel::INFO),
409 E_USER_DEPRECATED => array($bootLogger, LogLevel::INFO),
410 E_NOTICE => array($bootLogger, LogLevel::WARNING),
411 E_USER_NOTICE => array($bootLogger, LogLevel::WARNING),
412 E_STRICT => array($bootLogger, LogLevel::WARNING),
413 E_WARNING => array($bootLogger, LogLevel::WARNING),
414 E_USER_WARNING => array($bootLogger, LogLevel::WARNING),
415 E_COMPILE_WARNING => array($bootLogger, LogLevel::WARNING),
416 E_CORE_WARNING => array($bootLogger, LogLevel::WARNING),
417 E_USER_ERROR => array($bootLogger, LogLevel::CRITICAL),
418 E_RECOVERABLE_ERROR => array($bootLogger, LogLevel::CRITICAL),
419 E_COMPILE_ERROR => array($bootLogger, LogLevel::CRITICAL),
420 E_PARSE => array($bootLogger, LogLevel::CRITICAL),
421 E_ERROR => array($bootLogger, LogLevel::CRITICAL),
422 E_CORE_ERROR => array($bootLogger, LogLevel::CRITICAL),
425 $this->assertSame($loggers, $handler->setLoggers(array()));
427 $handler->handleError(E_DEPRECATED, 'Foo message', __FILE__, 123, array());
429 $logs = $bootLogger->cleanLogs();
431 $this->assertCount(1, $logs);
433 $this->assertSame('info', $log[0]);
434 $this->assertSame('Deprecated: Foo message', $log[1]);
435 $this->assertArrayHasKey('exception', $log[2]);
436 $exception = $log[2]['exception'];
437 $this->assertInstanceOf(\ErrorException::class, $exception);
438 $this->assertSame('Deprecated: Foo message', $exception->getMessage());
439 $this->assertSame(__FILE__, $exception->getFile());
440 $this->assertSame(123, $exception->getLine());
441 $this->assertSame(E_DEPRECATED, $exception->getSeverity());
443 $bootLogger->log(LogLevel::WARNING, 'Foo message', array('exception' => $exception));
445 $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
446 $mockLogger->expects($this->once())
448 ->with(LogLevel::WARNING, 'Foo message', array('exception' => $exception));
450 $handler->setLoggers(array(E_DEPRECATED => array($mockLogger, LogLevel::WARNING)));
456 public function testSettingLoggerWhenExceptionIsBuffered()
458 $bootLogger = new BufferingLogger();
459 $handler = new ErrorHandler($bootLogger);
461 $exception = new \Exception('Foo message');
463 $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
464 $mockLogger->expects($this->once())
466 ->with(LogLevel::CRITICAL, 'Uncaught Exception: Foo message', array('exception' => $exception));
468 $handler->setExceptionHandler(function () use ($handler, $mockLogger) {
469 $handler->setDefaultLogger($mockLogger);
472 $handler->handleException($exception);
478 public function testHandleFatalError()
481 $handler = ErrorHandler::register();
490 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
492 $logArgCheck = function ($level, $message, $context) {
493 $this->assertEquals('Fatal Parse Error: foo', $message);
494 $this->assertArrayHasKey('exception', $context);
495 $this->assertInstanceOf(\Exception::class, $context['exception']);
499 ->expects($this->once())
501 ->will($this->returnCallback($logArgCheck))
504 $handler->setDefaultLogger($logger, E_PARSE);
506 $handler->handleFatalError($error);
508 restore_error_handler();
509 restore_exception_handler();
510 } catch (\Exception $e) {
511 restore_error_handler();
512 restore_exception_handler();
521 public function testHandleErrorException()
523 $exception = new \Error("Class 'Foo' not found");
525 $handler = new ErrorHandler();
526 $handler->setExceptionHandler(function () use (&$args) {
527 $args = func_get_args();
530 $handler->handleException($exception);
532 $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]);
533 $this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
539 public function testHandleFatalErrorOnHHVM()
542 $handler = ErrorHandler::register();
544 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
546 ->expects($this->once())
549 $this->equalTo(LogLevel::CRITICAL),
550 $this->equalTo('Fatal Error: foo')
554 $handler->setDefaultLogger($logger, E_ERROR);
557 'type' => E_ERROR + 0x1000000, // This error level is used by HHVM for fatal errors
561 'context' => array(123),
562 'backtrace' => array(456),
565 call_user_func_array(array($handler, 'handleError'), $error);
566 $handler->handleFatalError($error);
568 restore_error_handler();
569 restore_exception_handler();
574 * @expectedException \Exception
577 public function testCustomExceptionHandler()
579 $handler = new ErrorHandler();
580 $handler->setExceptionHandler(function ($e) use ($handler) {
581 $handler->handleException($e);
584 $handler->handleException(new \Exception());