ErrorTrapTest.php 9.7 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. use PHPUnit\Framework\Attributes\DataProvider;
  30. class ErrorTrapTest extends TestCase
  31. {
  32. public function setUp(): void
  33. {
  34. parent::setUp();
  35. Log::drop('test_error');
  36. Router::reload();
  37. }
  38. public function testConfigErrorRendererFallback(): void
  39. {
  40. $trap = new ErrorTrap(['errorRenderer' => null]);
  41. $this->assertInstanceOf(ConsoleErrorRenderer::class, $trap->renderer());
  42. }
  43. public function testConfigErrorRenderer(): void
  44. {
  45. $trap = new ErrorTrap(['errorRenderer' => HtmlErrorRenderer::class]);
  46. $this->assertInstanceOf(HtmlErrorRenderer::class, $trap->renderer());
  47. }
  48. public function testConfigRendererHandleUnsafeOverwrite(): void
  49. {
  50. $trap = new ErrorTrap();
  51. $trap->setConfig('errorRenderer', null);
  52. $this->assertInstanceOf(ConsoleErrorRenderer::class, $trap->renderer());
  53. }
  54. public function testLoggerConfig(): void
  55. {
  56. $trap = new ErrorTrap(['logger' => ErrorLogger::class]);
  57. $this->assertInstanceOf(ErrorLogger::class, $trap->logger());
  58. }
  59. public function testLoggerHandleUnsafeOverwrite(): void
  60. {
  61. $trap = new ErrorTrap();
  62. $trap->setConfig('logger', null);
  63. $this->assertInstanceOf(ErrorLogger::class, $trap->logger());
  64. }
  65. public function testRegisterAndRendering(): void
  66. {
  67. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  68. $trap->register();
  69. ob_start();
  70. trigger_error('Oh no it was bad', E_USER_NOTICE);
  71. $output = ob_get_clean();
  72. restore_error_handler();
  73. $this->assertStringContainsString('Oh no it was bad', $output);
  74. }
  75. public function testRegisterAndHandleFatalUserError(): void
  76. {
  77. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  78. $trap->register();
  79. try {
  80. trigger_error('Oh no it was bad', E_USER_ERROR);
  81. $this->fail('Should raise a fatal error');
  82. } catch (FatalErrorException $e) {
  83. $this->assertEquals('Oh no it was bad', $e->getMessage());
  84. $this->assertEquals(E_USER_ERROR, $e->getCode());
  85. } finally {
  86. restore_error_handler();
  87. }
  88. }
  89. public static function logLevelProvider(): array
  90. {
  91. return [
  92. // PHP error level, expected log level
  93. [E_USER_WARNING, 'warning'],
  94. [E_USER_NOTICE, 'notice'],
  95. // Log level is notice on windows because windows log levels are different.
  96. [E_USER_DEPRECATED, DS === '\\' ? 'notice' : 'debug'],
  97. ];
  98. }
  99. #[DataProvider('logLevelProvider')]
  100. public function testHandleErrorLoggingLevel($level, $logLevel): void
  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(): void
  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(): void
  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(): void
  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(): void
  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(): void
  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(): void
  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(): void
  222. {
  223. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  224. $trap->register();
  225. $trap->getEventManager()->on('Error.beforeRender', function ($event, PhpError $error): void {
  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(): void
  236. {
  237. $trap = new ErrorTrap(['errorRenderer' => TextErrorRenderer::class]);
  238. $trap->register();
  239. $trap->getEventManager()->on('Error.beforeRender', function ($event, PhpError $error): void {
  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. }