DebuggerTest.php 24 KB

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