'bar', 'inner' => new self()]; } } /** * DebuggerTest class * * !!! Be careful with changing code below as it may * !!! change line numbers which are used in the tests * */ class DebuggerTest extends TestCase { protected $_restoreError = false; /** * setUp method * * @return void */ public function setUp() { parent::setUp(); Configure::write('debug', true); Log::drop('stderr'); Log::drop('stdout'); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); if ($this->_restoreError) { restore_error_handler(); } } /** * testDocRef method * * @return void */ public function testDocRef() { ini_set('docref_root', ''); $this->assertEquals(ini_get('docref_root'), ''); new Debugger(); $this->assertEquals(ini_get('docref_root'), 'http://php.net/'); } /** * test Excerpt writing * * @return void */ public function testExcerpt() { $result = Debugger::excerpt(__FILE__, __LINE__ - 1, 2); $this->assertTrue(is_array($result)); $this->assertCount(5, $result); $this->assertRegExp('/function(.+)testExcerpt/', $result[1]); $result = Debugger::excerpt(__FILE__, 2, 2); $this->assertTrue(is_array($result)); $this->assertCount(4, $result); $pattern = '/.*?.*?<\?php/'; $this->assertRegExp($pattern, $result[0]); $result = Debugger::excerpt(__FILE__, 11, 2); $this->assertCount(5, $result); $pattern = '/\*<\/span>/'; $this->assertRegExp($pattern, $result[0]); $return = Debugger::excerpt('[internal]', 2, 2); $this->assertTrue(empty($return)); $result = Debugger::excerpt(__FILE__, __LINE__, 5); $this->assertCount(11, $result); $this->assertContains('Debugger', $result[5]); $this->assertContains('excerpt', $result[5]); $this->assertContains('__FILE__', $result[5]); } /** * testOutput method * * @return void */ public function testOutput() { set_error_handler('Cake\Error\Debugger::showError'); $this->_restoreError = true; $result = Debugger::output(false); $this->assertEquals('', $result); $out .= ''; $result = Debugger::output(true); $this->assertEquals('Notice', $result[0]['error']); $this->assertRegExp('/Undefined variable\:\s+out/', $result[0]['description']); $this->assertRegExp('/DebuggerTest::testOutput/i', $result[0]['trace']); ob_start(); Debugger::output('txt'); $other .= ''; $result = ob_get_clean(); $this->assertRegExp('/Undefined variable:\s+other/', $result); $this->assertRegExp('/Context:/', $result); $this->assertRegExp('/DebuggerTest::testOutput/i', $result); ob_start(); Debugger::output('html'); $wrong .= ''; $result = ob_get_clean(); $this->assertRegExp('/
.+<\/pre>/', $result);
        $this->assertRegExp('/Notice<\/b>/', $result);
        $this->assertRegExp('/variable:\s+wrong/', $result);

        ob_start();
        Debugger::output('js');
        $buzz .= '';
        $result = explode('', ob_get_clean());
        $expected = array(
            'pre' => array('class' => 'cake-error'),
            'a' => array(
                'href' => "javascript:void(0);",
                'onclick' => "preg:/document\.getElementById\('cakeErr[a-z0-9]+\-trace'\)\.style\.display = " .
                    "\(document\.getElementById\('cakeErr[a-z0-9]+\-trace'\)\.style\.display == 'none'" .
                    " \? '' \: 'none'\);/"
            ),
            'b' => array(), 'Notice', '/b', ' (8)',
        );
        $this->assertHtml($expected, $result[0]);

        $this->assertRegExp('/Undefined variable:\s+buzz/', $result[1]);
        $this->assertRegExp('/]+>Code/', $result[1]);
        $this->assertRegExp('/]+>Context/', $result[2]);
        $this->assertContains('$wrong = ''', $result[3], 'Context should be HTML escaped.');
    }

    /**
     * Tests that changes in output formats using Debugger::output() change the templates used.
     *
     * @return void
     */
    public function testChangeOutputFormats()
    {
        set_error_handler('Cake\Error\Debugger::showError');
        $this->_restoreError = true;

        Debugger::output('js', array(
            'traceLine' => '{:reference} - {:path}, line {:line}'
        ));
        $result = Debugger::trace();
        $this->assertRegExp('/' . preg_quote('txmt://open?url=file://', '/') . '(\/|[A-Z]:\\\\)' . '/', $result);

        Debugger::output('xml', array(
            'error' => '{:code}{:file}{:line}' .
                '{:description}',
            'context' => "{:context}",
            'trace' => "{:trace}",
        ));
        Debugger::output('xml');

        ob_start();
        $foo .= '';
        $result = ob_get_clean();

        $expected = array(
            'error' => array(),
            'code' => array(), '8', '/code',
            'file' => array(), 'preg:/[^<]+/', '/file',
            'line' => array(), '' . ((int)__LINE__ - 7), '/line',
            'preg:/Undefined variable:\s+foo/',
            '/error'
        );
        $this->assertHtml($expected, $result, true);
    }

    /**
     * Test that outputAs works.
     *
     * @return void
     */
    public function testOutputAs()
    {
        Debugger::outputAs('html');
        $this->assertEquals('html', Debugger::outputAs());
    }

    /**
     * Test that choosing a non-existent format causes an exception
     *
     * @expectedException \InvalidArgumentException
     * @return void
     */
    public function testOutputAsException()
    {
        Debugger::outputAs('Invalid junk');
    }

    /**
     * Tests that changes in output formats using Debugger::output() change the templates used.
     *
     * @return void
     */
    public function testAddFormat()
    {
        set_error_handler('Cake\Error\Debugger::showError');
        $this->_restoreError = true;

        Debugger::addFormat('js', array(
            'traceLine' => '{:reference} - {:path}, line {:line}'
        ));
        Debugger::outputAs('js');

        $result = Debugger::trace();
        $this->assertRegExp('/' . preg_quote('txmt://open?url=file://', '/') . '(\/|[A-Z]:\\\\)' . '/', $result);

        Debugger::addFormat('xml', array(
            'error' => '{:code}{:file}{:line}' .
                '{:description}',
        ));
        Debugger::outputAs('xml');

        ob_start();
        $foo .= '';
        $result = ob_get_clean();

        $expected = array(
            'assertHtml($expected, $result, true);
    }

    /**
     * Test adding a format that is handled by a callback.
     *
     * @return void
     */
    public function testAddFormatCallback()
    {
        set_error_handler('Cake\Error\Debugger::showError');
        $this->_restoreError = true;

        Debugger::addFormat('callback', array('callback' => array($this, 'customFormat')));
        Debugger::outputAs('callback');

        ob_start();
        $foo .= '';
        $result = ob_get_clean();
        $this->assertContains('Notice: I eated an error', $result);
        $this->assertContains('DebuggerTest.php', $result);
    }

    /**
     * Test method for testing addFormat with callbacks.
     *
     * @return void
     */
    public function customFormat($error, $strings)
    {
        return $error['error'] . ': I eated an error ' . $error['file'];
    }

    /**
     * testTrimPath method
     *
     * @return void
     */
    public function testTrimPath()
    {
        $this->assertEquals('APP/', Debugger::trimPath(APP));
        $this->assertEquals('CORE' . DS . 'src' . DS, Debugger::trimPath(CAKE));
        $this->assertEquals('Some/Other/Path', Debugger::trimPath('Some/Other/Path'));
    }

    /**
     * testExportVar method
     *
     * @return void
     */
    public function testExportVar()
    {
        $Controller = new Controller();
        $Controller->helpers = array('Html', 'Form');
        $View = $Controller->createView();
        $View->int = 2;
        $View->float = 1.333;

        $result = Debugger::exportVar($View);
        $expected = << object(Cake\View\ViewBlock) {}
	plugin => null
	name => ''
	passedArgs => []
	helpers => [
		(int) 0 => 'Html',
		(int) 1 => 'Form'
	]
	viewPath => ''
	view => null
	layout => 'default'
	layoutPath => null
	autoLayout => true
	subDir => null
	theme => null
	hasRendered => false
	uuids => []
	request => object(Cake\Network\Request) {}
	response => object(Cake\Network\Response) {}
	elementCache => 'default'
	viewVars => []
	Html => object(Cake\View\Helper\HtmlHelper) {}
	Form => object(Cake\View\Helper\FormHelper) {}
	int => (int) 2
	float => (float) 1.333
	[protected] _helpers => object(Cake\View\HelperRegistry) {}
	[protected] _ext => '.ctp'
	[protected] _passedVars => [
		(int) 0 => 'viewVars',
		(int) 1 => 'autoLayout',
		(int) 2 => 'helpers',
		(int) 3 => 'view',
		(int) 4 => 'layout',
		(int) 5 => 'name',
		(int) 6 => 'theme',
		(int) 7 => 'layoutPath',
		(int) 8 => 'viewPath',
		(int) 9 => 'plugin',
		(int) 10 => 'passedArgs'
	]
	[protected] _paths => []
	[protected] _pathsForPlugin => []
	[protected] _parents => []
	[protected] _current => null
	[protected] _currentType => ''
	[protected] _stack => []
	[protected] _eventManager => object(Cake\Event\EventManager) {}
	[protected] _eventClass => '\Cake\Event\Event'
}
TEXT;

        $this->assertTextEquals($expected, $result);

        $data = array(
            1 => 'Index one',
            5 => 'Index five'
        );
        $result = Debugger::exportVar($data);
        $expected = << 'Index one',
	(int) 5 => 'Index five'
]
TEXT;
        $this->assertTextEquals($expected, $result);

        $data = array(
            'key' => array(
                '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->assertTextEquals('unknown', $result);
    }

    /**
     * Test exporting various kinds of false.
     *
     * @return void
     */
    public function testExportVarZero()
    {
        $data = array(
            '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);
    }

    /**
     * testLog method
     *
     * @return void
     */
    public function testLog()
    {
        $mock = $this->getMock('Cake\Log\Engine\BaseLog', ['log']);
        Log::config('test', ['engine' => $mock]);

        $mock->expects($this->at(0))
            ->method('log')
            ->with('debug', $this->logicalAnd(
                $this->stringContains('DebuggerTest::testLog'),
                $this->stringContains('cool')
            ));

        $mock->expects($this->at(1))
            ->method('log')
            ->with('debug', $this->logicalAnd(
                $this->stringContains('DebuggerTest::testLog'),
                $this->stringContains('[main]'),
                $this->stringContains("'whatever',"),
                $this->stringContains("'here'")
            ));

        Debugger::log('cool');
        Debugger::log(array('whatever', 'here'));

        Log::drop('test');
    }

    /**
     * test log() depth
     *
     * @return void
     */
    public function testLogDepth()
    {
        $mock = $this->getMock('Cake\Log\Engine\BaseLog', ['log']);
        Log::config('test', ['engine' => $mock]);

        $mock->expects($this->at(0))
            ->method('log')
            ->with('debug', $this->logicalAnd(
                $this->stringContains('DebuggerTest::testLog'),
                $this->stringContains('test'),
                $this->logicalNot($this->stringContains('val'))
            ));

        $val = array(
            'test' => array('key' => 'val')
        );
        Debugger::log($val, 'debug', 0);
    }

    /**
     * testDump method
     *
     * @return void
     */
    public function testDump()
    {
        $var = array('People' => array(
            array(
                'name' => 'joeseph',
                'coat' => 'technicolor',
                'hair_color' => 'brown'
            ),
            array(
                'name' => 'Shaft',
                'coat' => 'black',
                'hair' => 'black'
            )
        ));
        ob_start();
        Debugger::dump($var);
        $result = ob_get_clean();

        $open = "\n";
        $close = "\n\n";
        $expected = << [
		(int) 0 => [
			'name' => 'joeseph',
			'coat' => 'technicolor',
			'hair_color' => 'brown'
		],
		(int) 1 => [
			'name' => 'Shaft',
			'coat' => 'black',
			'hair' => 'black'
		]
	]
]{$close}
TEXT;
        $this->assertTextEquals($expected, $result);

        ob_start();
        Debugger::dump($var, 1);
        $result = ob_get_clean();

        $expected = << [
		[maximum depth reached]
	]
]{$close}
TEXT;
        $this->assertTextEquals($expected, $result);
    }

    /**
     * test getInstance.
     *
     * @return void
     */
    public function testGetInstance()
    {
        $result = Debugger::getInstance();
        $this->assertInstanceOf('Cake\Error\Debugger', $result);

        $result = Debugger::getInstance(__NAMESPACE__ . '\DebuggerTestCaseDebugger');
        $this->assertInstanceOf(__NAMESPACE__ . '\DebuggerTestCaseDebugger', $result);

        $result = Debugger::getInstance();
        $this->assertInstanceOf(__NAMESPACE__ . '\DebuggerTestCaseDebugger', $result);

        $result = Debugger::getInstance('Cake\Error\Debugger');
        $this->assertInstanceOf('Cake\Error\Debugger', $result);
    }

    /**
     * Test that exportVar() doesn't loop through recursive structures.
     *
     * @return void
     */
    public function testExportVarRecursion()
    {
        $output = Debugger::exportVar($GLOBALS);
        $this->assertContains("'GLOBALS' => [recursion]", $output);
    }

    /**
     * test trace exclude
     *
     * @return void
     */
    public function testTraceExclude()
    {
        $result = Debugger::trace();
        $this->assertRegExp('/^Cake\\\Test\\\TestCase\\\Error\\\DebuggerTest::testTraceExclude/', $result);

        $result = Debugger::trace(array(
            'exclude' => array('Cake\Test\TestCase\Error\DebuggerTest::testTraceExclude')
        ));
        $this->assertNotRegExp('/^Cake\\\Test\\\TestCase\\\Error\\\DebuggerTest::testTraceExclude/', $result);
    }

    /**
     * Tests that __debugInfo is used when available
     *
     * @return void
     */
    public function testDebugInfo()
    {
        $object = new DebuggableThing();
        $result = Debugger::exportVar($object, 2);
        $expected = << 'bar',
	'inner' => object(Cake\Test\TestCase\Error\DebuggableThing) {}

}
eos;
        $this->assertEquals($expected, $result);
    }
}