ErrorTrapTest.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP Project
  13. * @since 4.4.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Error;
  17. use Cake\Console\TestSuite\StubConsoleOutput;
  18. use Cake\Core\Configure;
  19. use Cake\Error\ErrorLogger;
  20. use Cake\Error\ErrorTrap;
  21. use Cake\Error\FatalErrorException;
  22. use Cake\Error\PhpError;
  23. use Cake\Error\Renderer\ConsoleErrorRenderer;
  24. use Cake\Error\Renderer\HtmlErrorRenderer;
  25. use Cake\Error\Renderer\TextErrorRenderer;
  26. use Cake\Log\Log;
  27. use Cake\Routing\Router;
  28. use Cake\TestSuite\TestCase;
  29. class ErrorTrapTest extends TestCase
  30. {
  31. public function setUp(): void
  32. {
  33. parent::setUp();
  34. Log::drop('test_error');
  35. Router::reload();
  36. }
  37. public function testConfigErrorRendererFallback()
  38. {
  39. $trap = new ErrorTrap(['errorRenderer' => null]);
  40. $this->assertInstanceOf(ConsoleErrorRenderer::class, $trap->renderer());
  41. }
  42. public function testConfigErrorRenderer()
  43. {
  44. $trap = new ErrorTrap(['errorRenderer' => HtmlErrorRenderer::class]);
  45. $this->assertInstanceOf(HtmlErrorRenderer::class, $trap->renderer());
  46. }
  47. public function testConfigRendererHandleUnsafeOverwrite()
  48. {
  49. $trap = new ErrorTrap();
  50. $trap->setConfig('errorRenderer', null);
  51. $this->assertInstanceOf(ConsoleErrorRenderer::class, $trap->renderer());
  52. }
  53. public function testLoggerConfig()
  54. {
  55. $trap = new ErrorTrap(['logger' => ErrorLogger::class]);
  56. $this->assertInstanceOf(ErrorLogger::class, $trap->logger());
  57. }
  58. public function testLoggerHandleUnsafeOverwrite()
  59. {
  60. $trap = new ErrorTrap();
  61. $trap->setConfig('logger', null);
  62. $this->assertInstanceOf(ErrorLogger::class, $trap->logger());
  63. }
  64. public function testRegisterAndRendering()
  65. {
  66. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  67. $trap->register();
  68. ob_start();
  69. trigger_error('Oh no it was bad', E_USER_NOTICE);
  70. $output = ob_get_clean();
  71. restore_error_handler();
  72. $this->assertStringContainsString('Oh no it was bad', $output);
  73. }
  74. public function testRegisterAndHandleFatalUserError()
  75. {
  76. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  77. $trap->register();
  78. try {
  79. trigger_error('Oh no it was bad', E_USER_ERROR);
  80. $this->fail('Should raise a fatal error');
  81. } catch (FatalErrorException $e) {
  82. $this->assertEquals('Oh no it was bad', $e->getMessage());
  83. $this->assertEquals(E_USER_ERROR, $e->getCode());
  84. } finally {
  85. restore_error_handler();
  86. }
  87. }
  88. public static function logLevelProvider(): array
  89. {
  90. return [
  91. // PHP error level, expected log level
  92. [E_USER_WARNING, 'warning'],
  93. [E_USER_NOTICE, 'notice'],
  94. [E_USER_DEPRECATED, 'debug'],
  95. ];
  96. }
  97. /**
  98. * @dataProvider logLevelProvider
  99. */
  100. public function testHandleErrorLoggingLevel($level, $logLevel)
  101. {
  102. Log::setConfig('test_error', [
  103. 'className' => 'Array',
  104. ]);
  105. $trap = new ErrorTrap([
  106. 'errorRenderer' => TextErrorRenderer::class,
  107. ]);
  108. $trap->register();
  109. ob_start();
  110. trigger_error('Oh no it was bad', $level);
  111. ob_get_clean();
  112. restore_error_handler();
  113. $logs = Log::engine('test_error')->read();
  114. $this->assertStringContainsString('Oh no it was bad', $logs[0]);
  115. $this->assertStringContainsString($logLevel, $logs[0]);
  116. }
  117. public function testHandleErrorLogTrace()
  118. {
  119. Log::setConfig('test_error', [
  120. 'className' => 'Array',
  121. ]);
  122. $trap = new ErrorTrap([
  123. 'errorRenderer' => TextErrorRenderer::class,
  124. 'trace' => true,
  125. ]);
  126. $trap->register();
  127. ob_start();
  128. trigger_error('Oh no it was bad', E_USER_WARNING);
  129. ob_get_clean();
  130. restore_error_handler();
  131. $logs = Log::engine('test_error')->read();
  132. $this->assertStringContainsString('Oh no it was bad', $logs[0]);
  133. $this->assertStringContainsString('Trace:', $logs[0]);
  134. $this->assertStringContainsString('ErrorTrapTest->testHandleErrorLogTrace', $logs[0]);
  135. }
  136. public function testHandleErrorNoLog()
  137. {
  138. Log::setConfig('test_error', [
  139. 'className' => 'Array',
  140. ]);
  141. $trap = new ErrorTrap([
  142. 'log' => false,
  143. 'errorRenderer' => TextErrorRenderer::class,
  144. ]);
  145. $trap->register();
  146. ob_start();
  147. trigger_error('Oh no it was bad', E_USER_WARNING);
  148. ob_get_clean();
  149. restore_error_handler();
  150. $logs = Log::engine('test_error')->read();
  151. $this->assertEmpty($logs);
  152. }
  153. public function testConsoleRenderingNoTrace()
  154. {
  155. $stub = new StubConsoleOutput();
  156. $trap = new ErrorTrap([
  157. 'errorRenderer' => ConsoleErrorRenderer::class,
  158. 'trace' => false,
  159. 'stderr' => $stub,
  160. ]);
  161. $trap->register();
  162. ob_start();
  163. trigger_error('Oh no it was bad', E_USER_NOTICE);
  164. ob_get_clean();
  165. restore_error_handler();
  166. $out = $stub->messages();
  167. $this->assertStringContainsString('Oh no it was bad', $out[0]);
  168. $this->assertStringNotContainsString('Trace', $out[0]);
  169. }
  170. public function testConsoleRenderingWithTrace()
  171. {
  172. $stub = new StubConsoleOutput();
  173. $trap = new ErrorTrap([
  174. 'errorRenderer' => ConsoleErrorRenderer::class,
  175. 'trace' => true,
  176. 'stderr' => $stub,
  177. ]);
  178. $trap->register();
  179. ob_start();
  180. trigger_error('Oh no it was bad', E_USER_NOTICE);
  181. ob_get_clean();
  182. restore_error_handler();
  183. $out = $stub->messages();
  184. $this->assertStringContainsString('Oh no it was bad', $out[0]);
  185. $this->assertStringContainsString('Trace', $out[0]);
  186. $this->assertStringContainsString('ErrorTrapTest->testConsoleRenderingWithTrace', $out[0]);
  187. }
  188. public function testRegisterNoOutputDebug()
  189. {
  190. Log::setConfig('test_error', [
  191. 'className' => 'Array',
  192. ]);
  193. Configure::write('debug', false);
  194. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  195. $trap->register();
  196. ob_start();
  197. trigger_error('Oh no it was bad', E_USER_NOTICE);
  198. $output = ob_get_clean();
  199. restore_error_handler();
  200. $this->assertSame('', $output);
  201. }
  202. public function testRegisterIgnoredDeprecations()
  203. {
  204. $trap = new ErrorTrap([
  205. 'errorRenderer' => TextErrorRenderer::class,
  206. 'trace' => false,
  207. ]);
  208. $trap->register();
  209. ob_start();
  210. Configure::write('Error.ignoredDeprecationPaths', [
  211. 'tests/TestCase/Error/ErrorTrap*',
  212. ]);
  213. trigger_error('Should be ignored', E_USER_DEPRECATED);
  214. Configure::write('Error.ignoredDeprecationPaths', []);
  215. trigger_error('Not ignored', E_USER_DEPRECATED);
  216. $output = ob_get_clean();
  217. restore_error_handler();
  218. $this->assertStringNotContainsString('Should be ignored', $output);
  219. $this->assertStringContainsString('Not ignored', $output);
  220. }
  221. public function testEventTriggered()
  222. {
  223. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  224. $trap->register();
  225. $trap->getEventManager()->on('Error.beforeRender', function ($event, PhpError $error) {
  226. $this->assertEquals(E_USER_NOTICE, $error->getCode());
  227. $this->assertStringContainsString('Oh no it was bad', $error->getMessage());
  228. });
  229. ob_start();
  230. trigger_error('Oh no it was bad', E_USER_NOTICE);
  231. $out = ob_get_clean();
  232. restore_error_handler();
  233. $this->assertNotEmpty($out);
  234. }
  235. public function testEventTriggeredAbortRender()
  236. {
  237. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  238. $trap->register();
  239. $trap->getEventManager()->on('Error.beforeRender', function ($event, PhpError $error) {
  240. $this->assertEquals(E_USER_NOTICE, $error->getCode());
  241. $this->assertStringContainsString('Oh no it was bad', $error->getMessage());
  242. $event->stopPropagation();
  243. });
  244. ob_start();
  245. trigger_error('Oh no it was bad', E_USER_NOTICE);
  246. $out = ob_get_clean();
  247. restore_error_handler();
  248. $this->assertSame('', $out);
  249. }
  250. public function testEventReturnResponse(): void
  251. {
  252. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  253. $trap->register();
  254. $trap->getEventManager()->on('Error.beforeRender', function ($event, PhpError $error) {
  255. return "This ain't so bad";
  256. });
  257. ob_start();
  258. trigger_error('Oh no it was bad', E_USER_NOTICE);
  259. $out = ob_get_clean();
  260. restore_error_handler();
  261. $this->assertSame("This ain't so bad", $out);
  262. }
  263. }