<?php
/* SVN FILE: $Id: code_coverage_manager.test.php 6563 2008-03-12 21:19:31Z phpnut $ */
/**
 * Short description for file.
 *
 * Long description for file
 *
 * PHP versions 4 and 5
 *
 * CakePHP(tm) Tests <https://trac.cakephp.org/wiki/Developement/TestSuite>
 * Copyright 2005-2008, Cake Software Foundation, Inc.
 *								1785 E. Sahara Avenue, Suite 490-204
 *								Las Vegas, Nevada 89104
 *
 *  Licensed under The Open Group Test Suite License
 *  Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright		Copyright 2005-2008, Cake Software Foundation, Inc.
 * @link				https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
 * @package			cake.tests
 * @subpackage		cake.tests.cases.libs
 * @since			CakePHP(tm) v 1.2.0.4206
 * @version			$Revision: 6563 $
 * @modifiedby		$LastChangedBy: phpnut $
 * @lastmodified	$Date: 2008-03-12 22:19:31 +0100 (Wed, 12 Mar 2008) $
 * @license			http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
 */
App::import('Core', 'CodeCoverageManager');
require_once CAKE . 'tests' . DS . 'lib' . DS . 'cli_reporter.php';
require_once CAKE . 'tests' . DS . 'lib' . DS . 'cake_reporter.php';
/**
 * Short description for class.
 *
 * @package		cake.tests
 * @subpackage	cake.tests.cases.libs
 */
class CodeCoverageManagerTest extends UnitTestCase {
/**
 * Skip if XDebug not installed
 *
 * @access public
 */
	function skip() {
		$this->skipif (!extension_loaded('xdebug'), 'XDebug not installed');
	}

	function testNoTestCaseSupplied() {
		if (php_sapi_name() != 'cli') {
			CodeCoverageManager::start(substr(md5(microtime()), 0, 5), new CakeHtmlReporter());
			CodeCoverageManager::report(false);
			$this->assertError();

			CodeCoverageManager::start('libs/'.basename(__FILE__), new CakeHtmlReporter());
			CodeCoverageManager::report(false);
			$this->assertError();

			$path = LIBS;
			if (strpos(LIBS, ROOT) === false) {
				$path = ROOT.DS.LIBS;
			}
			App::import('Core', 'Folder');
			$folder = new Folder();
			$folder->cd($path);
			$contents = $folder->ls();

			function remove($var) {
	 		    return ($var != basename(__FILE__));
			}
			$contents[1] = array_filter($contents[1], "remove");
			$keys = array_rand($contents[1], 5);
			foreach ($keys as $key) {
				CodeCoverageManager::start('libs'.DS.$contents[1][$key], new CakeHtmlReporter());
				CodeCoverageManager::report(false);
				$this->assertNoErrors();
			}
		}
	}

	function testGetTestObjectFileNameFromTestCaseFile() {
		$manager =& CodeCoverageManager::getInstance();
		$manager->reporter = new CakeHtmlReporter();

		$expected = $manager->__testObjectFileFromCaseFile('models/some_file.test.php', true);
		$this->assertIdentical(APP.'models'.DS.'some_file.php', $expected);

		$expected = $manager->__testObjectFileFromCaseFile('controllers/some_file.test.php', true);
		$this->assertIdentical(APP.'controllers'.DS.'some_file.php', $expected);

		$expected = $manager->__testObjectFileFromCaseFile('views/some_file.test.php', true);
		$this->assertIdentical(APP.'views'.DS.'some_file.php', $expected);

		$expected = $manager->__testObjectFileFromCaseFile('behaviors/some_file.test.php', true);
		$this->assertIdentical(APP.'models'.DS.'behaviors'.DS.'some_file.php', $expected);

		$expected = $manager->__testObjectFileFromCaseFile('components/some_file.test.php', true);
		$this->assertIdentical(APP.'controllers'.DS.'components'.DS.'some_file.php', $expected);

		$expected = $manager->__testObjectFileFromCaseFile('helpers/some_file.test.php', true);
		$this->assertIdentical(APP.'views'.DS.'helpers'.DS.'some_file.php', $expected);

		$manager->pluginTest = 'bugs';
		$expected = $manager->__testObjectFileFromCaseFile('models/some_file.test.php', false);
		$this->assertIdentical(APP.'plugins'.DS.'bugs'.DS.'models'.DS.'some_file.php', $expected);

		$manager->pluginTest = false;
		$manager->reporter = new CLIReporter;
		$expected = $manager->__testObjectFileFromCaseFile('libs/set.test.php', false);
		$this->assertIdentical(ROOT.DS.'cake'.DS.'libs'.DS.'set.php', $expected);
	}

	function testOfHtmlReport() {
		$manager =& CodeCoverageManager::getInstance();
		$code = <<<PHP
		class Set extends Object {
		/**
		 * Value of the Set object.
		 *
		 * @var array
		 * @access public
		 */
			var \$value = array();
		/**
		 * Constructor. Defaults to an empty array.
		 *
		 * @access public
		 */
			function __construct() {
				if (func_num_args() == 1 && is_array(func_get_arg(0))) {
					\$this->value = func_get_arg(0);
				} else {
					\$this->value = func_get_args();
				}
			}
		/**
		 * Returns the contents of the Set object
		 *
		 * @return array
		 * @access public
		 */
			function &get() {
				return \$this->value;
			}
		/**
		 * This function can be thought of as a hybrid between PHP's array_merge and array_merge_recursive. The difference
		 * to the two is that if an array key contains another array then the function behaves recursive (unlike array_merge)
		 * but does not do if for keys containing strings (unlike array_merge_recursive). See the unit test for more information.
		 *
		 * Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays.
		 *
		 * @param array \$arr1 Array to be merged
		 * @param array \$arr2 Array to merge with
		 * @return array Merged array
		 * @access public
		 */
			function merge(\$arr1, \$arr2 = null) {
				\$args = func_get_args();

				if (isset(\$this) && is_a(\$this, 'set')) {
					\$backtrace = debug_backtrace();
					\$previousCall = strtolower(\$backtrace[1]['class'].'::'.\$backtrace[1]['function']);
					if (\$previousCall != 'set::merge') {
						\$r =& \$this->value;
						array_unshift(\$args, null);
					}
				}
				if (!isset(\$r)) {
					\$r = (array)current(\$args);
				}

				while ((\$arg = next(\$args)) !== false) {
					if (is_a(\$arg, 'set')) {
						\$arg = \$arg->get();
					}

					foreach ((array)\$arg as \$key => \$val)	 {
						if (is_array(\$val) && isset(\$r[\$key]) && is_array(\$r[\$key])) {
							\$r[\$key] = Set::merge(\$r[\$key], \$val);
						} elseif (is_int(\$key)) {

						} else {
							\$r[\$key] = \$val;
						}
					}
				}
				return \$r;
			}
PHP;

		$testObjectFile = explode("\n", $code);
		$coverageData = array(
			0 => 1,
			1 => 1,
			2 => -2,
			3 => -2,
			4 => -2,
			5 => -2,
			6 => -2,
			7 => -2,
			8 => -1,
			9 => -2,
			10 => -2,
			11 => -2,
			12 => -2,
			13 => -2,
			14 => 1,
			15 => 1,
			16 => -1,
			17 => 1,
			18 => 1,
			19 => -1,
			20 => 1,
			21 => -2,
			22 => -2,
			23 => -2,
			24 => -2,
			25 => -2,
			26 => -2,
			27 => 1,
			28 => -1,
			29 => 1,
			30 => 1,
			31 => -2,
			32 => -2,
			33 => -2,
			34 => -2,
			35 => -2,
			36 => -2,
		 	37=> -2,
			38 => -2,
			39 => -2,
			40 => -2,
			41 => -2,
			42 => -2,
			43 => -1,
		);
		$execCodeLines = range(0, 72);
		$result = explode("</div>", $report = $manager->reportCaseHtml($testObjectFile, $coverageData, $execCodeLines));

		foreach ($result as $num => $line) {
			$num++;
			if (array_key_exists($num, $coverageData)) {
				if ($coverageData[$num] == 1) {
					$this->assertTrue(strpos($line, 'covered') !== false, $num.': '.$line." fails");
				}

				if (!array_key_exists($num, $execCodeLines) || $coverageData[$num] == -2) {
					$this->assertTrue(strpos($line, 'ignored') !== false, $num.': '.$line." fails");
				}

				if ($coverageData[$num] == -1) {
					$this->assertTrue(strpos($line, 'uncovered') !== false, $num.': '.$line." fails");
				}
			}
		}
	}

	function testOfHtmlDiffReport() {
		$manager =& CodeCoverageManager::getInstance();
		$code = <<<PHP
		class Set extends Object {
		/**
		 * Value of the Set object.
		 *
		 * @var array
		 * @access public
		 */
			var \$value = array();
		/**
		 * Constructor. Defaults to an empty array.
		 *
		 * @access public
		 */
			function __construct() {
				if (func_num_args() == 1 && is_array(func_get_arg(0))) {
					\$this->value = func_get_arg(0);
				} else {
					\$this->value = func_get_args();
				}
			}
		/**
		 * Returns the contents of the Set object
		 *
		 * @return array
		 * @access public
		 */
			function &get() {
				return \$this->value;
			}
		/**
		 * This function can be thought of as a hybrid between PHP's array_merge and array_merge_recursive. The difference
		 * to the two is that if an array key contains another array then the function behaves recursive (unlike array_merge)
		 * but does not do if for keys containing strings (unlike array_merge_recursive). See the unit test for more information.
		 *
		 * Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays.
		 *
		 * @param array \$arr1 Array to be merged
		 * @param array \$arr2 Array to merge with
		 * @return array Merged array
		 * @access public
		 */
			function merge(\$arr1, \$arr2 = null) {
				\$args = func_get_args();

				if (isset(\$this) && is_a(\$this, 'set')) {
					\$backtrace = debug_backtrace();
					\$previousCall = strtolower(\$backtrace[1]['class'].'::'.\$backtrace[1]['function']);
					if (\$previousCall != 'set::merge') {
						\$r =& \$this->value;
						array_unshift(\$args, null);
					}
				}
				if (!isset(\$r)) {
					\$r = (array)current(\$args);
				}

				while ((\$arg = next(\$args)) !== false) {
					if (is_a(\$arg, 'set')) {
						\$arg = \$arg->get();
					}

					foreach ((array)\$arg as \$key => \$val)	 {
						if (is_array(\$val) && isset(\$r[\$key]) && is_array(\$r[\$key])) {
							\$r[\$key] = Set::merge(\$r[\$key], \$val);
						} elseif (is_int(\$key)) {

						} else {
							\$r[\$key] = \$val;
						}
					}
				}
				return \$r;
			}
PHP;

		$testObjectFile = explode("\n", $code);
		$coverageData = array(
			0 => 1,
			1 => 1,
			2 => -2,
			3 => -2,
			4 => -2,
			5 => -2,
			6 => -2,
			7 => -2,
			8 => -1,
			9 => -2,
			10 => -2,
			11 => -2,
			12 => -2,
			13 => -2,
			14 => 1,
			15 => 1,
			16 => -1,
			17 => 1,
			18 => 1,
			19 => -1,
			20 => 1,
			21 => -2,
			22 => -2,
			23 => -2,
			24 => -2,
			25 => -2,
			26 => -2,
			27 => 1,
			28 => -1,
			29 => 1,
			30 => 1,
			31 => -2,
			32 => -2,
			33 => -2,
			34 => -2,
			35 => -2,
			36 => -2,
		 	37=> -2,
			38 => -2,
			39 => -2,
			40 => -2,
			41 => -2,
			42 => -2,
			43 => -1,
			44 => -2,
			45 => -2,
			46 => -2,
			47 => -2,
			48 => 1,
			49 => 1,
		 	50 => -1,
			51 => 1,
			52 => 1,
			53 => -2,
			54 => -2,
			55 => 1,
			56 => 1,
			57 => 1,
			58 => 1,
			59 => -1,
			60 => 1,
			61 => 1,
			62 => -2,
		 	63 => -2,
			64 => 1,
			65 => -2,
			66 => 1,
			67 => -1,
			68 => -2,
			69 => -1,
			70 => -1,
			71 => 1,
			72 => -2,
		);
		$expected = array(
			0 => 'ignored',
			1 => 'ignored',
			2 => 'ignored',
			3 => 'ignored',
			4 => 'ignored',
			5 => 'ignored show start realstart',
			6 => 'ignored show',
			7 => 'ignored show',
			8 => 'uncovered show',
			9 => 'ignored show',
			10 => 'ignored show',
			11 => 'ignored show end',
			12 => 'ignored',
			13 => 'ignored show start',
			14 => 'covered show',
			15 => 'covered show',
			16 => 'uncovered show',
			17 => 'covered show show',
			18 => 'covered show show',
			19 => 'uncovered show',
			20 => 'covered show',
			21 => 'ignored show',
			22 => 'ignored show end',
			23 => 'ignored',
			24 => 'ignored',
			25 => 'ignored show start',
			26 => 'ignored show',
			27 => 'covered show',
			28 => 'uncovered show',
			29 => 'covered show',
			30 => 'covered show',
			31 => 'ignored show end',
			32 => 'ignored',
			33 => 'ignored',
			34 => 'ignored',
			35 => 'ignored',
			36 => 'ignored',
		 	37 => 'ignored',
			38 => 'ignored',
			39 => 'ignored',
			40 => 'ignored show start',
			41 => 'ignored show',
			42 => 'ignored show',
			43 => 'uncovered show',
			41 => 'ignored show',
			42 => 'ignored show',
			43 => 'uncovered show',
			44 => 'ignored show',
			45 => 'ignored show',
			46 => 'ignored show',
			47 => 'ignored show',
			48 => 'covered show',
			49 => 'covered show',
		 	50 => 'uncovered show',
			51 => 'covered show',
			52 => 'covered show',
			53 => 'ignored show end',
			54 => 'ignored',
			55 => 'covered',
			56 => 'covered show start',
			57 => 'covered show',
			58 => 'covered show',
			59 => 'uncovered show',
			60 => 'covered show',
			61 => 'covered show',
			62 => 'ignored show end',
		 	63 => 'ignored',
			64 => 'covered show start',
			65 => 'ignored show',
			66 => 'covered show show',
			67 => 'uncovered show',
			68 => 'ignored show',
			69 => 'uncovered show',
			70 => 'uncovered show',
			71 => 'covered show',
			72 => 'ignored show end',
		);
		$execCodeLines = range(0, 72);
		$result = explode("</div>", $report = $manager->reportCaseHtmlDiff($testObjectFile, $coverageData, $execCodeLines, 3));

		foreach ($result as $line) {
			preg_match('/<span class="line-num">(.*?)<\/span>/', $line, $matches);
			if (!isset($matches[1])) {
				continue;
			}

			$num = $matches[1];
			$class = $expected[$num];
			$pattern = '/<div class="code-line '.$class.'">/';
			$this->assertTrue(preg_match($pattern, $line), $num.': '.$line." fails");
		}
	}

	function testArrayStrrpos() {
		$manager =& CodeCoverageManager::getInstance();

		$a = array(
			'apples',
			'bananas',
			'oranges'
		);
		$this->assertEqual(1, $manager->__array_strpos($a, 'ba', true));
		$this->assertEqual(2, $manager->__array_strpos($a, 'range', true));
		$this->assertEqual(0, $manager->__array_strpos($a, 'pp', true));
		$this->assertFalse($manager->__array_strpos('', 'ba', true));
		$this->assertFalse($manager->__array_strpos(false, 'ba', true));
		$this->assertFalse($manager->__array_strpos(array(), 'ba', true));

		$a = array(
			'rang',
			'orange',
			'oranges'
		);
		$this->assertEqual(0, $manager->__array_strpos($a, 'rang'));
		$this->assertEqual(2, $manager->__array_strpos($a, 'rang', true));
		$this->assertEqual(1, $manager->__array_strpos($a, 'orange', false));
		$this->assertEqual(1, $manager->__array_strpos($a, 'orange'));
		$this->assertEqual(2, $manager->__array_strpos($a, 'orange', true));
	}

	function testGetExecutableLines() {
		$manager =& CodeCoverageManager::getInstance();
		$code = <<<HTML
			\$manager =& CodeCoverageManager::getInstance();
HTML;
		$result = $manager->__getExecutableLines($code);
		foreach ($result as $line) {
			$this->assertNotIdentical($line, '');
		}

		$code = <<<HTML
		{
		}
		<?php?>
		?>
		<?
}
{{}}
(())
		@codeCoverageIgnoreStart
		some
		more
		code
		here
		@codeCoverageIgnoreEnd
HTML;
		$result = $manager->__getExecutableLines($code);
		foreach ($result as $line) {
			$this->assertIdentical(trim($line), '');
		}
	}

	function testCalculateCodeCoverage() {
		$manager =& CodeCoverageManager::getInstance();
		$data = array(
			'25' => array(100, 25),
			'50' => array(100, 50),
			'0' => array(0, 0),
			'0' => array(100, 0),
			'100' => array(100, 100),
		);
		foreach ($data as $coverage => $lines) {
			$this->assertEqual($coverage, $manager->__calcCoverage($lines[0], $lines[1]));
		}

		$manager->__calcCoverage(100, 1000);
		$this->assertError();
	}
}
?>