ErrorTrapTest.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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. // Log level is notice on windows because windows log levels are different.
  95. [E_USER_DEPRECATED, DS === '\\' ? 'notice' : 'debug'],
  96. ];
  97. }
  98. /**
  99. * @dataProvider logLevelProvider
  100. */
  101. public function testHandleErrorLoggingLevel($level, $logLevel)
  102. {
  103. Log::setConfig('test_error', [
  104. 'className' => 'Array',
  105. ]);
  106. $trap = new ErrorTrap([
  107. 'errorRenderer' => TextErrorRenderer::class,
  108. ]);
  109. $trap->register();
  110. ob_start();
  111. trigger_error('Oh no it was bad', $level);
  112. ob_get_clean();
  113. restore_error_handler();
  114. $logs = Log::engine('test_error')->read();
  115. $this->assertStringContainsString('Oh no it was bad', $logs[0]);
  116. $this->assertStringContainsString($logLevel, $logs[0]);
  117. }
  118. public function testHandleErrorLogTrace()
  119. {
  120. Log::setConfig('test_error', [
  121. 'className' => 'Array',
  122. ]);
  123. $trap = new ErrorTrap([
  124. 'errorRenderer' => TextErrorRenderer::class,
  125. 'trace' => true,
  126. ]);
  127. $trap->register();
  128. ob_start();
  129. trigger_error('Oh no it was bad', E_USER_WARNING);
  130. ob_get_clean();
  131. restore_error_handler();
  132. $logs = Log::engine('test_error')->read();
  133. $this->assertStringContainsString('Oh no it was bad', $logs[0]);
  134. $this->assertStringContainsString('Trace:', $logs[0]);
  135. $this->assertStringContainsString('ErrorTrapTest->testHandleErrorLogTrace', $logs[0]);
  136. }
  137. public function testHandleErrorNoLog()
  138. {
  139. Log::setConfig('test_error', [
  140. 'className' => 'Array',
  141. ]);
  142. $trap = new ErrorTrap([
  143. 'log' => false,
  144. 'errorRenderer' => TextErrorRenderer::class,
  145. ]);
  146. $trap->register();
  147. ob_start();
  148. trigger_error('Oh no it was bad', E_USER_WARNING);
  149. ob_get_clean();
  150. restore_error_handler();
  151. $logs = Log::engine('test_error')->read();
  152. $this->assertEmpty($logs);
  153. }
  154. public function testConsoleRenderingNoTrace()
  155. {
  156. $stub = new StubConsoleOutput();
  157. $trap = new ErrorTrap([
  158. 'errorRenderer' => ConsoleErrorRenderer::class,
  159. 'trace' => false,
  160. 'stderr' => $stub,
  161. ]);
  162. $trap->register();
  163. ob_start();
  164. trigger_error('Oh no it was bad', E_USER_NOTICE);
  165. ob_get_clean();
  166. restore_error_handler();
  167. $out = $stub->messages();
  168. $this->assertStringContainsString('Oh no it was bad', $out[0]);
  169. $this->assertStringNotContainsString('Trace', $out[0]);
  170. }
  171. public function testConsoleRenderingWithTrace()
  172. {
  173. $stub = new StubConsoleOutput();
  174. $trap = new ErrorTrap([
  175. 'errorRenderer' => ConsoleErrorRenderer::class,
  176. 'trace' => true,
  177. 'stderr' => $stub,
  178. ]);
  179. $trap->register();
  180. ob_start();
  181. trigger_error('Oh no it was bad', E_USER_NOTICE);
  182. ob_get_clean();
  183. restore_error_handler();
  184. $out = $stub->messages();
  185. $this->assertStringContainsString('Oh no it was bad', $out[0]);
  186. $this->assertStringContainsString('Trace', $out[0]);
  187. $this->assertStringContainsString('ErrorTrapTest->testConsoleRenderingWithTrace', $out[0]);
  188. }
  189. public function testRegisterNoOutputDebug()
  190. {
  191. Log::setConfig('test_error', [
  192. 'className' => 'Array',
  193. ]);
  194. Configure::write('debug', false);
  195. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  196. $trap->register();
  197. ob_start();
  198. trigger_error('Oh no it was bad', E_USER_NOTICE);
  199. $output = ob_get_clean();
  200. restore_error_handler();
  201. $this->assertSame('', $output);
  202. }
  203. public function testRegisterIgnoredDeprecations()
  204. {
  205. $trap = new ErrorTrap([
  206. 'errorRenderer' => TextErrorRenderer::class,
  207. 'trace' => false,
  208. ]);
  209. $trap->register();
  210. ob_start();
  211. Configure::write('Error.ignoredDeprecationPaths', [
  212. 'tests/TestCase/Error/ErrorTrap*',
  213. ]);
  214. trigger_error('Should be ignored', E_USER_DEPRECATED);
  215. Configure::write('Error.ignoredDeprecationPaths', []);
  216. trigger_error('Not ignored', E_USER_DEPRECATED);
  217. $output = ob_get_clean();
  218. restore_error_handler();
  219. $this->assertStringNotContainsString('Should be ignored', $output);
  220. $this->assertStringContainsString('Not ignored', $output);
  221. }
  222. public function testEventTriggered()
  223. {
  224. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  225. $trap->register();
  226. $trap->getEventManager()->on('Error.beforeRender', function ($event, PhpError $error) {
  227. $this->assertEquals(E_USER_NOTICE, $error->getCode());
  228. $this->assertStringContainsString('Oh no it was bad', $error->getMessage());
  229. });
  230. ob_start();
  231. trigger_error('Oh no it was bad', E_USER_NOTICE);
  232. $out = ob_get_clean();
  233. restore_error_handler();
  234. $this->assertNotEmpty($out);
  235. }
  236. public function testEventTriggeredAbortRender()
  237. {
  238. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  239. $trap->register();
  240. $trap->getEventManager()->on('Error.beforeRender', function ($event, PhpError $error) {
  241. $this->assertEquals(E_USER_NOTICE, $error->getCode());
  242. $this->assertStringContainsString('Oh no it was bad', $error->getMessage());
  243. $event->stopPropagation();
  244. });
  245. ob_start();
  246. trigger_error('Oh no it was bad', E_USER_NOTICE);
  247. $out = ob_get_clean();
  248. restore_error_handler();
  249. $this->assertSame('', $out);
  250. }
  251. public function testEventReturnResponse(): void
  252. {
  253. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  254. $trap->register();
  255. $trap->getEventManager()->on('Error.beforeRender', function ($event, PhpError $error) {
  256. return "This ain't so bad";
  257. });
  258. ob_start();
  259. trigger_error('Oh no it was bad', E_USER_NOTICE);
  260. $out = ob_get_clean();
  261. restore_error_handler();
  262. $this->assertSame("This ain't so bad", $out);
  263. }
  264. }