PluginProbe
UpStream: a Project Management Plugin for WordPress / trunk
UpStream: a Project Management Plugin for WordPress vtrunk
trunk 1.39.0 1.39.1 1.39.2 1.39.3 2.0.7 2.1.0
upstream / vendor / symfony / debug / Tests / ErrorHandlerTest.php

ErrorHandlerTest.php in UpStream: a Project Management Plugin for WordPress trunk, at vendor/symfony/debug/Tests/ErrorHandlerTest.php

665 lines 22.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\Debug\Tests;
13
14 use PHPUnit\Framework\TestCase;
15 use Psr\Log\LogLevel;
16 use Psr\Log\NullLogger;
17 use Symfony\Component\Debug\BufferingLogger;
18 use Symfony\Component\Debug\ErrorHandler;
19 use Symfony\Component\Debug\Exception\SilencedErrorContext;
20 use Symfony\Component\Debug\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
21 use Symfony\Component\Debug\Tests\Fixtures\LoggerThatSetAnErrorHandler;
22
23 /**
24 * ErrorHandlerTest.
25 *
26 * @author Robert Schönthal <seroscho@googlemail.com>
27 * @author Nicolas Grekas <p@tchwork.com>
28 */
29 class ErrorHandlerTest extends TestCase
30 {
31 public function testRegister()
32 {
33 $handler = ErrorHandler::register();
34
35 try {
36 $this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
37 $this->assertSame($handler, ErrorHandler::register());
38
39 $newHandler = new ErrorHandler();
40
41 $this->assertSame($handler, ErrorHandler::register($newHandler, false));
42 $h = set_error_handler('var_dump');
43 restore_error_handler();
44 $this->assertSame([$handler, 'handleError'], $h);
45
46 try {
47 $this->assertSame($newHandler, ErrorHandler::register($newHandler, true));
48 $h = set_error_handler('var_dump');
49 restore_error_handler();
50 $this->assertSame([$newHandler, 'handleError'], $h);
51 } catch (\Exception $e) {
52 }
53
54 restore_error_handler();
55 restore_exception_handler();
56
57 if (isset($e)) {
58 throw $e;
59 }
60 } catch (\Exception $e) {
61 }
62
63 restore_error_handler();
64 restore_exception_handler();
65
66 if (isset($e)) {
67 throw $e;
68 }
69 }
70
71 public function testErrorGetLast()
72 {
73 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
74 $handler = ErrorHandler::register();
75 $handler->setDefaultLogger($logger);
76 $handler->screamAt(\E_ALL);
77
78 try {
79 @trigger_error('Hello', \E_USER_WARNING);
80 $expected = [
81 'type' => \E_USER_WARNING,
82 'message' => 'Hello',
83 'file' => __FILE__,
84 'line' => __LINE__ - 5,
85 ];
86 $this->assertSame($expected, error_get_last());
87 } catch (\Exception $e) {
88 restore_error_handler();
89 restore_exception_handler();
90
91 throw $e;
92 }
93 }
94
95 public function testNotice()
96 {
97 ErrorHandler::register();
98
99 try {
100 self::triggerNotice($this);
101 $this->fail('ErrorException expected');
102 } catch (\ErrorException $exception) {
103 // if an exception is thrown, the test passed
104 if (\PHP_VERSION_ID < 80000) {
105 $this->assertEquals(\E_NOTICE, $exception->getSeverity());
106 $this->assertMatchesRegularExpression('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
107 } else {
108 $this->assertEquals(\E_WARNING, $exception->getSeverity());
109 $this->assertMatchesRegularExpression('/^Warning: Undefined variable \$(foo|bar)/', $exception->getMessage());
110 }
111 $this->assertEquals(__FILE__, $exception->getFile());
112
113 $trace = $exception->getTrace();
114
115 $this->assertEquals(__FILE__, $trace[0]['file']);
116 $this->assertEquals(__CLASS__, $trace[0]['class']);
117 $this->assertEquals('triggerNotice', $trace[0]['function']);
118 $this->assertEquals('::', $trace[0]['type']);
119
120 $this->assertEquals(__FILE__, $trace[0]['file']);
121 $this->assertEquals(__CLASS__, $trace[1]['class']);
122 $this->assertEquals(__FUNCTION__, $trace[1]['function']);
123 $this->assertEquals('->', $trace[1]['type']);
124 } finally {
125 restore_error_handler();
126 restore_exception_handler();
127 }
128 }
129
130 // dummy function to test trace in error handler.
131 private static function triggerNotice($that)
132 {
133 $that->assertSame('', $foo.$foo.$bar);
134 }
135
136 public function testConstruct()
137 {
138 try {
139 $handler = ErrorHandler::register();
140 $handler->throwAt(3, true);
141 $this->assertEquals(3 | \E_RECOVERABLE_ERROR | \E_USER_ERROR, $handler->throwAt(0));
142 } finally {
143 restore_error_handler();
144 restore_exception_handler();
145 }
146 }
147
148 public function testDefaultLogger()
149 {
150 try {
151 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
152 $handler = ErrorHandler::register();
153
154 $handler->setDefaultLogger($logger, \E_NOTICE);
155 $handler->setDefaultLogger($logger, [\E_USER_NOTICE => LogLevel::CRITICAL]);
156
157 $loggers = [
158 \E_DEPRECATED => [null, LogLevel::INFO],
159 \E_USER_DEPRECATED => [null, LogLevel::INFO],
160 \E_NOTICE => [$logger, LogLevel::WARNING],
161 \E_USER_NOTICE => [$logger, LogLevel::CRITICAL],
162 \E_STRICT => [null, LogLevel::WARNING],
163 \E_WARNING => [null, LogLevel::WARNING],
164 \E_USER_WARNING => [null, LogLevel::WARNING],
165 \E_COMPILE_WARNING => [null, LogLevel::WARNING],
166 \E_CORE_WARNING => [null, LogLevel::WARNING],
167 \E_USER_ERROR => [null, LogLevel::CRITICAL],
168 \E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
169 \E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
170 \E_PARSE => [null, LogLevel::CRITICAL],
171 \E_ERROR => [null, LogLevel::CRITICAL],
172 \E_CORE_ERROR => [null, LogLevel::CRITICAL],
173 ];
174 $this->assertSame($loggers, $handler->setLoggers([]));
175 } finally {
176 restore_error_handler();
177 restore_exception_handler();
178 }
179 }
180
181 public function testHandleError()
182 {
183 try {
184 $handler = ErrorHandler::register();
185 $handler->throwAt(0, true);
186 $this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, []));
187
188 restore_error_handler();
189 restore_exception_handler();
190
191 $handler = ErrorHandler::register();
192 $handler->throwAt(3, true);
193 $this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, []));
194
195 restore_error_handler();
196 restore_exception_handler();
197
198 $handler = ErrorHandler::register();
199 $handler->throwAt(3, true);
200 try {
201 $handler->handleError(4, 'foo', 'foo.php', 12, []);
202 } catch (\ErrorException $e) {
203 $this->assertSame('Parse Error: foo', $e->getMessage());
204 $this->assertSame(4, $e->getSeverity());
205 $this->assertSame('foo.php', $e->getFile());
206 $this->assertSame(12, $e->getLine());
207 }
208
209 restore_error_handler();
210 restore_exception_handler();
211
212 $handler = ErrorHandler::register();
213 $handler->throwAt(\E_USER_DEPRECATED, true);
214 $this->assertFalse($handler->handleError(\E_USER_DEPRECATED, 'foo', 'foo.php', 12, []));
215
216 restore_error_handler();
217 restore_exception_handler();
218
219 $handler = ErrorHandler::register();
220 $handler->throwAt(\E_DEPRECATED, true);
221 $this->assertFalse($handler->handleError(\E_DEPRECATED, 'foo', 'foo.php', 12, []));
222
223 restore_error_handler();
224 restore_exception_handler();
225
226 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
227
228 $warnArgCheck = function ($logLevel, $message, $context) {
229 $this->assertEquals('info', $logLevel);
230 $this->assertEquals('User Deprecated: foo', $message);
231 $this->assertArrayHasKey('exception', $context);
232 $exception = $context['exception'];
233 $this->assertInstanceOf(\ErrorException::class, $exception);
234 $this->assertSame('User Deprecated: foo', $exception->getMessage());
235 $this->assertSame(\E_USER_DEPRECATED, $exception->getSeverity());
236 };
237
238 $logger
239 ->expects($this->once())
240 ->method('log')
241 ->willReturnCallback($warnArgCheck)
242 ;
243
244 $handler = ErrorHandler::register();
245 $handler->setDefaultLogger($logger, \E_USER_DEPRECATED);
246 $this->assertTrue($handler->handleError(\E_USER_DEPRECATED, 'foo', 'foo.php', 12, []));
247
248 restore_error_handler();
249 restore_exception_handler();
250
251 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
252
253 $line = null;
254 $logArgCheck = function ($level, $message, $context) use (&$line) {
255 $this->assertArrayHasKey('exception', $context);
256 $exception = $context['exception'];
257
258 if (\PHP_VERSION_ID < 80000) {
259 $this->assertEquals('Notice: Undefined variable: undefVar', $message);
260 $this->assertSame(\E_NOTICE, $exception->getSeverity());
261 } else {
262 $this->assertEquals('Warning: Undefined variable $undefVar', $message);
263 $this->assertSame(\E_WARNING, $exception->getSeverity());
264 }
265 $this->assertInstanceOf(SilencedErrorContext::class, $exception);
266 $this->assertSame(__FILE__, $exception->getFile());
267 $this->assertSame($line, $exception->getLine());
268 $this->assertNotEmpty($exception->getTrace());
269 $this->assertSame(1, $exception->count);
270 };
271
272 $logger
273 ->expects($this->once())
274 ->method('log')
275 ->willReturnCallback($logArgCheck)
276 ;
277
278 $handler = ErrorHandler::register();
279 if (\PHP_VERSION_ID < 80000) {
280 $handler->setDefaultLogger($logger, \E_NOTICE);
281 $handler->screamAt(\E_NOTICE);
282 } else {
283 $handler->setDefaultLogger($logger, \E_WARNING);
284 $handler->screamAt(\E_WARNING);
285 }
286 unset($undefVar);
287 $line = __LINE__ + 1;
288 @$undefVar++;
289
290 restore_error_handler();
291 restore_exception_handler();
292 } catch (\Exception $e) {
293 restore_error_handler();
294 restore_exception_handler();
295
296 throw $e;
297 }
298 }
299
300 public function testHandleUserError()
301 {
302 if (\PHP_VERSION_ID >= 70400) {
303 $this->markTestSkipped('PHP 7.4 allows __toString to throw exceptions');
304 }
305
306 try {
307 $handler = ErrorHandler::register();
308 $handler->throwAt(0, true);
309
310 $e = null;
311 $x = new \Exception('Foo');
312
313 try {
314 $f = new Fixtures\ToStringThrower($x);
315 $f .= ''; // Trigger $f->__toString()
316 } catch (\Exception $e) {
317 }
318
319 $this->assertSame($x, $e);
320 } finally {
321 restore_error_handler();
322 restore_exception_handler();
323 }
324 }
325
326 public function testHandleDeprecation()
327 {
328 $logArgCheck = function ($level, $message, $context) {
329 $this->assertEquals(LogLevel::INFO, $level);
330 $this->assertArrayHasKey('exception', $context);
331 $exception = $context['exception'];
332 $this->assertInstanceOf(\ErrorException::class, $exception);
333 $this->assertSame('User Deprecated: Foo deprecation', $exception->getMessage());
334 };
335
336 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
337 $logger
338 ->expects($this->once())
339 ->method('log')
340 ->willReturnCallback($logArgCheck)
341 ;
342
343 $handler = new ErrorHandler();
344 $handler->setDefaultLogger($logger);
345 @$handler->handleError(\E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, []);
346 }
347
348 /**
349 * @group no-hhvm
350 */
351 public function testHandleException()
352 {
353 try {
354 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
355 $handler = ErrorHandler::register();
356
357 $exception = new \Exception('foo');
358
359 $logArgCheck = function ($level, $message, $context) {
360 $this->assertSame('Uncaught Exception: foo', $message);
361 $this->assertArrayHasKey('exception', $context);
362 $this->assertInstanceOf(\Exception::class, $context['exception']);
363 };
364
365 $logger
366 ->expects($this->exactly(2))
367 ->method('log')
368 ->willReturnCallback($logArgCheck)
369 ;
370
371 $handler->setDefaultLogger($logger, \E_ERROR);
372
373 try {
374 $handler->handleException($exception);
375 $this->fail('Exception expected');
376 } catch (\Exception $e) {
377 $this->assertSame($exception, $e);
378 }
379
380 $handler->setExceptionHandler(function ($e) use ($exception) {
381 $this->assertSame($exception, $e);
382 });
383
384 $handler->handleException($exception);
385 } finally {
386 restore_error_handler();
387 restore_exception_handler();
388 }
389 }
390
391 /**
392 * @group legacy
393 */
394 public function testErrorStacking()
395 {
396 try {
397 $handler = ErrorHandler::register();
398 $handler->screamAt(\E_USER_WARNING);
399
400 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
401
402 $logger
403 ->expects($this->exactly(2))
404 ->method('log')
405 ->withConsecutive(
406 [$this->equalTo(LogLevel::WARNING), $this->equalTo('Dummy log')],
407 [$this->equalTo(LogLevel::DEBUG), $this->equalTo('User Warning: Silenced warning')]
408 )
409 ;
410
411 $handler->setDefaultLogger($logger, [\E_USER_WARNING => LogLevel::WARNING]);
412
413 ErrorHandler::stackErrors();
414 @trigger_error('Silenced warning', \E_USER_WARNING);
415 $logger->log(LogLevel::WARNING, 'Dummy log');
416 ErrorHandler::unstackErrors();
417 } finally {
418 restore_error_handler();
419 restore_exception_handler();
420 }
421 }
422
423 public function testBootstrappingLogger()
424 {
425 $bootLogger = new BufferingLogger();
426 $handler = new ErrorHandler($bootLogger);
427
428 $loggers = [
429 \E_DEPRECATED => [$bootLogger, LogLevel::INFO],
430 \E_USER_DEPRECATED => [$bootLogger, LogLevel::INFO],
431 \E_NOTICE => [$bootLogger, LogLevel::WARNING],
432 \E_USER_NOTICE => [$bootLogger, LogLevel::WARNING],
433 \E_STRICT => [$bootLogger, LogLevel::WARNING],
434 \E_WARNING => [$bootLogger, LogLevel::WARNING],
435 \E_USER_WARNING => [$bootLogger, LogLevel::WARNING],
436 \E_COMPILE_WARNING => [$bootLogger, LogLevel::WARNING],
437 \E_CORE_WARNING => [$bootLogger, LogLevel::WARNING],
438 \E_USER_ERROR => [$bootLogger, LogLevel::CRITICAL],
439 \E_RECOVERABLE_ERROR => [$bootLogger, LogLevel::CRITICAL],
440 \E_COMPILE_ERROR => [$bootLogger, LogLevel::CRITICAL],
441 \E_PARSE => [$bootLogger, LogLevel::CRITICAL],
442 \E_ERROR => [$bootLogger, LogLevel::CRITICAL],
443 \E_CORE_ERROR => [$bootLogger, LogLevel::CRITICAL],
444 ];
445
446 $this->assertSame($loggers, $handler->setLoggers([]));
447
448 $handler->handleError(\E_DEPRECATED, 'Foo message', __FILE__, 123, []);
449
450 $logs = $bootLogger->cleanLogs();
451
452 $this->assertCount(1, $logs);
453 $log = $logs[0];
454 $this->assertSame('info', $log[0]);
455 $this->assertSame('Deprecated: Foo message', $log[1]);
456 $this->assertArrayHasKey('exception', $log[2]);
457 $exception = $log[2]['exception'];
458 $this->assertInstanceOf(\ErrorException::class, $exception);
459 $this->assertSame('Deprecated: Foo message', $exception->getMessage());
460 $this->assertSame(__FILE__, $exception->getFile());
461 $this->assertSame(123, $exception->getLine());
462 $this->assertSame(\E_DEPRECATED, $exception->getSeverity());
463
464 $bootLogger->log(LogLevel::WARNING, 'Foo message', ['exception' => $exception]);
465
466 $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
467 $mockLogger->expects($this->once())
468 ->method('log')
469 ->with(LogLevel::WARNING, 'Foo message', ['exception' => $exception]);
470
471 $handler->setLoggers([\E_DEPRECATED => [$mockLogger, LogLevel::WARNING]]);
472 }
473
474 /**
475 * @group no-hhvm
476 */
477 public function testSettingLoggerWhenExceptionIsBuffered()
478 {
479 $bootLogger = new BufferingLogger();
480 $handler = new ErrorHandler($bootLogger);
481
482 $exception = new \Exception('Foo message');
483
484 $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
485 $mockLogger->expects($this->once())
486 ->method('log')
487 ->with(LogLevel::CRITICAL, 'Uncaught Exception: Foo message', ['exception' => $exception]);
488
489 $handler->setExceptionHandler(function () use ($handler, $mockLogger) {
490 $handler->setDefaultLogger($mockLogger);
491 });
492
493 $handler->handleException($exception);
494 }
495
496 /**
497 * @group no-hhvm
498 */
499 public function testHandleFatalError()
500 {
501 try {
502 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
503 $handler = ErrorHandler::register();
504
505 $error = [
506 'type' => \E_PARSE,
507 'message' => 'foo',
508 'file' => 'bar',
509 'line' => 123,
510 ];
511
512 $logArgCheck = function ($level, $message, $context) {
513 $this->assertEquals('Fatal Parse Error: foo', $message);
514 $this->assertArrayHasKey('exception', $context);
515 $this->assertInstanceOf(\Exception::class, $context['exception']);
516 };
517
518 $logger
519 ->expects($this->once())
520 ->method('log')
521 ->willReturnCallback($logArgCheck)
522 ;
523
524 $handler->setDefaultLogger($logger, \E_PARSE);
525
526 $handler->handleFatalError($error);
527
528 restore_error_handler();
529 restore_exception_handler();
530 } catch (\Exception $e) {
531 restore_error_handler();
532 restore_exception_handler();
533
534 throw $e;
535 }
536 }
537
538 /**
539 * @requires PHP 7
540 */
541 public function testHandleErrorException()
542 {
543 $exception = new \Error("Class 'IReallyReallyDoNotExistAnywhereInTheRepositoryISwear' not found");
544
545 $handler = new ErrorHandler();
546 $handler->setExceptionHandler(function () use (&$args) {
547 $args = \func_get_args();
548 });
549
550 $handler->handleException($exception);
551
552 $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]);
553 $this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
554 }
555
556 /**
557 * @group no-hhvm
558 */
559 public function testHandleFatalErrorOnHHVM()
560 {
561 if (!\defined('HHVM_VERSION')) {
562 $this->markTestSkipped('This test requires HHVM.');
563 }
564
565 try {
566 $handler = ErrorHandler::register();
567
568 $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
569 $logger
570 ->expects($this->once())
571 ->method('log')
572 ->with(
573 $this->equalTo(LogLevel::CRITICAL),
574 $this->equalTo('Fatal Error: foo')
575 )
576 ;
577
578 $handler->setDefaultLogger($logger, \E_ERROR);
579
580 $error = [
581 'type' => \E_ERROR + 0x1000000, // This error level is used by HHVM for fatal errors
582 'message' => 'foo',
583 'file' => 'bar',
584 'line' => 123,
585 'context' => [123],
586 'backtrace' => [456],
587 ];
588
589 \call_user_func_array([$handler, 'handleError'], $error);
590 $handler->handleFatalError($error);
591 } finally {
592 restore_error_handler();
593 restore_exception_handler();
594 }
595 }
596
597 /**
598 * @group no-hhvm
599 */
600 public function testCustomExceptionHandler()
601 {
602 $this->expectException('Exception');
603 $handler = new ErrorHandler();
604 $handler->setExceptionHandler(function ($e) use ($handler) {
605 $handler->handleException($e);
606 });
607
608 $handler->handleException(new \Exception());
609 }
610
611 /**
612 * @dataProvider errorHandlerWhenLoggingProvider
613 */
614 public function testErrorHandlerWhenLogging($previousHandlerWasDefined, $loggerSetsAnotherHandler, $nextHandlerIsDefined)
615 {
616 try {
617 if ($previousHandlerWasDefined) {
618 set_error_handler('count');
619 }
620
621 $logger = $loggerSetsAnotherHandler ? new LoggerThatSetAnErrorHandler() : new NullLogger();
622
623 $handler = ErrorHandler::register();
624 $handler->setDefaultLogger($logger);
625
626 if ($nextHandlerIsDefined) {
627 $handler = ErrorHandlerThatUsesThePreviousOne::register();
628 }
629
630 @trigger_error('foo', \E_USER_DEPRECATED);
631 @trigger_error('bar', \E_USER_DEPRECATED);
632
633 $this->assertSame([$handler, 'handleError'], set_error_handler('var_dump'));
634
635 if ($logger instanceof LoggerThatSetAnErrorHandler) {
636 $this->assertCount(2, $logger->cleanLogs());
637 }
638
639 restore_error_handler();
640
641 if ($previousHandlerWasDefined) {
642 restore_error_handler();
643 }
644
645 if ($nextHandlerIsDefined) {
646 restore_error_handler();
647 }
648 } finally {
649 restore_error_handler();
650 restore_exception_handler();
651 }
652 }
653
654 public function errorHandlerWhenLoggingProvider()
655 {
656 foreach ([false, true] as $previousHandlerWasDefined) {
657 foreach ([false, true] as $loggerSetsAnotherHandler) {
658 foreach ([false, true] as $nextHandlerIsDefined) {
659 yield [$previousHandlerWasDefined, $loggerSetsAnotherHandler, $nextHandlerIsDefined];
660 }
661 }
662 }
663 }
664 }
665