DebuggerTest.php 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  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 1.2.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Error;
  17. use Cake\Controller\Controller;
  18. use Cake\Core\Configure;
  19. use Cake\Error\Debug\ConsoleFormatter;
  20. use Cake\Error\Debug\HtmlFormatter;
  21. use Cake\Error\Debug\NodeInterface;
  22. use Cake\Error\Debug\ScalarNode;
  23. use Cake\Error\Debug\SpecialNode;
  24. use Cake\Error\Debug\TextFormatter;
  25. use Cake\Error\Debugger;
  26. use Cake\Form\Form;
  27. use Cake\Log\Log;
  28. use Cake\TestSuite\TestCase;
  29. use RuntimeException;
  30. use SplFixedArray;
  31. use stdClass;
  32. use TestApp\Error\TestDebugger;
  33. use TestApp\Error\Thing\DebuggableThing;
  34. use TestApp\Error\Thing\SecurityThing;
  35. use TestApp\Utility\ThrowsDebugInfo;
  36. /**
  37. * DebuggerTest class
  38. *
  39. * !!! Be careful with changing code below as it may
  40. * !!! change line numbers which are used in the tests
  41. */
  42. class DebuggerTest extends TestCase
  43. {
  44. /**
  45. * @var bool
  46. */
  47. protected $restoreError = false;
  48. /**
  49. * setUp method
  50. *
  51. * @return void
  52. */
  53. public function setUp(): void
  54. {
  55. parent::setUp();
  56. Configure::write('debug', true);
  57. Log::drop('stderr');
  58. Log::drop('stdout');
  59. Debugger::configInstance('exportFormatter', TextFormatter::class);
  60. }
  61. /**
  62. * tearDown method
  63. *
  64. * @return void
  65. */
  66. public function tearDown(): void
  67. {
  68. parent::tearDown();
  69. if ($this->restoreError) {
  70. restore_error_handler();
  71. }
  72. }
  73. /**
  74. * testDocRef method
  75. *
  76. * @return void
  77. */
  78. public function testDocRef()
  79. {
  80. ini_set('docref_root', '');
  81. $this->assertEquals(ini_get('docref_root'), '');
  82. // Force a new instance.
  83. Debugger::getInstance(TestDebugger::class);
  84. Debugger::getInstance(Debugger::class);
  85. $this->assertEquals(ini_get('docref_root'), 'https://secure.php.net/');
  86. }
  87. /**
  88. * test Excerpt writing
  89. *
  90. * @return void
  91. */
  92. public function testExcerpt()
  93. {
  94. $result = Debugger::excerpt(__FILE__, __LINE__ - 1, 2);
  95. $this->assertIsArray($result);
  96. $this->assertCount(5, $result);
  97. $this->assertMatchesRegularExpression('/function(.+)testExcerpt/', $result[1]);
  98. $result = Debugger::excerpt(__FILE__, 2, 2);
  99. $this->assertIsArray($result);
  100. $this->assertCount(4, $result);
  101. $this->skipIf(defined('HHVM_VERSION'), 'HHVM does not highlight php code');
  102. $pattern = '/<code>.*?<span style\="color\: \#\d+">.*?&lt;\?php/';
  103. $this->assertMatchesRegularExpression($pattern, $result[0]);
  104. $result = Debugger::excerpt(__FILE__, 11, 2);
  105. $this->assertCount(5, $result);
  106. $pattern = '/<span style\="color\: \#\d{6}">.*?<\/span>/';
  107. $this->assertMatchesRegularExpression($pattern, $result[0]);
  108. $return = Debugger::excerpt('[internal]', 2, 2);
  109. $this->assertEmpty($return);
  110. $result = Debugger::excerpt(__FILE__, __LINE__, 5);
  111. $this->assertCount(11, $result);
  112. $this->assertStringContainsString('Debugger', $result[5]);
  113. $this->assertStringContainsString('excerpt', $result[5]);
  114. $this->assertStringContainsString('__FILE__', $result[5]);
  115. $result = Debugger::excerpt(__FILE__, 1, 2);
  116. $this->assertCount(3, $result);
  117. $lastLine = count(explode("\n", file_get_contents(__FILE__)));
  118. $result = Debugger::excerpt(__FILE__, $lastLine, 2);
  119. $this->assertCount(3, $result);
  120. }
  121. /**
  122. * Test that setOutputFormat works.
  123. *
  124. * @return void
  125. */
  126. public function testSetOutputFormat()
  127. {
  128. Debugger::setOutputFormat('html');
  129. $this->assertSame('html', Debugger::getOutputFormat());
  130. }
  131. /**
  132. * Test that getOutputFormat/setOutputFormat works.
  133. *
  134. * @return void
  135. */
  136. public function testGetSetOutputFormat()
  137. {
  138. Debugger::setOutputFormat('html');
  139. $this->assertSame('html', Debugger::getOutputFormat());
  140. }
  141. /**
  142. * Test that choosing a nonexistent format causes an exception
  143. *
  144. * @return void
  145. */
  146. public function testSetOutputAsException()
  147. {
  148. $this->expectException(\InvalidArgumentException::class);
  149. Debugger::setOutputFormat('Invalid junk');
  150. }
  151. /**
  152. * Test outputError with description encoding
  153. *
  154. * @return void
  155. */
  156. public function testOutputErrorDescriptionEncoding()
  157. {
  158. Debugger::setOutputFormat('html');
  159. ob_start();
  160. $debugger = Debugger::getInstance();
  161. $debugger->outputError([
  162. 'error' => 'Notice',
  163. 'code' => E_NOTICE,
  164. 'level' => E_NOTICE,
  165. 'description' => 'Undefined index <script>alert(1)</script>',
  166. 'file' => __FILE__,
  167. 'line' => __LINE__,
  168. ]);
  169. $result = ob_get_clean();
  170. $this->assertStringContainsString('&lt;script&gt;', $result);
  171. $this->assertStringNotContainsString('<script>', $result);
  172. }
  173. /**
  174. * Tests that the correct line is being highlighted.
  175. *
  176. * @return void
  177. */
  178. public function testOutputErrorLineHighlight()
  179. {
  180. Debugger::setOutputFormat('js');
  181. ob_start();
  182. $debugger = Debugger::getInstance();
  183. $data = [
  184. 'level' => E_NOTICE,
  185. 'code' => E_NOTICE,
  186. 'file' => __FILE__,
  187. 'line' => __LINE__,
  188. 'description' => 'Error description',
  189. 'start' => 1,
  190. ];
  191. $debugger->outputError($data);
  192. $result = ob_get_clean();
  193. $this->assertMatchesRegularExpression('#^\<span class\="code\-highlight"\>.*outputError.*\</span\>$#m', $result);
  194. }
  195. /**
  196. * Tests that changes in output formats using Debugger::output() change the templates used.
  197. *
  198. * @return void
  199. */
  200. public function testAddFormat()
  201. {
  202. Debugger::addFormat('js', [
  203. 'traceLine' => '{:reference} - <a href="txmt://open?url=file://{:file}' .
  204. '&line={:line}">{:path}</a>, line {:line}',
  205. ]);
  206. Debugger::setOutputFormat('js');
  207. $result = Debugger::trace();
  208. $this->assertMatchesRegularExpression('/' . preg_quote('txmt://open?url=file://', '/') . '(\/|[A-Z]:\\\\)' . '/', $result);
  209. Debugger::addFormat('xml', [
  210. 'error' => '<error><code>{:code}</code><file>{:file}</file><line>{:line}</line>' .
  211. '{:description}</error>',
  212. ]);
  213. Debugger::setOutputFormat('xml');
  214. ob_start();
  215. $debugger = Debugger::getInstance();
  216. $debugger->outputError([
  217. 'level' => E_NOTICE,
  218. 'code' => E_NOTICE,
  219. 'file' => __FILE__,
  220. 'line' => __LINE__,
  221. 'description' => 'Undefined variable: foo',
  222. ]);
  223. $result = ob_get_clean();
  224. $expected = [
  225. '<error',
  226. '<code', '8', '/code',
  227. '<file', 'preg:/[^<]+/', '/file',
  228. '<line', '' . ((int)__LINE__ - 9), '/line',
  229. 'preg:/Undefined variable:\s+foo/',
  230. '/error',
  231. ];
  232. $this->assertHtml($expected, $result, true);
  233. }
  234. /**
  235. * Test adding a format that is handled by a callback.
  236. *
  237. * @return void
  238. */
  239. public function testAddFormatCallback()
  240. {
  241. Debugger::addFormat('callback', ['callback' => [$this, 'customFormat']]);
  242. Debugger::setOutputFormat('callback');
  243. ob_start();
  244. $debugger = Debugger::getInstance();
  245. $debugger->outputError([
  246. 'error' => 'Notice',
  247. 'code' => E_NOTICE,
  248. 'level' => E_NOTICE,
  249. 'description' => 'Undefined variable $foo',
  250. 'file' => __FILE__,
  251. 'line' => __LINE__,
  252. ]);
  253. $result = ob_get_clean();
  254. $this->assertStringContainsString('Notice: I eated an error', $result);
  255. $this->assertStringContainsString('DebuggerTest.php', $result);
  256. Debugger::setOutputFormat('js');
  257. }
  258. /**
  259. * Test method for testing addFormat with callbacks.
  260. *
  261. * @return void
  262. */
  263. public function customFormat(array $error, array $strings)
  264. {
  265. echo $error['error'] . ': I eated an error ' . $error['file'];
  266. }
  267. /**
  268. * testTrimPath method
  269. *
  270. * @return void
  271. */
  272. public function testTrimPath()
  273. {
  274. $this->assertSame('APP/', Debugger::trimPath(APP));
  275. $this->assertSame('CORE' . DS . 'src' . DS, Debugger::trimPath(CAKE));
  276. $this->assertSame('Some/Other/Path', Debugger::trimPath('Some/Other/Path'));
  277. }
  278. /**
  279. * testExportVar method
  280. *
  281. * @return void
  282. */
  283. public function testExportVar()
  284. {
  285. $Controller = new Controller();
  286. $Controller->viewBuilder()->setHelpers(['Html', 'Form']);
  287. $View = $Controller->createView();
  288. $View->int = 2;
  289. $View->float = 1.333;
  290. $View->string = ' ';
  291. $result = Debugger::exportVar($View);
  292. $expected = <<<TEXT
  293. object(Cake\View\View) id:0 {
  294. Html => object(Cake\View\Helper\HtmlHelper) id:1 {}
  295. Form => object(Cake\View\Helper\FormHelper) id:2 {}
  296. int => (int) 2
  297. float => (float) 1.333
  298. string => ' '
  299. [protected] _helpers => object(Cake\View\HelperRegistry) id:3 {}
  300. [protected] Blocks => object(Cake\View\ViewBlock) id:4 {}
  301. [protected] plugin => null
  302. [protected] name => ''
  303. [protected] helpers => [
  304. (int) 0 => 'Html',
  305. (int) 1 => 'Form'
  306. ]
  307. [protected] templatePath => ''
  308. [protected] template => null
  309. [protected] layout => 'default'
  310. [protected] layoutPath => ''
  311. [protected] autoLayout => true
  312. [protected] viewVars => []
  313. [protected] _ext => '.php'
  314. [protected] subDir => ''
  315. [protected] theme => null
  316. [protected] request => object(Cake\Http\ServerRequest) id:5 {}
  317. [protected] response => object(Cake\Http\Response) id:6 {}
  318. [protected] elementCache => 'default'
  319. [protected] _passedVars => [
  320. (int) 0 => 'viewVars',
  321. (int) 1 => 'autoLayout',
  322. (int) 2 => 'helpers',
  323. (int) 3 => 'template',
  324. (int) 4 => 'layout',
  325. (int) 5 => 'name',
  326. (int) 6 => 'theme',
  327. (int) 7 => 'layoutPath',
  328. (int) 8 => 'templatePath',
  329. (int) 9 => 'plugin'
  330. ]
  331. [protected] _defaultConfig => []
  332. [protected] _paths => []
  333. [protected] _pathsForPlugin => []
  334. [protected] _parents => []
  335. [protected] _current => null
  336. [protected] _currentType => ''
  337. [protected] _stack => []
  338. [protected] _viewBlockClass => 'Cake\View\ViewBlock'
  339. [protected] _eventManager => object(Cake\Event\EventManager) id:7 {}
  340. [protected] _eventClass => 'Cake\Event\Event'
  341. [protected] _config => []
  342. [protected] _configInitialized => true
  343. }
  344. TEXT;
  345. $this->assertTextEquals($expected, $result);
  346. $data = [
  347. 1 => 'Index one',
  348. 5 => 'Index five',
  349. ];
  350. $result = Debugger::exportVar($data);
  351. $expected = <<<TEXT
  352. [
  353. (int) 1 => 'Index one',
  354. (int) 5 => 'Index five'
  355. ]
  356. TEXT;
  357. $this->assertTextEquals($expected, $result);
  358. $data = [
  359. 'key' => [
  360. 'value',
  361. ],
  362. ];
  363. $result = Debugger::exportVar($data, 1);
  364. $expected = <<<TEXT
  365. [
  366. 'key' => [
  367. '' => [maximum depth reached]
  368. ]
  369. ]
  370. TEXT;
  371. $this->assertTextEquals($expected, $result);
  372. $data = false;
  373. $result = Debugger::exportVar($data);
  374. $expected = <<<TEXT
  375. false
  376. TEXT;
  377. $this->assertTextEquals($expected, $result);
  378. $file = fopen('php://output', 'w');
  379. fclose($file);
  380. $result = Debugger::exportVar($file);
  381. $this->assertStringContainsString('(resource (closed)) Resource id #', $result);
  382. }
  383. public function testExportVarTypedProperty()
  384. {
  385. $this->skipIf(version_compare(PHP_VERSION, '7.4.0', '<'), 'typed properties require PHP7.4');
  386. // This is gross but was simpler than adding a fixture file.
  387. // phpcs:ignore
  388. eval('class MyClass { private string $field; }');
  389. $obj = new \MyClass();
  390. $out = Debugger::exportVar($obj);
  391. $this->assertTextContains('field => [uninitialized]', $out);
  392. }
  393. /**
  394. * Test exporting various kinds of false.
  395. *
  396. * @return void
  397. */
  398. public function testExportVarZero()
  399. {
  400. $data = [
  401. 'nothing' => '',
  402. 'null' => null,
  403. 'false' => false,
  404. 'szero' => '0',
  405. 'zero' => 0,
  406. ];
  407. $result = Debugger::exportVar($data);
  408. $expected = <<<TEXT
  409. [
  410. 'nothing' => '',
  411. 'null' => null,
  412. 'false' => false,
  413. 'szero' => '0',
  414. 'zero' => (int) 0
  415. ]
  416. TEXT;
  417. $this->assertTextEquals($expected, $result);
  418. }
  419. /**
  420. * test exportVar with cyclic objects.
  421. *
  422. * @return void
  423. */
  424. public function testExportVarCyclicRef()
  425. {
  426. $parent = new stdClass();
  427. $parent->name = 'cake';
  428. $middle = new stdClass();
  429. $parent->child = $middle;
  430. $middle->name = 'php';
  431. $middle->child = $parent;
  432. $result = Debugger::exportVar($parent, 6);
  433. $expected = <<<TEXT
  434. object(stdClass) id:0 {
  435. name => 'cake'
  436. child => object(stdClass) id:1 {
  437. name => 'php'
  438. child => object(stdClass) id:0 {}
  439. }
  440. }
  441. TEXT;
  442. $this->assertTextEquals($expected, $result);
  443. }
  444. /**
  445. * test exportVar with array objects
  446. *
  447. * @return void
  448. */
  449. public function testExportVarSplFixedArray()
  450. {
  451. $subject = new SplFixedArray(2);
  452. $subject[0] = 'red';
  453. $subject[1] = 'blue';
  454. $result = Debugger::exportVar($subject, 6);
  455. $expected = <<<TEXT
  456. object(SplFixedArray) id:0 {
  457. 0 => 'red'
  458. 1 => 'blue'
  459. }
  460. TEXT;
  461. $this->assertTextEquals($expected, $result);
  462. }
  463. /**
  464. * Tests plain text variable export.
  465. *
  466. * @return void
  467. */
  468. public function testExportVarAsPlainText()
  469. {
  470. Debugger::configInstance('exportFormatter', null);
  471. $result = Debugger::exportVarAsPlainText(123);
  472. $this->assertSame('(int) 123', $result);
  473. Debugger::configInstance('exportFormatter', ConsoleFormatter::class);
  474. $result = Debugger::exportVarAsPlainText(123);
  475. $this->assertSame('(int) 123', $result);
  476. }
  477. /**
  478. * test exportVar with cyclic objects.
  479. *
  480. * @return void
  481. */
  482. public function testExportVarDebugInfo()
  483. {
  484. $form = new Form();
  485. $result = Debugger::exportVar($form, 6);
  486. $this->assertStringContainsString("'_schema' => [", $result, 'Has debuginfo keys');
  487. $this->assertStringContainsString("'_validator' => [", $result);
  488. }
  489. /**
  490. * Test exportVar with an exception during __debugInfo()
  491. *
  492. * @return void
  493. */
  494. public function testExportVarInvalidDebugInfo()
  495. {
  496. $result = Debugger::exportVar(new ThrowsDebugInfo());
  497. $expected = '(unable to export object: from __debugInfo)';
  498. $this->assertTextEquals($expected, $result);
  499. }
  500. /**
  501. * Text exportVarAsNodes()
  502. *
  503. * @return void
  504. */
  505. public function testExportVarAsNodes()
  506. {
  507. $data = [
  508. 1 => 'Index one',
  509. 5 => 'Index five',
  510. ];
  511. $result = Debugger::exportVarAsNodes($data);
  512. $this->assertInstanceOf(NodeInterface::class, $result);
  513. $this->assertCount(2, $result->getChildren());
  514. /** @var \Cake\Error\Debug\ArrayItemNode $item */
  515. $item = $result->getChildren()[0];
  516. $key = new ScalarNode('int', 1);
  517. $this->assertEquals($key, $item->getKey());
  518. $value = new ScalarNode('string', 'Index one');
  519. $this->assertEquals($value, $item->getValue());
  520. $data = [
  521. 'key' => [
  522. 'value',
  523. ],
  524. ];
  525. $result = Debugger::exportVarAsNodes($data, 1);
  526. $item = $result->getChildren()[0];
  527. $nestedItem = $item->getValue()->getChildren()[0];
  528. $expected = new SpecialNode('[maximum depth reached]');
  529. $this->assertEquals($expected, $nestedItem->getValue());
  530. }
  531. /**
  532. * testLog method
  533. *
  534. * @return void
  535. */
  536. public function testLog()
  537. {
  538. Log::setConfig('test', [
  539. 'className' => 'Array',
  540. ]);
  541. Debugger::log('cool');
  542. Debugger::log(['whatever', 'here']);
  543. $messages = Log::engine('test')->read();
  544. $this->assertCount(2, $messages);
  545. $this->assertStringContainsString('DebuggerTest::testLog', $messages[0]);
  546. $this->assertStringContainsString('cool', $messages[0]);
  547. $this->assertStringContainsString('DebuggerTest::testLog', $messages[1]);
  548. $this->assertStringContainsString('[main]', $messages[1]);
  549. $this->assertStringContainsString("'whatever'", $messages[1]);
  550. $this->assertStringContainsString("'here'", $messages[1]);
  551. Log::drop('test');
  552. }
  553. /**
  554. * Tests that logging does not apply formatting.
  555. *
  556. * @return void
  557. */
  558. public function testLogShouldNotApplyFormatting()
  559. {
  560. Log::setConfig('test', [
  561. 'className' => 'Array',
  562. ]);
  563. Debugger::configInstance('exportFormatter', null);
  564. Debugger::log(123);
  565. $messages = implode('', Log::engine('test')->read());
  566. Log::engine('test')->clear();
  567. $this->assertStringContainsString('(int) 123', $messages);
  568. $this->assertStringNotContainsString("\033[0m", $messages);
  569. Debugger::configInstance('exportFormatter', HtmlFormatter::class);
  570. Debugger::log(123);
  571. $messages = implode('', Log::engine('test')->read());
  572. Log::engine('test')->clear();
  573. $this->assertStringContainsString('(int) 123', $messages);
  574. $this->assertStringNotContainsString('<style', $messages);
  575. Debugger::configInstance('exportFormatter', ConsoleFormatter::class);
  576. Debugger::log(123);
  577. $messages = implode('', Log::engine('test')->read());
  578. Log::engine('test')->clear();
  579. $this->assertStringContainsString('(int) 123', $messages);
  580. $this->assertStringNotContainsString("\033[0m", $messages);
  581. Log::drop('test');
  582. }
  583. /**
  584. * test log() depth
  585. *
  586. * @return void
  587. */
  588. public function testLogDepth()
  589. {
  590. Log::setConfig('test', [
  591. 'className' => 'Array',
  592. ]);
  593. $val = [
  594. 'test' => ['key' => 'val'],
  595. ];
  596. Debugger::log($val, 'debug', 0);
  597. $messages = Log::engine('test')->read();
  598. $this->assertStringContainsString('DebuggerTest::testLogDepth', $messages[0]);
  599. $this->assertStringContainsString('test', $messages[0]);
  600. $this->assertStringNotContainsString('val', $messages[0]);
  601. }
  602. /**
  603. * testDump method
  604. *
  605. * @return void
  606. */
  607. public function testDump()
  608. {
  609. $var = ['People' => [
  610. [
  611. 'name' => 'joeseph',
  612. 'coat' => 'technicolor',
  613. 'hair_color' => 'brown',
  614. ],
  615. [
  616. 'name' => 'Shaft',
  617. 'coat' => 'black',
  618. 'hair' => 'black',
  619. ],
  620. ]];
  621. ob_start();
  622. Debugger::dump($var);
  623. $result = ob_get_clean();
  624. $open = "\n";
  625. $close = "\n\n";
  626. $expected = <<<TEXT
  627. {$open}[
  628. 'People' => [
  629. (int) 0 => [
  630. 'name' => 'joeseph',
  631. 'coat' => 'technicolor',
  632. 'hair_color' => 'brown'
  633. ],
  634. (int) 1 => [
  635. 'name' => 'Shaft',
  636. 'coat' => 'black',
  637. 'hair' => 'black'
  638. ]
  639. ]
  640. ]{$close}
  641. TEXT;
  642. $this->assertTextEquals($expected, $result);
  643. ob_start();
  644. Debugger::dump($var, 1);
  645. $result = ob_get_clean();
  646. $expected = <<<TEXT
  647. {$open}[
  648. 'People' => [
  649. '' => [maximum depth reached]
  650. ]
  651. ]{$close}
  652. TEXT;
  653. $this->assertTextEquals($expected, $result);
  654. }
  655. /**
  656. * test getInstance.
  657. *
  658. * @return void
  659. */
  660. public function testGetInstance()
  661. {
  662. $result = Debugger::getInstance();
  663. $exporter = $result->getConfig('exportFormatter');
  664. $this->assertInstanceOf(Debugger::class, $result);
  665. $result = Debugger::getInstance(TestDebugger::class);
  666. $this->assertInstanceOf(TestDebugger::class, $result);
  667. $result = Debugger::getInstance();
  668. $this->assertInstanceOf(TestDebugger::class, $result);
  669. $result = Debugger::getInstance(Debugger::class);
  670. $this->assertInstanceOf(Debugger::class, $result);
  671. $result->setConfig('exportFormatter', $exporter);
  672. }
  673. /**
  674. * Test that exportVar() will stop traversing recursive arrays.
  675. *
  676. * @return void
  677. */
  678. public function testExportVarRecursion()
  679. {
  680. $array = [];
  681. $array['foo'] = &$array;
  682. $output = Debugger::exportVar($array);
  683. $this->assertMatchesRegularExpression("/'foo' => \[\s+'' \=\> \[maximum depth reached\]/", $output);
  684. }
  685. /**
  686. * test trace exclude
  687. *
  688. * @return void
  689. */
  690. public function testTraceExclude()
  691. {
  692. $result = Debugger::trace();
  693. $this->assertMatchesRegularExpression('/^Cake\\\Test\\\TestCase\\\Error\\\DebuggerTest::testTraceExclude/', $result);
  694. $result = Debugger::trace([
  695. 'exclude' => ['Cake\Test\TestCase\Error\DebuggerTest::testTraceExclude'],
  696. ]);
  697. $this->assertDoesNotMatchRegularExpression('/^Cake\\\Test\\\TestCase\\\Error\\\DebuggerTest::testTraceExclude/', $result);
  698. }
  699. /**
  700. * Tests that __debugInfo is used when available
  701. *
  702. * @return void
  703. */
  704. public function testDebugInfo()
  705. {
  706. $object = new DebuggableThing();
  707. $result = Debugger::exportVar($object, 2);
  708. $expected = <<<eos
  709. object(TestApp\Error\Thing\DebuggableThing) id:0 {
  710. 'foo' => 'bar'
  711. 'inner' => object(TestApp\Error\Thing\DebuggableThing) id:1 {}
  712. }
  713. eos;
  714. $this->assertSame($expected, $result);
  715. }
  716. /**
  717. * Tests reading the output mask settings.
  718. *
  719. * @return void
  720. */
  721. public function testSetOutputMask()
  722. {
  723. Debugger::setOutputMask(['password' => '[**********]']);
  724. $this->assertEquals(['password' => '[**********]'], Debugger::outputMask());
  725. Debugger::setOutputMask(['serial' => 'XXXXXX']);
  726. $this->assertEquals(['password' => '[**********]', 'serial' => 'XXXXXX'], Debugger::outputMask());
  727. Debugger::setOutputMask([], false);
  728. $this->assertEquals([], Debugger::outputMask());
  729. }
  730. /**
  731. * Test configure based output mask configuration
  732. *
  733. * @return void
  734. */
  735. public function testConfigureOutputMask()
  736. {
  737. Configure::write('Debugger.outputMask', ['wow' => 'xxx']);
  738. Debugger::getInstance(TestDebugger::class);
  739. Debugger::getInstance(Debugger::class);
  740. $result = Debugger::exportVar(['wow' => 'pass1234']);
  741. $this->assertStringContainsString('xxx', $result);
  742. $this->assertStringNotContainsString('pass1234', $result);
  743. }
  744. /**
  745. * Tests the masking of an array key.
  746. *
  747. * @return void
  748. */
  749. public function testMaskArray()
  750. {
  751. Debugger::setOutputMask(['password' => '[**********]']);
  752. $result = Debugger::exportVar(['password' => 'pass1234']);
  753. $expected = "['password'=>'[**********]']";
  754. $this->assertSame($expected, preg_replace('/\s+/', '', $result));
  755. }
  756. /**
  757. * Tests the masking of an array key.
  758. *
  759. * @return void
  760. */
  761. public function testMaskObject()
  762. {
  763. Debugger::setOutputMask(['password' => '[**********]']);
  764. $object = new SecurityThing();
  765. $result = Debugger::exportVar($object);
  766. $expected = "object(TestApp\\Error\\Thing\\SecurityThing)id:0{password=>'[**********]'}";
  767. $this->assertSame($expected, preg_replace('/\s+/', '', $result));
  768. }
  769. /**
  770. * test testPrintVar()
  771. *
  772. * @return void
  773. */
  774. public function testPrintVar()
  775. {
  776. ob_start();
  777. Debugger::printVar('this-is-a-test', ['file' => __FILE__, 'line' => __LINE__], false);
  778. $result = ob_get_clean();
  779. $expectedText = <<<EXPECTED
  780. %s (line %d)
  781. ########## DEBUG ##########
  782. 'this-is-a-test'
  783. ###########################
  784. EXPECTED;
  785. $expected = sprintf($expectedText, Debugger::trimPath(__FILE__), __LINE__ - 9);
  786. $this->assertSame($expected, $result);
  787. ob_start();
  788. $value = '<div>this-is-a-test</div>';
  789. Debugger::printVar($value, ['file' => __FILE__, 'line' => __LINE__], true);
  790. $result = ob_get_clean();
  791. $this->assertStringContainsString('&#039;&lt;div&gt;this-is-a-test&lt;/div&gt;&#039;', $result);
  792. ob_start();
  793. Debugger::printVar('<div>this-is-a-test</div>', ['file' => __FILE__, 'line' => __LINE__], true);
  794. $result = ob_get_clean();
  795. $expected = <<<EXPECTED
  796. <div class="cake-debug-output cake-debug" style="direction:ltr">
  797. <span><strong>%s</strong> (line <strong>%d</strong>)</span>
  798. <div class="cake-dbg"><span class="cake-dbg-string">&#039;&lt;div&gt;this-is-a-test&lt;/div&gt;&#039;</span></div>
  799. </div>
  800. EXPECTED;
  801. $expected = sprintf($expected, Debugger::trimPath(__FILE__), __LINE__ - 8);
  802. $this->assertSame($expected, $result);
  803. ob_start();
  804. Debugger::printVar('<div>this-is-a-test</div>', [], true);
  805. $result = ob_get_clean();
  806. $expected = <<<EXPECTED
  807. <div class="cake-debug-output cake-debug" style="direction:ltr">
  808. <div class="cake-dbg"><span class="cake-dbg-string">&#039;&lt;div&gt;this-is-a-test&lt;/div&gt;&#039;</span></div>
  809. </div>
  810. EXPECTED;
  811. $expected = sprintf($expected, Debugger::trimPath(__FILE__), __LINE__ - 8);
  812. $this->assertSame($expected, $result);
  813. ob_start();
  814. Debugger::printVar('<div>this-is-a-test</div>', ['file' => __FILE__, 'line' => __LINE__], false);
  815. $result = ob_get_clean();
  816. $expected = <<<EXPECTED
  817. %s (line %d)
  818. ########## DEBUG ##########
  819. '<div>this-is-a-test</div>'
  820. ###########################
  821. EXPECTED;
  822. $expected = sprintf($expected, Debugger::trimPath(__FILE__), __LINE__ - 9);
  823. $this->assertSame($expected, $result);
  824. ob_start();
  825. Debugger::printVar('<div>this-is-a-test</div>');
  826. $result = ob_get_clean();
  827. $expected = <<<EXPECTED
  828. ########## DEBUG ##########
  829. '<div>this-is-a-test</div>'
  830. ###########################
  831. EXPECTED;
  832. $expected = sprintf($expected, Debugger::trimPath(__FILE__), __LINE__ - 8);
  833. $this->assertSame($expected, $result);
  834. }
  835. /**
  836. * test formatHtmlMessage
  837. *
  838. * @return void
  839. */
  840. public function testFormatHtmlMessage()
  841. {
  842. $output = Debugger::formatHtmlMessage('Some `code` to `replace`');
  843. $this->assertSame('Some <code>code</code> to <code>replace</code>', $output);
  844. $output = Debugger::formatHtmlMessage("Some `co\nde` to `replace`\nmore");
  845. $this->assertSame("Some <code>co<br />\nde</code> to <code>replace</code><br />\nmore", $output);
  846. $output = Debugger::formatHtmlMessage("Some `code` to <script>alert(\"test\")</script>\nmore");
  847. $this->assertSame(
  848. "Some <code>code</code> to &lt;script&gt;alert(&quot;test&quot;)&lt;/script&gt;<br />\nmore",
  849. $output
  850. );
  851. }
  852. /**
  853. * test adding invalid editor
  854. *
  855. * @return void
  856. */
  857. public function testAddEditorInvalid()
  858. {
  859. $this->expectException(RuntimeException::class);
  860. Debugger::addEditor('nope', ['invalid']);
  861. }
  862. /**
  863. * test choosing an unknown editor
  864. *
  865. * @return void
  866. */
  867. public function testSetEditorInvalid()
  868. {
  869. $this->expectException(RuntimeException::class);
  870. Debugger::setEditor('nope');
  871. }
  872. /**
  873. * test choosing a default editor
  874. *
  875. * @return void
  876. */
  877. public function testSetEditorPredefined()
  878. {
  879. Debugger::setEditor('phpstorm');
  880. Debugger::setEditor('macvim');
  881. Debugger::setEditor('sublime');
  882. Debugger::setEditor('emacs');
  883. // No exceptions raised.
  884. $this->assertTrue(true);
  885. }
  886. /**
  887. * Test configure based editor setup
  888. *
  889. * @return void
  890. */
  891. public function testConfigureEditor()
  892. {
  893. Configure::write('Debugger.editor', 'emacs');
  894. Debugger::getInstance(TestDebugger::class);
  895. Debugger::getInstance(Debugger::class);
  896. $result = Debugger::editorUrl('file.php', 123);
  897. $this->assertStringContainsString('emacs://', $result);
  898. }
  899. /**
  900. * test using a valid editor.
  901. *
  902. * @return void
  903. */
  904. public function testEditorUrlValid()
  905. {
  906. Debugger::addEditor('open', 'open://{file}:{line}');
  907. Debugger::setEditor('open');
  908. $this->assertSame('open://test.php:123', Debugger::editorUrl('test.php', 123));
  909. }
  910. /**
  911. * test using a valid editor.
  912. *
  913. * @return void
  914. */
  915. public function testEditorUrlClosure()
  916. {
  917. Debugger::addEditor('open', function (string $file, int $line) {
  918. return "${file}/${line}";
  919. });
  920. Debugger::setEditor('open');
  921. $this->assertSame('test.php/123', Debugger::editorUrl('test.php', 123));
  922. }
  923. }