ErrorHandlerTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <?php
  2. /*
  3. * This file is part of the Monolog package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  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 Monolog;
  11. use Monolog\Handler\StreamHandler;
  12. use Monolog\Handler\TestHandler;
  13. use Psr\Log\LogLevel;
  14. class ErrorHandlerTest extends TestCase
  15. {
  16. private $asyncSignalHandling;
  17. private $blockedSignals;
  18. private $signalHandlers;
  19. protected function setUp()
  20. {
  21. $this->signalHandlers = array();
  22. if (extension_loaded('pcntl')) {
  23. if (function_exists('pcntl_async_signals')) {
  24. $this->asyncSignalHandling = pcntl_async_signals();
  25. }
  26. if (function_exists('pcntl_sigprocmask')) {
  27. pcntl_sigprocmask(SIG_BLOCK, array(), $this->blockedSignals);
  28. }
  29. }
  30. }
  31. protected function tearDown()
  32. {
  33. if ($this->asyncSignalHandling !== null) {
  34. pcntl_async_signals($this->asyncSignalHandling);
  35. }
  36. if ($this->blockedSignals !== null) {
  37. pcntl_sigprocmask(SIG_SETMASK, $this->blockedSignals);
  38. }
  39. if ($this->signalHandlers) {
  40. pcntl_signal_dispatch();
  41. foreach ($this->signalHandlers as $signo => $handler) {
  42. pcntl_signal($signo, $handler);
  43. }
  44. }
  45. }
  46. private function setSignalHandler($signo, $handler = SIG_DFL) {
  47. if (function_exists('pcntl_signal_get_handler')) {
  48. $this->signalHandlers[$signo] = pcntl_signal_get_handler($signo);
  49. } else {
  50. $this->signalHandlers[$signo] = SIG_DFL;
  51. }
  52. $this->assertTrue(pcntl_signal($signo, $handler));
  53. }
  54. public function testHandleError()
  55. {
  56. $logger = new Logger('test', array($handler = new TestHandler));
  57. $errHandler = new ErrorHandler($logger);
  58. $errHandler->registerErrorHandler(array(E_USER_NOTICE => Logger::EMERGENCY), false);
  59. trigger_error('Foo', E_USER_ERROR);
  60. $this->assertCount(1, $handler->getRecords());
  61. $this->assertTrue($handler->hasErrorRecords());
  62. trigger_error('Foo', E_USER_NOTICE);
  63. $this->assertCount(2, $handler->getRecords());
  64. $this->assertTrue($handler->hasEmergencyRecords());
  65. }
  66. public function testHandleSignal()
  67. {
  68. $logger = new Logger('test', array($handler = new TestHandler));
  69. $errHandler = new ErrorHandler($logger);
  70. $signo = 2; // SIGINT.
  71. $siginfo = array('signo' => $signo, 'errno' => 0, 'code' => 0);
  72. $errHandler->handleSignal($signo, $siginfo);
  73. $this->assertCount(1, $handler->getRecords());
  74. $this->assertTrue($handler->hasCriticalRecords());
  75. $records = $handler->getRecords();
  76. $this->assertSame($siginfo, $records[0]['context']);
  77. }
  78. /**
  79. * @depends testHandleSignal
  80. * @requires extension pcntl
  81. * @requires extension posix
  82. * @requires function pcntl_signal
  83. * @requires function pcntl_signal_dispatch
  84. * @requires function posix_getpid
  85. * @requires function posix_kill
  86. */
  87. public function testRegisterSignalHandler()
  88. {
  89. // SIGCONT and SIGURG should be ignored by default.
  90. if (!defined('SIGCONT') || !defined('SIGURG')) {
  91. $this->markTestSkipped('This test requires the SIGCONT and SIGURG pcntl constants.');
  92. }
  93. $this->setSignalHandler(SIGCONT, SIG_IGN);
  94. $this->setSignalHandler(SIGURG, SIG_IGN);
  95. $logger = new Logger('test', array($handler = new TestHandler));
  96. $errHandler = new ErrorHandler($logger);
  97. $pid = posix_getpid();
  98. $this->assertTrue(posix_kill($pid, SIGURG));
  99. $this->assertTrue(pcntl_signal_dispatch());
  100. $this->assertCount(0, $handler->getRecords());
  101. $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, false);
  102. $this->assertTrue(posix_kill($pid, SIGCONT));
  103. $this->assertTrue(pcntl_signal_dispatch());
  104. $this->assertCount(0, $handler->getRecords());
  105. $this->assertTrue(posix_kill($pid, SIGURG));
  106. $this->assertTrue(pcntl_signal_dispatch());
  107. $this->assertCount(1, $handler->getRecords());
  108. $this->assertTrue($handler->hasInfoThatContains('SIGURG'));
  109. }
  110. /**
  111. * @dataProvider defaultPreviousProvider
  112. * @depends testRegisterSignalHandler
  113. * @requires function pcntl_fork
  114. * @requires function pcntl_sigprocmask
  115. * @requires function pcntl_waitpid
  116. */
  117. public function testRegisterDefaultPreviousSignalHandler($signo, $callPrevious, $expected)
  118. {
  119. $this->setSignalHandler($signo, SIG_DFL);
  120. $path = tempnam(sys_get_temp_dir(), 'monolog-');
  121. $this->assertNotFalse($path);
  122. $pid = pcntl_fork();
  123. if ($pid === 0) { // Child.
  124. $streamHandler = new StreamHandler($path);
  125. $streamHandler->setFormatter($this->getIdentityFormatter());
  126. $logger = new Logger('test', array($streamHandler));
  127. $errHandler = new ErrorHandler($logger);
  128. $errHandler->registerSignalHandler($signo, LogLevel::INFO, $callPrevious, false, false);
  129. pcntl_sigprocmask(SIG_SETMASK, array(SIGCONT));
  130. posix_kill(posix_getpid(), $signo);
  131. pcntl_signal_dispatch();
  132. // If $callPrevious is true, SIGINT should terminate by this line.
  133. pcntl_sigprocmask(SIG_BLOCK, array(), $oldset);
  134. file_put_contents($path, implode(' ', $oldset), FILE_APPEND);
  135. posix_kill(posix_getpid(), $signo);
  136. pcntl_signal_dispatch();
  137. exit();
  138. }
  139. $this->assertNotSame(-1, $pid);
  140. $this->assertNotSame(-1, pcntl_waitpid($pid, $status));
  141. $this->assertNotSame(-1, $status);
  142. $this->assertSame($expected, file_get_contents($path));
  143. }
  144. public function defaultPreviousProvider()
  145. {
  146. if (!defined('SIGCONT') || !defined('SIGINT') || !defined('SIGURG')) {
  147. return array();
  148. }
  149. return array(
  150. array(SIGINT, false, 'Program received signal SIGINT'.SIGCONT.'Program received signal SIGINT'),
  151. array(SIGINT, true, 'Program received signal SIGINT'),
  152. array(SIGURG, false, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'),
  153. array(SIGURG, true, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'),
  154. );
  155. }
  156. /**
  157. * @dataProvider callablePreviousProvider
  158. * @depends testRegisterSignalHandler
  159. * @requires function pcntl_signal_get_handler
  160. */
  161. public function testRegisterCallablePreviousSignalHandler($callPrevious)
  162. {
  163. $this->setSignalHandler(SIGURG, SIG_IGN);
  164. $logger = new Logger('test', array($handler = new TestHandler));
  165. $errHandler = new ErrorHandler($logger);
  166. $previousCalled = 0;
  167. pcntl_signal(SIGURG, function ($signo, array $siginfo = null) use (&$previousCalled) {
  168. ++$previousCalled;
  169. });
  170. $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, $callPrevious, false, false);
  171. $this->assertTrue(posix_kill(posix_getpid(), SIGURG));
  172. $this->assertTrue(pcntl_signal_dispatch());
  173. $this->assertCount(1, $handler->getRecords());
  174. $this->assertTrue($handler->hasInfoThatContains('SIGURG'));
  175. $this->assertSame($callPrevious ? 1 : 0, $previousCalled);
  176. }
  177. public function callablePreviousProvider()
  178. {
  179. return array(
  180. array(false),
  181. array(true),
  182. );
  183. }
  184. /**
  185. * @dataProvider restartSyscallsProvider
  186. * @depends testRegisterDefaultPreviousSignalHandler
  187. * @requires function pcntl_fork
  188. * @requires function pcntl_waitpid
  189. */
  190. public function testRegisterSyscallRestartingSignalHandler($restartSyscalls)
  191. {
  192. $this->setSignalHandler(SIGURG, SIG_IGN);
  193. $parentPid = posix_getpid();
  194. $microtime = microtime(true);
  195. $pid = pcntl_fork();
  196. if ($pid === 0) { // Child.
  197. usleep(100000);
  198. posix_kill($parentPid, SIGURG);
  199. usleep(100000);
  200. exit();
  201. }
  202. $this->assertNotSame(-1, $pid);
  203. $logger = new Logger('test', array($handler = new TestHandler));
  204. $errHandler = new ErrorHandler($logger);
  205. $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, $restartSyscalls, false);
  206. if ($restartSyscalls) {
  207. // pcntl_wait is expected to be restarted after the signal handler.
  208. $this->assertNotSame(-1, pcntl_waitpid($pid, $status));
  209. } else {
  210. // pcntl_wait is expected to be interrupted when the signal handler is invoked.
  211. $this->assertSame(-1, pcntl_waitpid($pid, $status));
  212. }
  213. $this->assertSame($restartSyscalls, microtime(true) - $microtime > 0.15);
  214. $this->assertTrue(pcntl_signal_dispatch());
  215. $this->assertCount(1, $handler->getRecords());
  216. if ($restartSyscalls) {
  217. // The child has already exited.
  218. $this->assertSame(-1, pcntl_waitpid($pid, $status));
  219. } else {
  220. // The child has not exited yet.
  221. $this->assertNotSame(-1, pcntl_waitpid($pid, $status));
  222. }
  223. }
  224. public function restartSyscallsProvider()
  225. {
  226. return array(
  227. array(false),
  228. array(true),
  229. array(false),
  230. array(true),
  231. );
  232. }
  233. /**
  234. * @dataProvider asyncProvider
  235. * @depends testRegisterDefaultPreviousSignalHandler
  236. * @requires function pcntl_async_signals
  237. */
  238. public function testRegisterAsyncSignalHandler($initialAsync, $desiredAsync, $expectedBefore, $expectedAfter)
  239. {
  240. $this->setSignalHandler(SIGURG, SIG_IGN);
  241. pcntl_async_signals($initialAsync);
  242. $logger = new Logger('test', array($handler = new TestHandler));
  243. $errHandler = new ErrorHandler($logger);
  244. $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, $desiredAsync);
  245. $this->assertTrue(posix_kill(posix_getpid(), SIGURG));
  246. $this->assertCount($expectedBefore, $handler->getRecords());
  247. $this->assertTrue(pcntl_signal_dispatch());
  248. $this->assertCount($expectedAfter, $handler->getRecords());
  249. }
  250. public function asyncProvider()
  251. {
  252. return array(
  253. array(false, false, 0, 1),
  254. array(false, null, 0, 1),
  255. array(false, true, 1, 1),
  256. array(true, false, 0, 1),
  257. array(true, null, 1, 1),
  258. array(true, true, 1, 1),
  259. );
  260. }
  261. }