From 44fae51ce81d00e8d23ad4caec6daba01c1fc1c6 Mon Sep 17 00:00:00 2001 From: DarkAngelBGE Date: Mon, 5 May 2008 14:41:27 +0000 Subject: [PATCH] aggregate code coverage for groups git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6760 3807eeeb-6ff5-0310-8944-8be069107fe0 --- app/webroot/css/cake.generic.css | 15 + app/webroot/test.php | 8 + cake/console/libs/testsuite.php | 25 +- .../cases/libs/code_coverage_manager.test.php | 6 +- cake/tests/lib/code_coverage_manager.php | 330 +++++++++++++++--- cake/tests/lib/test_manager.php | 11 +- 6 files changed, 342 insertions(+), 53 deletions(-) diff --git a/app/webroot/css/cake.generic.css b/app/webroot/css/cake.generic.css index fbdabbc8f..9d9b50c67 100644 --- a/app/webroot/css/cake.generic.css +++ b/app/webroot/css/cake.generic.css @@ -455,4 +455,19 @@ div.code-coverage-results div.end { } div.code-coverage-results div.realstart { margin-top:0px; +} +div.code-coverage-results p.note { + color:#bbb; + padding:5px; + margin:5px 0 10px; + font-size:10px; +} +div.code-coverage-results span.result-bad { + color: #a00; +} +div.code-coverage-results span.result-ok { + color: #fa0; +} +div.code-coverage-results span.result-good { + color: #0a0; } \ No newline at end of file diff --git a/app/webroot/test.php b/app/webroot/test.php index 66c8d5a75..19e5a32f1 100644 --- a/app/webroot/test.php +++ b/app/webroot/test.php @@ -123,9 +123,17 @@ if (isset($_GET['group'])) { if ('all' == $_GET['group']) { TestManager::runAllTests(CakeTestsGetReporter()); } else { + if ($analyzeCodeCoverage) { + CodeCoverageManager::start($_GET['group'], CakeTestsGetReporter()); + } TestManager::runGroupTest(ucfirst($_GET['group']), CakeTestsGetReporter()); + if ($analyzeCodeCoverage) { + CodeCoverageManager::report(); + } } + CakePHPTestRunMore(); + CakePHPTestAnalyzeCodeCoverage(); } elseif (isset($_GET['case'])) { if ($analyzeCodeCoverage) { diff --git a/cake/console/libs/testsuite.php b/cake/console/libs/testsuite.php index 5ee0f46b4..953b7a8ea 100644 --- a/cake/console/libs/testsuite.php +++ b/cake/console/libs/testsuite.php @@ -247,6 +247,13 @@ class TestSuiteShell extends Shell { return TestManager::runAllTests($reporter); } + if ($this->doCoverage) { + if (!extension_loaded('xdebug')) { + $this->out('You must install Xdebug to use the CakePHP(tm) Code Coverage Analyzation. Download it from http://www.xdebug.org/docs/install'); + exit(0); + } + } + if ($this->type == 'group') { $ucFirstGroup = ucfirst($this->file); @@ -257,7 +264,15 @@ class TestSuiteShell extends Shell { $path = APP.'plugins'.DS.$this->category.DS.'tests'.DS.'groups'; } - return TestManager::runGroupTest($ucFirstGroup, $reporter); + if ($this->doCoverage) { + require_once CAKE . 'tests' . DS . 'lib' . DS . 'code_coverage_manager.php'; + CodeCoverageManager::start($ucFirstGroup, $reporter); + } + $result = TestManager::runGroupTest($ucFirstGroup, $reporter); + if ($this->doCoverage) { + CodeCoverageManager::report(); + } + return $result; } $case = 'libs'.DS.$this->file.'.test.php'; @@ -268,11 +283,6 @@ class TestSuiteShell extends Shell { } if ($this->doCoverage) { - if (!extension_loaded('xdebug')) { - $this->out('You must install Xdebug to use the CakePHP(tm) Code Coverage Analyzation. Download it from http://www.xdebug.org/docs/install'); - exit(0); - } - require_once CAKE . 'tests' . DS . 'lib' . DS . 'code_coverage_manager.php'; CodeCoverageManager::start($case, $reporter); } @@ -317,6 +327,9 @@ class TestSuiteShell extends Shell { } elseif ($this->category == 'app') { $_GET['app'] = true; } + if ($this->type == 'group') { + $_GET['group'] = true; + } } /** * tries to install simpletest and exits gracefully if it is not there diff --git a/cake/tests/cases/libs/code_coverage_manager.test.php b/cake/tests/cases/libs/code_coverage_manager.test.php index 5f84c597c..e6d4b99fd 100644 --- a/cake/tests/cases/libs/code_coverage_manager.test.php +++ b/cake/tests/cases/libs/code_coverage_manager.test.php @@ -1,5 +1,5 @@ -1, ); $execCodeLines = range(0, 72); - $result = explode("", $report = $manager->reportHtml($testObjectFile, $coverageData, $execCodeLines)); + $result = explode("", $report = $manager->reportCaseHtml($testObjectFile, $coverageData, $execCodeLines)); foreach ($result as $num => $line) { $num++; @@ -483,7 +483,7 @@ PHP; 72 => 'ignored show end', ); $execCodeLines = range(0, 72); - $result = explode("", $report = $manager->reportHtmlDiff($testObjectFile, $coverageData, $execCodeLines, 3)); + $result = explode("", $report = $manager->reportCaseHtmlDiff($testObjectFile, $coverageData, $execCodeLines, 3)); foreach ($result as $line) { preg_match('/(.*?)<\/span>/', $line, $matches); diff --git a/cake/tests/lib/code_coverage_manager.php b/cake/tests/lib/code_coverage_manager.php index 55efa1477..c3217256a 100644 --- a/cake/tests/lib/code_coverage_manager.php +++ b/cake/tests/lib/code_coverage_manager.php @@ -46,6 +46,13 @@ class CodeCoverageManager { * @var string */ var $pluginTest = false; +/** + * Is this a grouptest? + * + * @var string + * @access public + */ + var $groupTest = false; /** * The test case file to analyze * @@ -98,7 +105,9 @@ class CodeCoverageManager { if (isset($_GET['app'])) { $manager->appTest = true; } - + if (isset($_GET['group'])) { + $manager->groupTest = true; + } if (isset($_GET['plugin'])) { $manager->pluginTest = Inflector::underscore($_GET['plugin']); } @@ -114,40 +123,79 @@ class CodeCoverageManager { function report($output = true) { $manager =& CodeCoverageManager::getInstance(); - $testObjectFile = $manager->__testObjectFileFromCaseFile($manager->testCaseFile, $manager->appTest); + if (!$manager->groupTest) { + $testObjectFile = $manager->__testObjectFileFromCaseFile($manager->testCaseFile, $manager->appTest); - if (!file_exists($testObjectFile)) { - trigger_error('This test object file is invalid: '.$testObjectFile); - return ; - } + if (!file_exists($testObjectFile)) { + trigger_error('This test object file is invalid: '.$testObjectFile); + return ; + } - $dump = xdebug_get_code_coverage(); - $coverageData = array(); - foreach ($dump as $file => $data) { - if ($file == $testObjectFile) { - $coverageData = $data; - break; + $dump = xdebug_get_code_coverage(); + $coverageData = array(); + foreach ($dump as $file => $data) { + if ($file == $testObjectFile) { + $coverageData = $data; + break; + } + } + + if (empty($coverageData) && $output) { + echo 'The test object file is never loaded.'; + } + + $execCodeLines = $manager->__getExecutableLines(file_get_contents($testObjectFile)); + $result = ''; + + switch (get_class($manager->reporter)) { + case 'CakeHtmlReporter': + $result = $manager->reportCaseHtmlDiff(@file($testObjectFile), $coverageData, $execCodeLines, $manager->numDiffContextLines); + break; + case 'CLIReporter': + $result = $manager->reportCaseCli(@file($testObjectFile), $coverageData, $execCodeLines, $manager->numDiffContextLines); + break; + default: + trigger_error('Currently only HTML and CLI reporting is supported for code coverage analysis.'); + break; + } + } else { + $testObjectFiles = $manager->__testObjectFilesFromGroupFile($manager->testCaseFile, $manager->appTest); + + foreach ($testObjectFiles as $file) { + if (!file_exists($file)) { + trigger_error('This test object file is invalid: '.$file); + return ; + } + } + + $dump = xdebug_get_code_coverage(); + $coverageData = array(); + foreach ($dump as $file => $data) { + if (in_array($file, $testObjectFiles)) { + $coverageData[$file] = $data; + } + } + + if (empty($coverageData) && $output) { + echo 'The test object files are never loaded.'; + } + + $execCodeLines = $manager->__getExecutableLines($testObjectFiles); + $result = ''; + + switch (get_class($manager->reporter)) { + case 'CakeHtmlReporter': + $result = $manager->reportGroupHtml($testObjectFiles, $coverageData, $execCodeLines, $manager->numDiffContextLines); + break; + case 'CLIReporter': + $result = $manager->reportGroupCli($testObjectFiles, $coverageData, $execCodeLines, $manager->numDiffContextLines); + break; + default: + trigger_error('Currently only HTML and CLI reporting is supported for code coverage analysis.'); + break; } } - if (empty($coverageData) && $output) { - echo 'The test object file is never loaded.'; - } - - $execCodeLines = $manager->__getExecutableLines(file_get_contents($testObjectFile)); - $result = ''; - switch (get_class($manager->reporter)) { - case 'CakeHtmlReporter': - $result = $manager->reportHtmlDiff(@file($testObjectFile), $coverageData, $execCodeLines, $manager->numDiffContextLines); - break; - case 'CLIReporter': - $result = $manager->reportCli(@file($testObjectFile), $coverageData, $execCodeLines, $manager->numDiffContextLines); - break; - default: - trigger_error('Currently only HTML reporting is supported for code coverage analysis.'); - break; - } - if ($output) { echo $result; } @@ -161,7 +209,7 @@ class CodeCoverageManager { * @param string $output * @return void */ - function reportHtml($testObjectFile, $coverageData, $execCodeLines) { + function reportCaseHtml($testObjectFile, $coverageData, $execCodeLines) { $manager = CodeCoverageManager::getInstance(); $lineCount = $coveredCount = 0; $report = ''; @@ -198,7 +246,7 @@ class CodeCoverageManager { * @param string $output * @return void */ - function reportHtmlDiff($testObjectFile, $coverageData, $execCodeLines, $numContextLines) { + function reportCaseHtmlDiff($testObjectFile, $coverageData, $execCodeLines, $numContextLines) { $manager = CodeCoverageManager::getInstance(); $total = count($testObjectFile); $lines = array(); @@ -316,7 +364,7 @@ class CodeCoverageManager { * @param string $output * @return void */ - function reportCli($testObjectFile, $coverageData, $execCodeLines) { + function reportCaseCli($testObjectFile, $coverageData, $execCodeLines) { $manager = CodeCoverageManager::getInstance(); $lineCount = $coveredCount = 0; $report = ''; @@ -338,6 +386,85 @@ class CodeCoverageManager { return $manager->__paintHeaderCli($lineCount, $coveredCount, $report); } +/** + * Diff reporting + * + * @param string $testObjectFile + * @param string $coverageData + * @param string $execCodeLines + * @param string $output + * @return void + */ + function reportGroupHtml($testObjectFiles, $coverageData, $execCodeLines, $numContextLines) { + $manager = CodeCoverageManager::getInstance(); + $report = ''; + + foreach ($testObjectFiles as $testObjectFile) { + $lineCount = $coveredCount = 0; + $objFilename = $testObjectFile; + $testObjectFile = file($testObjectFile); + + foreach ($testObjectFile as $num => $line) { + $num++; + + $foundByManualFinder = array_key_exists($num, $execCodeLines[$objFilename]) && trim($execCodeLines[$objFilename][$num]) != ''; + $foundByXdebug = array_key_exists($num, $coverageData[$objFilename]) && $coverageData[$objFilename][$num] !== -2; + + if ($foundByManualFinder && $foundByXdebug) { + $class = 'uncovered'; + $lineCount++; + + if ($coverageData[$objFilename][$num] > 0) { + $class = 'covered'; + $coveredCount++; + } + } else { + $class = 'ignored'; + } + } + + $report .= $manager->__paintGroupResultLine($objFilename, $lineCount, $coveredCount); + } + return $manager->__paintGroupResultHeader($report); + } +/** + * CLI reporting + * + * @param string $testObjectFile + * @param string $coverageData + * @param string $execCodeLines + * @param string $output + * @return void + */ + function reportGroupCli($testObjectFiles, $coverageData, $execCodeLines) { + $manager = CodeCoverageManager::getInstance(); + $report = ''; + + foreach ($testObjectFiles as $testObjectFile) { + $lineCount = $coveredCount = 0; + $objFilename = $testObjectFile; + $testObjectFile = file($testObjectFile); + + foreach ($testObjectFile as $num => $line) { + $num++; + + $foundByManualFinder = array_key_exists($num, $execCodeLines[$objFilename]) && trim($execCodeLines[$objFilename][$num]) != ''; + $foundByXdebug = array_key_exists($num, $coverageData[$objFilename]) && $coverageData[$objFilename][$num] !== -2; + + if ($foundByManualFinder && $foundByXdebug) { + $lineCount++; + + if ($coverageData[$objFilename][$num] > 0) { + $coveredCount++; + } + } + } + + $report .= $manager->__paintGroupResultLineCli($objFilename, $lineCount, $coveredCount); + } + + return $report; + } /** * Returns the name of the test object file based on a given test case file name * @@ -348,16 +475,8 @@ class CodeCoverageManager { */ function __testObjectFileFromCaseFile($file, $isApp = true) { $manager = CodeCoverageManager::getInstance(); + $path = $manager->__getTestFilesPath($isApp); - $path = ROOT.DS; - if ($isApp) { - $path .= APP_DIR.DS; - } elseif (!!$manager->pluginTest) { - $path .= APP_DIR.DS.'plugins'.DS.$manager->pluginTest.DS; - } else { - $path = ROOT.DS.'cake'.DS; - } - $folderPrefixMap = array( 'behaviors' => 'models', 'components' => 'controllers', @@ -388,6 +507,53 @@ class CodeCoverageManager { return $path; } +/** + * Returns an array of names of the test object files based on a given test group file name + * + * @param array $files + * @param string $isApp + * @return array names of the test object files + * @access private + */ + function __testObjectFilesFromGroupFile($groupFile, $isApp = true) { + $manager = CodeCoverageManager::getInstance(); + $testManager =& new TestManager(); + + $path = TESTS.'groups'; + if (!$isApp) { + $path = ROOT.DS.'cake'.DS.'tests'.DS.'groups'; + } + if (!!$manager->pluginTest) { + $path = APP.'plugins'.DS.$manager->pluginTest.DS.'tests'.DS.'groups'; + } + $path .= DS.$groupFile.$testManager->_groupExtension; + + if (!file_exists($path)) { + trigger_error('This group file does not exist!'); + return array(); + } + + $groupContent = file_get_contents($path); + $ds = '\s*\.\s*DS\s*\.\s*'; + $pluginTest = 'APP\.\'plugins\''.$ds.'\''.$manager->pluginTest.'\''.$ds.'\'tests\''.$ds.'\'cases\''; + $pattern = '/\s*TestManager::addTestFile\(\s*\$this,\s*('.$pluginTest.'|APP_TEST_CASES|CORE_TEST_CASES)'.$ds.'(.*?)\)/i'; + preg_match_all($pattern, $groupContent, $matches); + + $result = array(); + foreach ($matches[2] as $file) { + $patterns = array( + '/\s*\.\s*DS\s*\.\s*/', + '/\s*APP_TEST_CASES\s*/', + '/\s*CORE_TEST_CASES\s*/', + ); + $replacements = array(DS, '', ''); + $file = preg_replace($patterns, $replacements, $file); + $file = r("'", '', $file); + $result[] = $manager->__testObjectFileFromCaseFile($file, $isApp).'.php'; + } + + return $result; + } /** * Parses a given code string into an array of lines and replaces some non-executable code lines with the needed * amount of new lines in order for the code line numbers to stay in sync @@ -397,6 +563,15 @@ class CodeCoverageManager { * @access private */ function __getExecutableLines($content) { + if (is_array($content)) { + $manager = CodeCoverageManager::getInstance(); + $result = array(); + foreach ($content as $file) { + $result[$file] = $manager->__getExecutableLines(file_get_contents($file)); + } + return $result; + } + $content = h($content); // arrays are 0-indexed, but we want 1-indexed stuff now as we are talking code lines mind you (**) @@ -444,6 +619,57 @@ class CodeCoverageManager { return $report = '

Code Coverage: '.$codeCoverage.'%

'.$report.'
'; } +/** + * Displays a notification concerning group test results + * + * @return void + * @access public + */ + function __paintGroupResultHeader($report) { + return '

Please keep in mind that the coverage can vary a little bit depending on how much the different tests in the group interfere. If for example, TEST A calls a line from TEST OBJECT B, the coverage for TEST OBJECT B will be a little greater than if you were running the corresponding test case for TEST OBJECT B alone.

'.$report.'
'; + } +/** + * Paints the headline for code coverage analysis + * + * @param string $codeCoverage + * @param string $report + * @return void + * @access private + */ + function __paintGroupResultLine($file, $lineCount, $coveredCount) { + $manager =& CodeCoverageManager::getInstance(); + $codeCoverage = $manager->__calcCoverage($lineCount, $coveredCount); + + $class = 'result-bad'; + if ($codeCoverage > 50) { + $class = 'result-ok'; + } + if ($codeCoverage > 80) { + $class = 'result-good'; + } + return '

Code Coverage for '.$file.': '.$codeCoverage.'%

'; + } +/** + * Paints the headline for code coverage analysis + * + * @param string $codeCoverage + * @param string $report + * @return void + * @access private + */ + function __paintGroupResultLineCli($file, $lineCount, $coveredCount) { + $manager =& CodeCoverageManager::getInstance(); + $codeCoverage = $manager->__calcCoverage($lineCount, $coveredCount); + + $class = 'bad'; + if ($codeCoverage > 50) { + $class = 'ok'; + } + if ($codeCoverage > 80) { + $class = 'good'; + } + return "\n".'Code Coverage for '.$file.': '.$codeCoverage.'% ('.$class.')'."\n"; + } /** * Paints the headline for code coverage analysis in the CLI * @@ -487,6 +713,26 @@ class CodeCoverageManager { ? round(100*$coveredCount/$lineCount, 2) : '0.00'; } +/** + * Gets us the base path to look for the test files + * + * @param string $isApp + * @return void + * @access public + */ + function __getTestFilesPath($isApp = true) { + $manager = CodeCoverageManager::getInstance(); + $path = ROOT.DS; + if ($isApp) { + $path .= APP_DIR.DS; + } elseif (!!$manager->pluginTest) { + $path .= APP_DIR.DS.'plugins'.DS.$manager->pluginTest.DS; + } else { + $path = ROOT.DS.'cake'.DS; + } + + return $path; + } /** * Finds the last element of an array that contains $needle in a strpos computation * diff --git a/cake/tests/lib/test_manager.php b/cake/tests/lib/test_manager.php index a63d98723..7170267cf 100644 --- a/cake/tests/lib/test_manager.php +++ b/cake/tests/lib/test_manager.php @@ -458,9 +458,16 @@ if (function_exists('caketestsgetreporter')) { } elseif (isset($_GET['plugin'])) { $query .= '&plugin=' . $_GET['plugin']; } - $query .= '&code_coverage=true'; - echo "

Analyze Code Coverage

\n"; + } else { + $query = '?group='.$_GET['group']; + if (isset($_GET['app'])) { + $query .= '&app=true'; + } elseif (isset($_GET['plugin'])) { + $query .= '&plugin=' . $_GET['plugin']; + } } + $query .= '&code_coverage=true'; + echo "

Analyze Code Coverage

\n"; break; } }