ErrorHandlerTest.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Debug\Tests;
  11. use PHPUnit\Framework\TestCase;
  12. use Psr\Log\LogLevel;
  13. use Symfony\Component\Debug\BufferingLogger;
  14. use Symfony\Component\Debug\ErrorHandler;
  15. use Symfony\Component\Debug\Exception\ContextErrorException;
  16. /**
  17. * ErrorHandlerTest.
  18. *
  19. * @author Robert Schönthal <seroscho@googlemail.com>
  20. * @author Nicolas Grekas <p@tchwork.com>
  21. */
  22. class ErrorHandlerTest extends TestCase
  23. {
  24. public function testRegister()
  25. {
  26. $handler = ErrorHandler::register();
  27. try {
  28. $this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
  29. $this->assertSame($handler, ErrorHandler::register());
  30. $newHandler = new ErrorHandler();
  31. $this->assertSame($handler, ErrorHandler::register($newHandler, false));
  32. $h = set_error_handler('var_dump');
  33. restore_error_handler();
  34. $this->assertSame(array($handler, 'handleError'), $h);
  35. try {
  36. $this->assertSame($newHandler, ErrorHandler::register($newHandler, true));
  37. $h = set_error_handler('var_dump');
  38. restore_error_handler();
  39. $this->assertSame(array($newHandler, 'handleError'), $h);
  40. } catch (\Exception $e) {
  41. }
  42. restore_error_handler();
  43. restore_exception_handler();
  44. if (isset($e)) {
  45. throw $e;
  46. }
  47. } catch (\Exception $e) {
  48. }
  49. restore_error_handler();
  50. restore_exception_handler();
  51. if (isset($e)) {
  52. throw $e;
  53. }
  54. }
  55. public function testErrorGetLast()
  56. {
  57. $handler = ErrorHandler::register();
  58. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  59. $handler->setDefaultLogger($logger);
  60. $handler->screamAt(E_ALL);
  61. try {
  62. @trigger_error('Hello', E_USER_WARNING);
  63. $expected = array(
  64. 'type' => E_USER_WARNING,
  65. 'message' => 'Hello',
  66. 'file' => __FILE__,
  67. 'line' => __LINE__ - 5,
  68. );
  69. $this->assertSame($expected, error_get_last());
  70. } catch (\Exception $e) {
  71. restore_error_handler();
  72. restore_exception_handler();
  73. throw $e;
  74. }
  75. }
  76. public function testNotice()
  77. {
  78. ErrorHandler::register();
  79. try {
  80. self::triggerNotice($this);
  81. $this->fail('ContextErrorException expected');
  82. } catch (ContextErrorException $exception) {
  83. // if an exception is thrown, the test passed
  84. restore_error_handler();
  85. restore_exception_handler();
  86. $this->assertEquals(E_NOTICE, $exception->getSeverity());
  87. $this->assertEquals(__FILE__, $exception->getFile());
  88. $this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
  89. if (\PHP_VERSION_ID < 70200) {
  90. $this->assertArrayHasKey('foobar', $exception->getContext());
  91. }
  92. $trace = $exception->getTrace();
  93. $this->assertEquals(__FILE__, $trace[0]['file']);
  94. $this->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
  95. $this->assertEquals('handleError', $trace[0]['function']);
  96. $this->assertEquals('->', $trace[0]['type']);
  97. $this->assertEquals(__FILE__, $trace[1]['file']);
  98. $this->assertEquals(__CLASS__, $trace[1]['class']);
  99. $this->assertEquals('triggerNotice', $trace[1]['function']);
  100. $this->assertEquals('::', $trace[1]['type']);
  101. $this->assertEquals(__FILE__, $trace[1]['file']);
  102. $this->assertEquals(__CLASS__, $trace[2]['class']);
  103. $this->assertEquals(__FUNCTION__, $trace[2]['function']);
  104. $this->assertEquals('->', $trace[2]['type']);
  105. } catch (\Exception $e) {
  106. restore_error_handler();
  107. restore_exception_handler();
  108. throw $e;
  109. }
  110. }
  111. // dummy function to test trace in error handler.
  112. private static function triggerNotice($that)
  113. {
  114. // dummy variable to check for in error handler.
  115. $foobar = 123;
  116. $that->assertSame('', $foo.$foo.$bar);
  117. }
  118. public function testConstruct()
  119. {
  120. try {
  121. $handler = ErrorHandler::register();
  122. $handler->throwAt(3, true);
  123. $this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0));
  124. restore_error_handler();
  125. restore_exception_handler();
  126. } catch (\Exception $e) {
  127. restore_error_handler();
  128. restore_exception_handler();
  129. throw $e;
  130. }
  131. }
  132. public function testDefaultLogger()
  133. {
  134. try {
  135. $handler = ErrorHandler::register();
  136. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  137. $handler->setDefaultLogger($logger, E_NOTICE);
  138. $handler->setDefaultLogger($logger, array(E_USER_NOTICE => LogLevel::CRITICAL));
  139. $loggers = array(
  140. E_DEPRECATED => array(null, LogLevel::INFO),
  141. E_USER_DEPRECATED => array(null, LogLevel::INFO),
  142. E_NOTICE => array($logger, LogLevel::WARNING),
  143. E_USER_NOTICE => array($logger, LogLevel::CRITICAL),
  144. E_STRICT => array(null, LogLevel::WARNING),
  145. E_WARNING => array(null, LogLevel::WARNING),
  146. E_USER_WARNING => array(null, LogLevel::WARNING),
  147. E_COMPILE_WARNING => array(null, LogLevel::WARNING),
  148. E_CORE_WARNING => array(null, LogLevel::WARNING),
  149. E_USER_ERROR => array(null, LogLevel::CRITICAL),
  150. E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL),
  151. E_COMPILE_ERROR => array(null, LogLevel::CRITICAL),
  152. E_PARSE => array(null, LogLevel::CRITICAL),
  153. E_ERROR => array(null, LogLevel::CRITICAL),
  154. E_CORE_ERROR => array(null, LogLevel::CRITICAL),
  155. );
  156. $this->assertSame($loggers, $handler->setLoggers(array()));
  157. restore_error_handler();
  158. restore_exception_handler();
  159. } catch (\Exception $e) {
  160. restore_error_handler();
  161. restore_exception_handler();
  162. throw $e;
  163. }
  164. }
  165. public function testHandleError()
  166. {
  167. try {
  168. $handler = ErrorHandler::register();
  169. $handler->throwAt(0, true);
  170. $this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, array()));
  171. restore_error_handler();
  172. restore_exception_handler();
  173. $handler = ErrorHandler::register();
  174. $handler->throwAt(3, true);
  175. $this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, array()));
  176. restore_error_handler();
  177. restore_exception_handler();
  178. $handler = ErrorHandler::register();
  179. $handler->throwAt(3, true);
  180. try {
  181. $handler->handleError(4, 'foo', 'foo.php', 12, array());
  182. } catch (\ErrorException $e) {
  183. $this->assertSame('Parse Error: foo', $e->getMessage());
  184. $this->assertSame(4, $e->getSeverity());
  185. $this->assertSame('foo.php', $e->getFile());
  186. $this->assertSame(12, $e->getLine());
  187. }
  188. restore_error_handler();
  189. restore_exception_handler();
  190. $handler = ErrorHandler::register();
  191. $handler->throwAt(E_USER_DEPRECATED, true);
  192. $this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
  193. restore_error_handler();
  194. restore_exception_handler();
  195. $handler = ErrorHandler::register();
  196. $handler->throwAt(E_DEPRECATED, true);
  197. $this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
  198. restore_error_handler();
  199. restore_exception_handler();
  200. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  201. $that = $this;
  202. $warnArgCheck = function ($logLevel, $message, $context) use ($that) {
  203. $that->assertEquals('info', $logLevel);
  204. $that->assertEquals('foo', $message);
  205. $that->assertArrayHasKey('type', $context);
  206. $that->assertEquals($context['type'], E_USER_DEPRECATED);
  207. $that->assertArrayHasKey('stack', $context);
  208. $that->assertInternalType('array', $context['stack']);
  209. };
  210. $logger
  211. ->expects($this->once())
  212. ->method('log')
  213. ->will($this->returnCallback($warnArgCheck))
  214. ;
  215. $handler = ErrorHandler::register();
  216. $handler->setDefaultLogger($logger, E_USER_DEPRECATED);
  217. $this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
  218. restore_error_handler();
  219. restore_exception_handler();
  220. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  221. $that = $this;
  222. $logArgCheck = function ($level, $message, $context) use ($that) {
  223. $that->assertEquals('Undefined variable: undefVar', $message);
  224. $that->assertArrayHasKey('type', $context);
  225. $that->assertEquals($context['type'], E_NOTICE);
  226. };
  227. $logger
  228. ->expects($this->once())
  229. ->method('log')
  230. ->will($this->returnCallback($logArgCheck))
  231. ;
  232. $handler = ErrorHandler::register();
  233. $handler->setDefaultLogger($logger, E_NOTICE);
  234. $handler->screamAt(E_NOTICE);
  235. unset($undefVar);
  236. @$undefVar++;
  237. restore_error_handler();
  238. restore_exception_handler();
  239. } catch (\Exception $e) {
  240. restore_error_handler();
  241. restore_exception_handler();
  242. throw $e;
  243. }
  244. }
  245. public function testHandleUserError()
  246. {
  247. try {
  248. $handler = ErrorHandler::register();
  249. $handler->throwAt(0, true);
  250. $e = null;
  251. $x = new \Exception('Foo');
  252. try {
  253. $f = new Fixtures\ToStringThrower($x);
  254. $f .= ''; // Trigger $f->__toString()
  255. } catch (\Exception $e) {
  256. }
  257. $this->assertSame($x, $e);
  258. restore_error_handler();
  259. restore_exception_handler();
  260. } catch (\Exception $e) {
  261. restore_error_handler();
  262. restore_exception_handler();
  263. throw $e;
  264. }
  265. }
  266. public function testHandleDeprecation()
  267. {
  268. $that = $this;
  269. $logArgCheck = function ($level, $message, $context) use ($that) {
  270. $that->assertEquals(LogLevel::INFO, $level);
  271. $that->assertArrayHasKey('level', $context);
  272. $that->assertEquals(E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED, $context['level']);
  273. $that->assertArrayHasKey('stack', $context);
  274. };
  275. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  276. $logger
  277. ->expects($this->once())
  278. ->method('log')
  279. ->will($this->returnCallback($logArgCheck))
  280. ;
  281. $handler = new ErrorHandler();
  282. $handler->setDefaultLogger($logger);
  283. @$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array());
  284. }
  285. /**
  286. * @group no-hhvm
  287. */
  288. public function testHandleException()
  289. {
  290. try {
  291. $handler = ErrorHandler::register();
  292. $exception = new \Exception('foo');
  293. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  294. $that = $this;
  295. $logArgCheck = function ($level, $message, $context) use ($that) {
  296. $that->assertEquals('Uncaught Exception: foo', $message);
  297. $that->assertArrayHasKey('type', $context);
  298. $that->assertEquals($context['type'], E_ERROR);
  299. };
  300. $logger
  301. ->expects($this->exactly(2))
  302. ->method('log')
  303. ->will($this->returnCallback($logArgCheck))
  304. ;
  305. $handler->setDefaultLogger($logger, E_ERROR);
  306. try {
  307. $handler->handleException($exception);
  308. $this->fail('Exception expected');
  309. } catch (\Exception $e) {
  310. $this->assertSame($exception, $e);
  311. }
  312. $that = $this;
  313. $handler->setExceptionHandler(function ($e) use ($exception, $that) {
  314. $that->assertSame($exception, $e);
  315. });
  316. $handler->handleException($exception);
  317. restore_error_handler();
  318. restore_exception_handler();
  319. } catch (\Exception $e) {
  320. restore_error_handler();
  321. restore_exception_handler();
  322. throw $e;
  323. }
  324. }
  325. public function testErrorStacking()
  326. {
  327. try {
  328. $handler = ErrorHandler::register();
  329. $handler->screamAt(E_USER_WARNING);
  330. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  331. $logger
  332. ->expects($this->exactly(2))
  333. ->method('log')
  334. ->withConsecutive(
  335. array($this->equalTo(LogLevel::WARNING), $this->equalTo('Dummy log')),
  336. array($this->equalTo(LogLevel::DEBUG), $this->equalTo('Silenced warning'))
  337. )
  338. ;
  339. $handler->setDefaultLogger($logger, array(E_USER_WARNING => LogLevel::WARNING));
  340. ErrorHandler::stackErrors();
  341. @trigger_error('Silenced warning', E_USER_WARNING);
  342. $logger->log(LogLevel::WARNING, 'Dummy log');
  343. ErrorHandler::unstackErrors();
  344. restore_error_handler();
  345. restore_exception_handler();
  346. } catch (\Exception $e) {
  347. restore_error_handler();
  348. restore_exception_handler();
  349. throw $e;
  350. }
  351. }
  352. public function testBootstrappingLogger()
  353. {
  354. $bootLogger = new BufferingLogger();
  355. $handler = new ErrorHandler($bootLogger);
  356. $loggers = array(
  357. E_DEPRECATED => array($bootLogger, LogLevel::INFO),
  358. E_USER_DEPRECATED => array($bootLogger, LogLevel::INFO),
  359. E_NOTICE => array($bootLogger, LogLevel::WARNING),
  360. E_USER_NOTICE => array($bootLogger, LogLevel::WARNING),
  361. E_STRICT => array($bootLogger, LogLevel::WARNING),
  362. E_WARNING => array($bootLogger, LogLevel::WARNING),
  363. E_USER_WARNING => array($bootLogger, LogLevel::WARNING),
  364. E_COMPILE_WARNING => array($bootLogger, LogLevel::WARNING),
  365. E_CORE_WARNING => array($bootLogger, LogLevel::WARNING),
  366. E_USER_ERROR => array($bootLogger, LogLevel::CRITICAL),
  367. E_RECOVERABLE_ERROR => array($bootLogger, LogLevel::CRITICAL),
  368. E_COMPILE_ERROR => array($bootLogger, LogLevel::CRITICAL),
  369. E_PARSE => array($bootLogger, LogLevel::CRITICAL),
  370. E_ERROR => array($bootLogger, LogLevel::CRITICAL),
  371. E_CORE_ERROR => array($bootLogger, LogLevel::CRITICAL),
  372. );
  373. $this->assertSame($loggers, $handler->setLoggers(array()));
  374. $handler->handleError(E_DEPRECATED, 'Foo message', __FILE__, 123, array());
  375. $expectedLog = array(LogLevel::INFO, 'Foo message', array('type' => E_DEPRECATED, 'file' => __FILE__, 'line' => 123, 'level' => error_reporting()));
  376. $logs = $bootLogger->cleanLogs();
  377. unset($logs[0][2]['stack']);
  378. $this->assertSame(array($expectedLog), $logs);
  379. $bootLogger->log($expectedLog[0], $expectedLog[1], $expectedLog[2]);
  380. $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  381. $mockLogger->expects($this->once())
  382. ->method('log')
  383. ->with(LogLevel::WARNING, 'Foo message', $expectedLog[2]);
  384. $handler->setLoggers(array(E_DEPRECATED => array($mockLogger, LogLevel::WARNING)));
  385. }
  386. /**
  387. * @group no-hhvm
  388. */
  389. public function testHandleFatalError()
  390. {
  391. try {
  392. $handler = ErrorHandler::register();
  393. $error = array(
  394. 'type' => E_PARSE,
  395. 'message' => 'foo',
  396. 'file' => 'bar',
  397. 'line' => 123,
  398. );
  399. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  400. $that = $this;
  401. $logArgCheck = function ($level, $message, $context) use ($that) {
  402. $that->assertEquals('Fatal Parse Error: foo', $message);
  403. $that->assertArrayHasKey('type', $context);
  404. $that->assertEquals($context['type'], E_PARSE);
  405. };
  406. $logger
  407. ->expects($this->once())
  408. ->method('log')
  409. ->will($this->returnCallback($logArgCheck))
  410. ;
  411. $handler->setDefaultLogger($logger, E_PARSE);
  412. $handler->handleFatalError($error);
  413. restore_error_handler();
  414. restore_exception_handler();
  415. } catch (\Exception $e) {
  416. restore_error_handler();
  417. restore_exception_handler();
  418. throw $e;
  419. }
  420. }
  421. /**
  422. * @requires PHP 7
  423. */
  424. public function testHandleErrorException()
  425. {
  426. $exception = new \Error("Class 'Foo' not found");
  427. $handler = new ErrorHandler();
  428. $handler->setExceptionHandler(function () use (&$args) {
  429. $args = \func_get_args();
  430. });
  431. $handler->handleException($exception);
  432. $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]);
  433. $this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
  434. }
  435. /**
  436. * @group no-hhvm
  437. */
  438. public function testHandleFatalErrorOnHHVM()
  439. {
  440. try {
  441. $handler = ErrorHandler::register();
  442. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  443. $logger
  444. ->expects($this->once())
  445. ->method('log')
  446. ->with(
  447. $this->equalTo(LogLevel::CRITICAL),
  448. $this->equalTo('Fatal Error: foo'),
  449. $this->equalTo(array(
  450. 'type' => 1,
  451. 'file' => 'bar',
  452. 'line' => 123,
  453. 'level' => -1,
  454. 'stack' => array(456),
  455. ))
  456. )
  457. ;
  458. $handler->setDefaultLogger($logger, E_ERROR);
  459. $error = array(
  460. 'type' => E_ERROR + 0x1000000, // This error level is used by HHVM for fatal errors
  461. 'message' => 'foo',
  462. 'file' => 'bar',
  463. 'line' => 123,
  464. 'context' => array(123),
  465. 'backtrace' => array(456),
  466. );
  467. \call_user_func_array(array($handler, 'handleError'), $error);
  468. $handler->handleFatalError($error);
  469. restore_error_handler();
  470. restore_exception_handler();
  471. } catch (\Exception $e) {
  472. restore_error_handler();
  473. restore_exception_handler();
  474. throw $e;
  475. }
  476. }
  477. /**
  478. * @group legacy
  479. */
  480. public function testLegacyInterface()
  481. {
  482. try {
  483. $handler = ErrorHandler::register(0);
  484. $this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, array()));
  485. restore_error_handler();
  486. restore_exception_handler();
  487. $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
  488. $that = $this;
  489. $logArgCheck = function ($level, $message, $context) use ($that) {
  490. $that->assertEquals('Undefined variable: undefVar', $message);
  491. $that->assertArrayHasKey('type', $context);
  492. $that->assertEquals($context['type'], E_NOTICE);
  493. };
  494. $logger
  495. ->expects($this->once())
  496. ->method('log')
  497. ->will($this->returnCallback($logArgCheck))
  498. ;
  499. $handler = ErrorHandler::register(E_NOTICE);
  500. @$handler->setLogger($logger, 'scream');
  501. unset($undefVar);
  502. @$undefVar++;
  503. restore_error_handler();
  504. restore_exception_handler();
  505. } catch (\Exception $e) {
  506. restore_error_handler();
  507. restore_exception_handler();
  508. throw $e;
  509. }
  510. }
  511. /**
  512. * @expectedException \Exception
  513. * @group no-hhvm
  514. */
  515. public function testCustomExceptionHandler()
  516. {
  517. $handler = new ErrorHandler();
  518. $handler->setExceptionHandler(function ($e) use ($handler) {
  519. $handler->handleException($e);
  520. });
  521. $handler->handleException(new \Exception());
  522. }
  523. }