restoreError) { restore_error_handler(); } } /** * testDocRef method */ public function testDocRef(): void { ini_set('docref_root', ''); $this->assertEquals(ini_get('docref_root'), ''); // Force a new instance. Debugger::getInstance(TestDebugger::class); Debugger::getInstance(Debugger::class); $this->assertEquals(ini_get('docref_root'), 'https://secure.php.net/'); } /** * test Excerpt writing */ public function testExcerpt(): void { $result = Debugger::excerpt(__FILE__, __LINE__ - 1, 2); $this->assertIsArray($result); $this->assertCount(5, $result); $this->assertMatchesRegularExpression('/function(.+)testExcerpt/', $result[1]); $result = Debugger::excerpt(__FILE__, 2, 2); $this->assertIsArray($result); $this->assertCount(4, $result); $this->skipIf(defined('HHVM_VERSION'), 'HHVM does not highlight php code'); // Due to different highlight_string() function behavior, see. https://3v4l.org/HcfBN. Since 8.3, it wraps it around
$pattern = version_compare(PHP_VERSION, '8.3', '<')
? '/.*?.*?<\?php/'
: '/.*?.*?.*?<\?php/';
$this->assertMatchesRegularExpression($pattern, $result[0]);
$result = Debugger::excerpt(__FILE__, 11, 2);
$this->assertCount(5, $result);
$pattern = '/.*?<\/span>/';
$this->assertMatchesRegularExpression($pattern, $result[0]);
$return = Debugger::excerpt('[internal]', 2, 2);
$this->assertEmpty($return);
$result = Debugger::excerpt(__FILE__, __LINE__, 5);
$this->assertCount(11, $result);
$this->assertStringContainsString('Debugger', $result[5]);
$this->assertStringContainsString('excerpt', $result[5]);
$this->assertStringContainsString('__FILE__', $result[5]);
$result = Debugger::excerpt(__FILE__, 1, 2);
$this->assertCount(3, $result);
$lastLine = count(explode("\n", file_get_contents(__FILE__)));
$result = Debugger::excerpt(__FILE__, $lastLine, 2);
$this->assertCount(3, $result);
}
/**
* testTrimPath method
*/
public function testTrimPath(): void
{
$this->assertSame('APP/', Debugger::trimPath(APP));
$this->assertSame('CORE' . DS . 'src' . DS, Debugger::trimPath(CAKE));
$this->assertSame('Some/Other/Path', Debugger::trimPath('Some/Other/Path'));
}
/**
* testExportVar method
*/
public function testExportVar(): void
{
$std = new stdClass();
$std->int = 2;
$std->float = 1.333;
$std->string = ' ';
$result = Debugger::exportVar($std);
$expected = << (int) 2
float => (float) 1.333
string => ' '
}
TEXT;
$this->assertTextEquals($expected, $result);
$Controller = new Controller(new ServerRequest());
$Controller->viewBuilder()->setHelpers(['Html', 'Form']);
$View = $Controller->createView();
$result = Debugger::exportVar($View);
$expected = << object(Cake\View\HelperRegistry) id:1 {}
[protected] Blocks => object(Cake\View\ViewBlock) id:2 {}
[protected] plugin => null
[protected] name => ''
[protected] helpers => [
'Html' => [
'' => [maximum depth reached]
],
'Form' => [
'' => [maximum depth reached]
]
]
[protected] templatePath => ''
[protected] template => ''
[protected] layout => 'default'
[protected] layoutPath => ''
[protected] autoLayout => true
[protected] viewVars => []
[protected] _ext => '.php'
[protected] subDir => ''
[protected] theme => null
[protected] request => object(Cake\Http\ServerRequest) id:3 {}
[protected] response => object(Cake\Http\Response) id:4 {}
[protected] elementCache => 'default'
[protected] _passedVars => [
(int) 0 => 'viewVars',
(int) 1 => 'autoLayout',
(int) 2 => 'helpers',
(int) 3 => 'template',
(int) 4 => 'layout',
(int) 5 => 'name',
(int) 6 => 'theme',
(int) 7 => 'layoutPath',
(int) 8 => 'templatePath',
(int) 9 => 'plugin'
]
[protected] _defaultConfig => []
[protected] _paths => []
[protected] _pathsForPlugin => []
[protected] _parents => []
[protected] _current => ''
[protected] _currentType => ''
[protected] _stack => []
[protected] _viewBlockClass => 'Cake\View\ViewBlock'
[protected] _eventManager => object(Cake\Event\EventManager) id:5 {}
[protected] _eventClass => 'Cake\Event\Event'
[protected] _config => []
[protected] _configInitialized => true
}
TEXT;
$this->assertTextEquals($expected, $result);
$data = [
1 => 'Index one',
5 => 'Index five',
];
$result = Debugger::exportVar($data);
$expected = << 'Index one',
(int) 5 => 'Index five'
]
TEXT;
$this->assertTextEquals($expected, $result);
$data = [
'key' => [
'value',
],
];
$result = Debugger::exportVar($data, 1);
$expected = << [
'' => [maximum depth reached]
]
]
TEXT;
$this->assertTextEquals($expected, $result);
$data = false;
$result = Debugger::exportVar($data);
$expected = <<assertTextEquals($expected, $result);
$file = fopen('php://output', 'w');
fclose($file);
$result = Debugger::exportVar($file);
$this->assertStringContainsString('(resource (closed)) Resource id #', $result);
}
public function testExportVarTypedProperty(): void
{
// This is gross but was simpler than adding a fixture file.
// phpcs:ignore
eval('class MyClass { private string $field; }');
/** @phpstan-ignore-next-line */
$obj = new MyClass();
$out = Debugger::exportVar($obj);
$this->assertTextContains('field => [uninitialized]', $out);
}
/**
* Test exporting various kinds of false.
*/
public function testExportVarZero(): void
{
$data = [
'nothing' => '',
'null' => null,
'false' => false,
'szero' => '0',
'zero' => 0,
];
$result = Debugger::exportVar($data);
$expected = << '',
'null' => null,
'false' => false,
'szero' => '0',
'zero' => (int) 0
]
TEXT;
$this->assertTextEquals($expected, $result);
}
/**
* test exportVar with cyclic objects.
*/
public function testExportVarCyclicRef(): void
{
$parent = new stdClass();
$parent->name = 'cake';
$middle = new stdClass();
$parent->child = $middle;
$middle->name = 'php';
$middle->child = $parent;
$result = Debugger::exportVar($parent, 6);
$expected = << 'cake'
child => object(stdClass) id:1 {
name => 'php'
child => object(stdClass) id:0 {}
}
}
TEXT;
$this->assertTextEquals($expected, $result);
}
/**
* test exportVar with array objects
*/
public function testExportVarSplFixedArray(): void
{
$this->skipIf(
version_compare(PHP_VERSION, '8.3', '>='),
'Due to different get_object_vars() function behavior used in Debugger::exportObject()' // see. https://3v4l.org/DWpRl
);
$subject = new SplFixedArray(2);
$subject[0] = 'red';
$subject[1] = 'blue';
$result = Debugger::exportVar($subject, 6);
$this->assertStringContainsString('object(SplFixedArray) id:0 {', $result);
}
/**
* Tests plain text variable export.
*/
public function testExportVarAsPlainText(): void
{
Debugger::configInstance('exportFormatter', null);
$result = Debugger::exportVarAsPlainText(123);
$this->assertSame('(int) 123', $result);
Debugger::configInstance('exportFormatter', ConsoleFormatter::class);
$result = Debugger::exportVarAsPlainText(123);
$this->assertSame('(int) 123', $result);
}
/**
* test exportVar with cyclic objects.
*/
public function testExportVarDebugInfo(): void
{
$form = new Form();
$result = Debugger::exportVar($form, 6);
$this->assertStringContainsString("'_schema' => [", $result, 'Has debuginfo keys');
$this->assertStringContainsString("'_validator' => [", $result);
}
/**
* Test exportVar with an exception during __debugInfo()
*/
public function testExportVarInvalidDebugInfo(): void
{
$this->skipIf(extension_loaded('xdebug'), 'Throwing exceptions inside __debugInfo breaks with xDebug');
$result = Debugger::exportVar(new ThrowsDebugInfo());
$expected = '(unable to export object: from __debugInfo)';
$this->assertTextEquals($expected, $result);
}
/**
* Test exportVar with a mock
*/
public function testExportVarMockObject(): void
{
$result = Debugger::exportVar($this->getMockBuilder(Table::class)->getMock());
$this->assertStringStartsWith('object(Mock', $result);
$this->assertStringContainsString('_Table_', $result);
}
/**
* Text exportVarAsNodes()
*/
public function testExportVarAsNodes(): void
{
$data = [
1 => 'Index one',
5 => 'Index five',
];
$result = Debugger::exportVarAsNodes($data);
$this->assertInstanceOf(NodeInterface::class, $result);
$this->assertCount(2, $result->getChildren());
/** @var \Cake\Error\Debug\ArrayItemNode $item */
$item = $result->getChildren()[0];
$key = new ScalarNode('int', 1);
$this->assertEquals($key, $item->getKey());
$value = new ScalarNode('string', 'Index one');
$this->assertEquals($value, $item->getValue());
$data = [
'key' => [
'value',
],
];
$result = Debugger::exportVarAsNodes($data, 1);
$item = $result->getChildren()[0];
$nestedItem = $item->getValue()->getChildren()[0];
$expected = new SpecialNode('[maximum depth reached]');
$this->assertEquals($expected, $nestedItem->getValue());
}
/**
* testLog method
*/
public function testLog(): void
{
Log::setConfig('test', [
'className' => 'Array',
]);
Debugger::log('cool');
Debugger::log(['whatever', 'here']);
$messages = Log::engine('test')->read();
$this->assertCount(2, $messages);
$this->assertStringContainsString('DebuggerTest->testLog', $messages[0]);
$this->assertStringContainsString('cool', $messages[0]);
$this->assertStringContainsString('DebuggerTest->testLog', $messages[1]);
$this->assertStringContainsString('[main]', $messages[1]);
$this->assertStringContainsString("'whatever'", $messages[1]);
$this->assertStringContainsString("'here'", $messages[1]);
Log::drop('test');
}
/**
* Tests that logging does not apply formatting.
*/
public function testLogShouldNotApplyFormatting(): void
{
Log::setConfig('test', [
'className' => 'Array',
]);
Debugger::configInstance('exportFormatter', null);
Debugger::log(123);
$messages = implode('', Log::engine('test')->read());
Log::engine('test')->clear();
$this->assertStringContainsString('(int) 123', $messages);
$this->assertStringNotContainsString("\033[0m", $messages);
Debugger::configInstance('exportFormatter', HtmlFormatter::class);
Debugger::log(123);
$messages = implode('', Log::engine('test')->read());
Log::engine('test')->clear();
$this->assertStringContainsString('(int) 123', $messages);
$this->assertStringNotContainsString('