DebuggerTest.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  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\Http\ServerRequest;
  28. use Cake\Log\Log;
  29. use Cake\ORM\Table;
  30. use Cake\TestSuite\TestCase;
  31. use InvalidArgumentException;
  32. use MyClass;
  33. use RuntimeException;
  34. use SplFixedArray;
  35. use stdClass;
  36. use TestApp\Error\TestDebugger;
  37. use TestApp\Error\Thing\DebuggableThing;
  38. use TestApp\Error\Thing\SecurityThing;
  39. use TestApp\Utility\ThrowsDebugInfo;
  40. /**
  41. * DebuggerTest class
  42. *
  43. * !!! Be careful with changing code below as it may
  44. * !!! change line numbers which are used in the tests
  45. */
  46. class DebuggerTest extends TestCase
  47. {
  48. /**
  49. * @var bool
  50. */
  51. protected $restoreError = false;
  52. /**
  53. * setUp method
  54. */
  55. public function setUp(): void
  56. {
  57. parent::setUp();
  58. Configure::write('debug', true);
  59. Log::drop('stderr');
  60. Log::drop('stdout');
  61. Debugger::configInstance('exportFormatter', TextFormatter::class);
  62. }
  63. /**
  64. * tearDown method
  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. public function testDocRef(): void
  77. {
  78. ini_set('docref_root', '');
  79. $this->assertEquals(ini_get('docref_root'), '');
  80. // Force a new instance.
  81. Debugger::getInstance(TestDebugger::class);
  82. Debugger::getInstance(Debugger::class);
  83. $this->assertEquals(ini_get('docref_root'), 'https://secure.php.net/');
  84. }
  85. /**
  86. * test Excerpt writing
  87. */
  88. public function testExcerpt(): void
  89. {
  90. $result = Debugger::excerpt(__FILE__, __LINE__ - 1, 2);
  91. $this->assertIsArray($result);
  92. $this->assertCount(5, $result);
  93. $this->assertMatchesRegularExpression('/function(.+)testExcerpt/', $result[1]);
  94. $result = Debugger::excerpt(__FILE__, 2, 2);
  95. $this->assertIsArray($result);
  96. $this->assertCount(4, $result);
  97. $this->skipIf(defined('HHVM_VERSION'), 'HHVM does not highlight php code');
  98. $pattern = '/<code>.*?<span style\="color\: \#\d+">.*?&lt;\?php/';
  99. $this->assertMatchesRegularExpression($pattern, $result[0]);
  100. $result = Debugger::excerpt(__FILE__, 11, 2);
  101. $this->assertCount(5, $result);
  102. $pattern = '/<span style\="color\: \#\d{6}">.*?<\/span>/';
  103. $this->assertMatchesRegularExpression($pattern, $result[0]);
  104. $return = Debugger::excerpt('[internal]', 2, 2);
  105. $this->assertEmpty($return);
  106. $result = Debugger::excerpt(__FILE__, __LINE__, 5);
  107. $this->assertCount(11, $result);
  108. $this->assertStringContainsString('Debugger', $result[5]);
  109. $this->assertStringContainsString('excerpt', $result[5]);
  110. $this->assertStringContainsString('__FILE__', $result[5]);
  111. $result = Debugger::excerpt(__FILE__, 1, 2);
  112. $this->assertCount(3, $result);
  113. $lastLine = count(explode("\n", file_get_contents(__FILE__)));
  114. $result = Debugger::excerpt(__FILE__, $lastLine, 2);
  115. $this->assertCount(3, $result);
  116. }
  117. /**
  118. * testTrimPath method
  119. */
  120. public function testTrimPath(): void
  121. {
  122. $this->assertSame('APP/', Debugger::trimPath(APP));
  123. $this->assertSame('CORE' . DS . 'src' . DS, Debugger::trimPath(CAKE));
  124. $this->assertSame('Some/Other/Path', Debugger::trimPath('Some/Other/Path'));
  125. }
  126. /**
  127. * testExportVar method
  128. */
  129. public function testExportVar(): void
  130. {
  131. $std = new stdClass();
  132. $std->int = 2;
  133. $std->float = 1.333;
  134. $std->string = ' ';
  135. $result = Debugger::exportVar($std);
  136. $expected = <<<TEXT
  137. object(stdClass) id:0 {
  138. int => (int) 2
  139. float => (float) 1.333
  140. string => ' '
  141. }
  142. TEXT;
  143. $this->assertTextEquals($expected, $result);
  144. $Controller = new Controller(new ServerRequest());
  145. $Controller->viewBuilder()->setHelpers(['Html', 'Form']);
  146. $View = $Controller->createView();
  147. $result = Debugger::exportVar($View);
  148. $expected = <<<TEXT
  149. object(Cake\View\View) id:0 {
  150. [protected] _helpers => object(Cake\View\HelperRegistry) id:1 {}
  151. [protected] Blocks => object(Cake\View\ViewBlock) id:2 {}
  152. [protected] plugin => null
  153. [protected] name => ''
  154. [protected] helpers => [
  155. 'Html' => [
  156. '' => [maximum depth reached]
  157. ],
  158. 'Form' => [
  159. '' => [maximum depth reached]
  160. ]
  161. ]
  162. [protected] templatePath => ''
  163. [protected] template => ''
  164. [protected] layout => 'default'
  165. [protected] layoutPath => ''
  166. [protected] autoLayout => true
  167. [protected] viewVars => []
  168. [protected] _ext => '.php'
  169. [protected] subDir => ''
  170. [protected] theme => null
  171. [protected] request => object(Cake\Http\ServerRequest) id:3 {}
  172. [protected] response => object(Cake\Http\Response) id:4 {}
  173. [protected] elementCache => 'default'
  174. [protected] _passedVars => [
  175. (int) 0 => 'viewVars',
  176. (int) 1 => 'autoLayout',
  177. (int) 2 => 'helpers',
  178. (int) 3 => 'template',
  179. (int) 4 => 'layout',
  180. (int) 5 => 'name',
  181. (int) 6 => 'theme',
  182. (int) 7 => 'layoutPath',
  183. (int) 8 => 'templatePath',
  184. (int) 9 => 'plugin'
  185. ]
  186. [protected] _defaultConfig => []
  187. [protected] _paths => []
  188. [protected] _pathsForPlugin => []
  189. [protected] _parents => []
  190. [protected] _current => ''
  191. [protected] _currentType => ''
  192. [protected] _stack => []
  193. [protected] _viewBlockClass => 'Cake\View\ViewBlock'
  194. [protected] _eventManager => object(Cake\Event\EventManager) id:5 {}
  195. [protected] _eventClass => 'Cake\Event\Event'
  196. [protected] _config => []
  197. [protected] _configInitialized => true
  198. }
  199. TEXT;
  200. $this->assertTextEquals($expected, $result);
  201. $data = [
  202. 1 => 'Index one',
  203. 5 => 'Index five',
  204. ];
  205. $result = Debugger::exportVar($data);
  206. $expected = <<<TEXT
  207. [
  208. (int) 1 => 'Index one',
  209. (int) 5 => 'Index five'
  210. ]
  211. TEXT;
  212. $this->assertTextEquals($expected, $result);
  213. $data = [
  214. 'key' => [
  215. 'value',
  216. ],
  217. ];
  218. $result = Debugger::exportVar($data, 1);
  219. $expected = <<<TEXT
  220. [
  221. 'key' => [
  222. '' => [maximum depth reached]
  223. ]
  224. ]
  225. TEXT;
  226. $this->assertTextEquals($expected, $result);
  227. $data = false;
  228. $result = Debugger::exportVar($data);
  229. $expected = <<<TEXT
  230. false
  231. TEXT;
  232. $this->assertTextEquals($expected, $result);
  233. $file = fopen('php://output', 'w');
  234. fclose($file);
  235. $result = Debugger::exportVar($file);
  236. $this->assertStringContainsString('(resource (closed)) Resource id #', $result);
  237. }
  238. public function testExportVarTypedProperty(): void
  239. {
  240. // This is gross but was simpler than adding a fixture file.
  241. // phpcs:ignore
  242. eval('class MyClass { private string $field; }');
  243. $obj = new MyClass();
  244. $out = Debugger::exportVar($obj);
  245. $this->assertTextContains('field => [uninitialized]', $out);
  246. }
  247. /**
  248. * Test exporting various kinds of false.
  249. */
  250. public function testExportVarZero(): void
  251. {
  252. $data = [
  253. 'nothing' => '',
  254. 'null' => null,
  255. 'false' => false,
  256. 'szero' => '0',
  257. 'zero' => 0,
  258. ];
  259. $result = Debugger::exportVar($data);
  260. $expected = <<<TEXT
  261. [
  262. 'nothing' => '',
  263. 'null' => null,
  264. 'false' => false,
  265. 'szero' => '0',
  266. 'zero' => (int) 0
  267. ]
  268. TEXT;
  269. $this->assertTextEquals($expected, $result);
  270. }
  271. /**
  272. * test exportVar with cyclic objects.
  273. */
  274. public function testExportVarCyclicRef(): void
  275. {
  276. $parent = new stdClass();
  277. $parent->name = 'cake';
  278. $middle = new stdClass();
  279. $parent->child = $middle;
  280. $middle->name = 'php';
  281. $middle->child = $parent;
  282. $result = Debugger::exportVar($parent, 6);
  283. $expected = <<<TEXT
  284. object(stdClass) id:0 {
  285. name => 'cake'
  286. child => object(stdClass) id:1 {
  287. name => 'php'
  288. child => object(stdClass) id:0 {}
  289. }
  290. }
  291. TEXT;
  292. $this->assertTextEquals($expected, $result);
  293. }
  294. /**
  295. * test exportVar with array objects
  296. */
  297. public function testExportVarSplFixedArray(): void
  298. {
  299. $subject = new SplFixedArray(2);
  300. $subject[0] = 'red';
  301. $subject[1] = 'blue';
  302. $result = Debugger::exportVar($subject, 6);
  303. $expected = <<<TEXT
  304. object(SplFixedArray) id:0 {
  305. 0 => 'red'
  306. 1 => 'blue'
  307. }
  308. TEXT;
  309. $this->assertTextEquals($expected, $result);
  310. }
  311. /**
  312. * Tests plain text variable export.
  313. */
  314. public function testExportVarAsPlainText(): void
  315. {
  316. Debugger::configInstance('exportFormatter', null);
  317. $result = Debugger::exportVarAsPlainText(123);
  318. $this->assertSame('(int) 123', $result);
  319. Debugger::configInstance('exportFormatter', ConsoleFormatter::class);
  320. $result = Debugger::exportVarAsPlainText(123);
  321. $this->assertSame('(int) 123', $result);
  322. }
  323. /**
  324. * test exportVar with cyclic objects.
  325. */
  326. public function testExportVarDebugInfo(): void
  327. {
  328. $form = new Form();
  329. $result = Debugger::exportVar($form, 6);
  330. $this->assertStringContainsString("'_schema' => [", $result, 'Has debuginfo keys');
  331. $this->assertStringContainsString("'_validator' => [", $result);
  332. }
  333. /**
  334. * Test exportVar with an exception during __debugInfo()
  335. */
  336. public function testExportVarInvalidDebugInfo(): void
  337. {
  338. $this->skipIf(extension_loaded('xdebug'), 'Throwing exceptions inside __debugInfo breaks with xDebug');
  339. $result = Debugger::exportVar(new ThrowsDebugInfo());
  340. $expected = '(unable to export object: from __debugInfo)';
  341. $this->assertTextEquals($expected, $result);
  342. }
  343. /**
  344. * Test exportVar with a mock
  345. */
  346. public function testExportVarMockObject(): void
  347. {
  348. $result = Debugger::exportVar($this->getMockBuilder(Table::class)->getMock());
  349. $this->assertStringContainsString('object(Mock_Table', $result);
  350. }
  351. /**
  352. * Text exportVarAsNodes()
  353. */
  354. public function testExportVarAsNodes(): void
  355. {
  356. $data = [
  357. 1 => 'Index one',
  358. 5 => 'Index five',
  359. ];
  360. $result = Debugger::exportVarAsNodes($data);
  361. $this->assertInstanceOf(NodeInterface::class, $result);
  362. $this->assertCount(2, $result->getChildren());
  363. /** @var \Cake\Error\Debug\ArrayItemNode $item */
  364. $item = $result->getChildren()[0];
  365. $key = new ScalarNode('int', 1);
  366. $this->assertEquals($key, $item->getKey());
  367. $value = new ScalarNode('string', 'Index one');
  368. $this->assertEquals($value, $item->getValue());
  369. $data = [
  370. 'key' => [
  371. 'value',
  372. ],
  373. ];
  374. $result = Debugger::exportVarAsNodes($data, 1);
  375. $item = $result->getChildren()[0];
  376. $nestedItem = $item->getValue()->getChildren()[0];
  377. $expected = new SpecialNode('[maximum depth reached]');
  378. $this->assertEquals($expected, $nestedItem->getValue());
  379. }
  380. /**
  381. * testLog method
  382. */
  383. public function testLog(): void
  384. {
  385. Log::setConfig('test', [
  386. 'className' => 'Array',
  387. ]);
  388. Debugger::log('cool');
  389. Debugger::log(['whatever', 'here']);
  390. $messages = Log::engine('test')->read();
  391. $this->assertCount(2, $messages);
  392. $this->assertStringContainsString('DebuggerTest->testLog', $messages[0]);
  393. $this->assertStringContainsString('cool', $messages[0]);
  394. $this->assertStringContainsString('DebuggerTest->testLog', $messages[1]);
  395. $this->assertStringContainsString('[main]', $messages[1]);
  396. $this->assertStringContainsString("'whatever'", $messages[1]);
  397. $this->assertStringContainsString("'here'", $messages[1]);
  398. Log::drop('test');
  399. }
  400. /**
  401. * Tests that logging does not apply formatting.
  402. */
  403. public function testLogShouldNotApplyFormatting(): void
  404. {
  405. Log::setConfig('test', [
  406. 'className' => 'Array',
  407. ]);
  408. Debugger::configInstance('exportFormatter', null);
  409. Debugger::log(123);
  410. $messages = implode('', Log::engine('test')->read());
  411. Log::engine('test')->clear();
  412. $this->assertStringContainsString('(int) 123', $messages);
  413. $this->assertStringNotContainsString("\033[0m", $messages);
  414. Debugger::configInstance('exportFormatter', HtmlFormatter::class);
  415. Debugger::log(123);
  416. $messages = implode('', Log::engine('test')->read());
  417. Log::engine('test')->clear();
  418. $this->assertStringContainsString('(int) 123', $messages);
  419. $this->assertStringNotContainsString('<style', $messages);
  420. Debugger::configInstance('exportFormatter', ConsoleFormatter::class);
  421. Debugger::log(123);
  422. $messages = implode('', Log::engine('test')->read());
  423. Log::engine('test')->clear();
  424. $this->assertStringContainsString('(int) 123', $messages);
  425. $this->assertStringNotContainsString("\033[0m", $messages);
  426. Log::drop('test');
  427. }
  428. /**
  429. * test log() depth
  430. */
  431. public function testLogDepth(): void
  432. {
  433. Log::setConfig('test', [
  434. 'className' => 'Array',
  435. ]);
  436. $veryRandomName = [
  437. 'test' => ['key' => 'val'],
  438. ];
  439. Debugger::log($veryRandomName, 'debug', 0);
  440. $messages = Log::engine('test')->read();
  441. $this->assertStringContainsString('DebuggerTest->testLogDepth', $messages[0]);
  442. $this->assertStringContainsString('test', $messages[0]);
  443. $this->assertStringNotContainsString('veryRandomName', $messages[0]);
  444. }
  445. /**
  446. * testDump method
  447. */
  448. public function testDump(): void
  449. {
  450. $var = ['People' => [
  451. [
  452. 'name' => 'joeseph',
  453. 'coat' => 'technicolor',
  454. 'hair_color' => 'brown',
  455. ],
  456. [
  457. 'name' => 'Shaft',
  458. 'coat' => 'black',
  459. 'hair' => 'black',
  460. ],
  461. ]];
  462. ob_start();
  463. Debugger::dump($var);
  464. $result = ob_get_clean();
  465. $open = "\n";
  466. $close = "\n\n";
  467. $expected = <<<TEXT
  468. {$open}[
  469. 'People' => [
  470. (int) 0 => [
  471. 'name' => 'joeseph',
  472. 'coat' => 'technicolor',
  473. 'hair_color' => 'brown'
  474. ],
  475. (int) 1 => [
  476. 'name' => 'Shaft',
  477. 'coat' => 'black',
  478. 'hair' => 'black'
  479. ]
  480. ]
  481. ]{$close}
  482. TEXT;
  483. $this->assertTextEquals($expected, $result);
  484. ob_start();
  485. Debugger::dump($var, 1);
  486. $result = ob_get_clean();
  487. $expected = <<<TEXT
  488. {$open}[
  489. 'People' => [
  490. '' => [maximum depth reached]
  491. ]
  492. ]{$close}
  493. TEXT;
  494. $this->assertTextEquals($expected, $result);
  495. }
  496. /**
  497. * test getInstance.
  498. */
  499. public function testGetInstance(): void
  500. {
  501. $result = Debugger::getInstance();
  502. $exporter = $result->getConfig('exportFormatter');
  503. $this->assertInstanceOf(Debugger::class, $result);
  504. $result = Debugger::getInstance(TestDebugger::class);
  505. $this->assertInstanceOf(TestDebugger::class, $result);
  506. $result = Debugger::getInstance();
  507. $this->assertInstanceOf(TestDebugger::class, $result);
  508. $result = Debugger::getInstance(Debugger::class);
  509. $this->assertInstanceOf(Debugger::class, $result);
  510. $result->setConfig('exportFormatter', $exporter);
  511. }
  512. /**
  513. * Test that exportVar() will stop traversing recursive arrays.
  514. */
  515. public function testExportVarRecursion(): void
  516. {
  517. $array = [];
  518. $array['foo'] = &$array;
  519. $output = Debugger::exportVar($array);
  520. $this->assertMatchesRegularExpression("/'foo' => \[\s+'' \=\> \[maximum depth reached\]/", $output);
  521. }
  522. /**
  523. * test trace exclude
  524. */
  525. public function testTraceExclude(): void
  526. {
  527. $result = Debugger::trace();
  528. $this->assertMatchesRegularExpression('/^Cake\\\Test\\\TestCase\\\Error\\\DebuggerTest..testTraceExclude/m', $result);
  529. $result = Debugger::trace([
  530. 'exclude' => ['Cake\Test\TestCase\Error\DebuggerTest->testTraceExclude'],
  531. ]);
  532. $this->assertDoesNotMatchRegularExpression('/^Cake\\\Test\\\TestCase\\\Error\\\DebuggerTest..testTraceExclude/m', $result);
  533. }
  534. protected function _makeException()
  535. {
  536. return new RuntimeException('testing');
  537. }
  538. /**
  539. * Test stack frame comparisons.
  540. */
  541. public function testGetUniqueFrames()
  542. {
  543. $parent = new RuntimeException('parent');
  544. $child = $this->_makeException();
  545. $result = Debugger::getUniqueFrames($child, $parent);
  546. $this->assertCount(1, $result);
  547. $this->assertEquals(__LINE__ - 4, $result[0]['line']);
  548. $result = Debugger::getUniqueFrames($child, null);
  549. $this->assertGreaterThan(1, count($result));
  550. }
  551. /**
  552. * Tests that __debugInfo is used when available
  553. */
  554. public function testDebugInfo(): void
  555. {
  556. $object = new DebuggableThing();
  557. $result = Debugger::exportVar($object, 2);
  558. $expected = <<<eos
  559. object(TestApp\Error\Thing\DebuggableThing) id:0 {
  560. 'foo' => 'bar'
  561. 'inner' => object(TestApp\Error\Thing\DebuggableThing) id:1 {}
  562. }
  563. eos;
  564. $this->assertSame($expected, $result);
  565. }
  566. /**
  567. * Tests reading the output mask settings.
  568. */
  569. public function testSetOutputMask(): void
  570. {
  571. Debugger::setOutputMask(['password' => '[**********]']);
  572. $this->assertEquals(['password' => '[**********]'], Debugger::outputMask());
  573. Debugger::setOutputMask(['serial' => 'XXXXXX']);
  574. $this->assertEquals(['password' => '[**********]', 'serial' => 'XXXXXX'], Debugger::outputMask());
  575. Debugger::setOutputMask([], false);
  576. $this->assertSame([], Debugger::outputMask());
  577. }
  578. /**
  579. * Test configure based output mask configuration
  580. */
  581. public function testConfigureOutputMask(): void
  582. {
  583. Configure::write('Debugger.outputMask', ['wow' => 'xxx']);
  584. Debugger::getInstance(TestDebugger::class);
  585. Debugger::getInstance(Debugger::class);
  586. $result = Debugger::exportVar(['wow' => 'pass1234']);
  587. $this->assertStringContainsString('xxx', $result);
  588. $this->assertStringNotContainsString('pass1234', $result);
  589. }
  590. /**
  591. * Tests the masking of an array key.
  592. */
  593. public function testMaskArray(): void
  594. {
  595. Debugger::setOutputMask(['password' => '[**********]']);
  596. $result = Debugger::exportVar(['password' => 'pass1234']);
  597. $expected = "['password'=>'[**********]']";
  598. $this->assertSame($expected, preg_replace('/\s+/', '', $result));
  599. }
  600. /**
  601. * Tests the masking of an array key.
  602. */
  603. public function testMaskObject(): void
  604. {
  605. Debugger::setOutputMask(['password' => '[**********]']);
  606. $object = new SecurityThing();
  607. $result = Debugger::exportVar($object);
  608. $expected = "object(TestApp\\Error\\Thing\\SecurityThing)id:0{password=>'[**********]'}";
  609. $this->assertSame($expected, preg_replace('/\s+/', '', $result));
  610. }
  611. /**
  612. * test testPrintVar()
  613. */
  614. public function testPrintVar(): void
  615. {
  616. ob_start();
  617. Debugger::printVar('this-is-a-test', ['file' => __FILE__, 'line' => __LINE__], false);
  618. $result = ob_get_clean();
  619. $expectedText = <<<EXPECTED
  620. %s (line %d)
  621. ########## DEBUG ##########
  622. 'this-is-a-test'
  623. ###########################
  624. EXPECTED;
  625. $expected = sprintf($expectedText, Debugger::trimPath(__FILE__), __LINE__ - 9);
  626. $this->assertSame($expected, $result);
  627. ob_start();
  628. $value = '<div>this-is-a-test</div>';
  629. Debugger::printVar($value, ['file' => __FILE__, 'line' => __LINE__], true);
  630. $result = ob_get_clean();
  631. $this->assertStringContainsString('&#039;&lt;div&gt;this-is-a-test&lt;/div&gt;&#039;', $result);
  632. ob_start();
  633. Debugger::printVar('<div>this-is-a-test</div>', ['file' => __FILE__, 'line' => __LINE__], true);
  634. $result = ob_get_clean();
  635. $expected = <<<EXPECTED
  636. <div class="cake-debug-output cake-debug" style="direction:ltr">
  637. <span><strong>%s</strong> (line <strong>%d</strong>)</span>
  638. <div class="cake-debug"><span class="cake-debug-string">&#039;&lt;div&gt;this-is-a-test&lt;/div&gt;&#039;</span></div>
  639. </div>
  640. EXPECTED;
  641. $expected = sprintf($expected, Debugger::trimPath(__FILE__), __LINE__ - 8);
  642. $this->assertSame($expected, $result);
  643. ob_start();
  644. Debugger::printVar('<div>this-is-a-test</div>', [], true);
  645. $result = ob_get_clean();
  646. $expected = <<<EXPECTED
  647. <div class="cake-debug-output cake-debug" style="direction:ltr">
  648. <div class="cake-debug"><span class="cake-debug-string">&#039;&lt;div&gt;this-is-a-test&lt;/div&gt;&#039;</span></div>
  649. </div>
  650. EXPECTED;
  651. $this->assertSame($expected, $result);
  652. ob_start();
  653. Debugger::printVar('<div>this-is-a-test</div>', ['file' => __FILE__, 'line' => __LINE__], false);
  654. $result = ob_get_clean();
  655. $expected = <<<EXPECTED
  656. %s (line %d)
  657. ########## DEBUG ##########
  658. '<div>this-is-a-test</div>'
  659. ###########################
  660. EXPECTED;
  661. $expected = sprintf($expected, Debugger::trimPath(__FILE__), __LINE__ - 9);
  662. $this->assertSame($expected, $result);
  663. ob_start();
  664. Debugger::printVar('<div>this-is-a-test</div>');
  665. $result = ob_get_clean();
  666. $expected = <<<EXPECTED
  667. ########## DEBUG ##########
  668. '<div>this-is-a-test</div>'
  669. ###########################
  670. EXPECTED;
  671. $this->assertSame($expected, $result);
  672. }
  673. /**
  674. * test formatHtmlMessage
  675. */
  676. public function testFormatHtmlMessage(): void
  677. {
  678. $output = Debugger::formatHtmlMessage('Some `code` to `replace`');
  679. $this->assertSame('Some <code>`code`</code> to <code>`replace`</code>', $output);
  680. $output = Debugger::formatHtmlMessage("Some `co\nde` to `replace`\nmore");
  681. $this->assertSame("Some <code>`co<br />\nde`</code> to <code>`replace`</code><br />\nmore", $output);
  682. $output = Debugger::formatHtmlMessage("Some `code` to <script>alert(\"test\")</script>\nmore");
  683. $this->assertSame(
  684. "Some <code>`code`</code> to &lt;script&gt;alert(&quot;test&quot;)&lt;/script&gt;<br />\nmore",
  685. $output
  686. );
  687. }
  688. /**
  689. * test choosing an unknown editor
  690. */
  691. public function testSetEditorInvalid(): void
  692. {
  693. $this->expectException(InvalidArgumentException::class);
  694. Debugger::setEditor('nope');
  695. }
  696. /**
  697. * test choosing a default editor
  698. */
  699. public function testSetEditorPredefined(): void
  700. {
  701. Debugger::setEditor('phpstorm');
  702. Debugger::setEditor('macvim');
  703. Debugger::setEditor('sublime');
  704. Debugger::setEditor('emacs');
  705. // No exceptions raised.
  706. $this->assertTrue(true);
  707. }
  708. /**
  709. * Test configure based editor setup
  710. */
  711. public function testConfigureEditor(): void
  712. {
  713. Configure::write('Debugger.editor', 'emacs');
  714. Debugger::getInstance(TestDebugger::class);
  715. Debugger::getInstance(Debugger::class);
  716. $result = Debugger::editorUrl('file.php', 123);
  717. $this->assertStringContainsString('emacs://', $result);
  718. }
  719. /**
  720. * test using a valid editor.
  721. */
  722. public function testEditorUrlValid(): void
  723. {
  724. Debugger::addEditor('open', 'open://{file}:{line}');
  725. Debugger::setEditor('open');
  726. $this->assertSame('open://test.php:123', Debugger::editorUrl('test.php', 123));
  727. }
  728. /**
  729. * test using a valid editor.
  730. */
  731. public function testEditorUrlClosure(): void
  732. {
  733. Debugger::addEditor('open', function (string $file, int $line) {
  734. return "{$file}/{$line}";
  735. });
  736. Debugger::setEditor('open');
  737. $this->assertSame('test.php/123', Debugger::editorUrl('test.php', 123));
  738. }
  739. }