aggregate code coverage for groups

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6760 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
DarkAngelBGE 2008-05-05 14:41:27 +00:00
parent 3263c0bc81
commit 44fae51ce8
6 changed files with 342 additions and 53 deletions

View file

@ -455,4 +455,19 @@ div.code-coverage-results div.end {
} }
div.code-coverage-results div.realstart { div.code-coverage-results div.realstart {
margin-top:0px; 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;
} }

View file

@ -123,9 +123,17 @@ if (isset($_GET['group'])) {
if ('all' == $_GET['group']) { if ('all' == $_GET['group']) {
TestManager::runAllTests(CakeTestsGetReporter()); TestManager::runAllTests(CakeTestsGetReporter());
} else { } else {
if ($analyzeCodeCoverage) {
CodeCoverageManager::start($_GET['group'], CakeTestsGetReporter());
}
TestManager::runGroupTest(ucfirst($_GET['group']), CakeTestsGetReporter()); TestManager::runGroupTest(ucfirst($_GET['group']), CakeTestsGetReporter());
if ($analyzeCodeCoverage) {
CodeCoverageManager::report();
}
} }
CakePHPTestRunMore(); CakePHPTestRunMore();
CakePHPTestAnalyzeCodeCoverage();
} elseif (isset($_GET['case'])) { } elseif (isset($_GET['case'])) {
if ($analyzeCodeCoverage) { if ($analyzeCodeCoverage) {

View file

@ -247,6 +247,13 @@ class TestSuiteShell extends Shell {
return TestManager::runAllTests($reporter); 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') { if ($this->type == 'group') {
$ucFirstGroup = ucfirst($this->file); $ucFirstGroup = ucfirst($this->file);
@ -257,7 +264,15 @@ class TestSuiteShell extends Shell {
$path = APP.'plugins'.DS.$this->category.DS.'tests'.DS.'groups'; $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'; $case = 'libs'.DS.$this->file.'.test.php';
@ -268,11 +283,6 @@ class TestSuiteShell extends Shell {
} }
if ($this->doCoverage) { 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'; require_once CAKE . 'tests' . DS . 'lib' . DS . 'code_coverage_manager.php';
CodeCoverageManager::start($case, $reporter); CodeCoverageManager::start($case, $reporter);
} }
@ -317,6 +327,9 @@ class TestSuiteShell extends Shell {
} elseif ($this->category == 'app') { } elseif ($this->category == 'app') {
$_GET['app'] = true; $_GET['app'] = true;
} }
if ($this->type == 'group') {
$_GET['group'] = true;
}
} }
/** /**
* tries to install simpletest and exits gracefully if it is not there * tries to install simpletest and exits gracefully if it is not there

View file

@ -1,5 +1,5 @@
<?php <?php
/* SVN FILE: $Id: http_socket.test.php 6563 2008-03-12 21:19:31Z phpnut $ */ /* SVN FILE: $Id: code_coverage_manager.test.php 6563 2008-03-12 21:19:31Z phpnut $ */
/** /**
* Short description for file. * Short description for file.
* *
@ -230,7 +230,7 @@ PHP;
43 => -1, 43 => -1,
); );
$execCodeLines = range(0, 72); $execCodeLines = range(0, 72);
$result = explode("</div>", $report = $manager->reportHtml($testObjectFile, $coverageData, $execCodeLines)); $result = explode("</div>", $report = $manager->reportCaseHtml($testObjectFile, $coverageData, $execCodeLines));
foreach ($result as $num => $line) { foreach ($result as $num => $line) {
$num++; $num++;
@ -483,7 +483,7 @@ PHP;
72 => 'ignored show end', 72 => 'ignored show end',
); );
$execCodeLines = range(0, 72); $execCodeLines = range(0, 72);
$result = explode("</div>", $report = $manager->reportHtmlDiff($testObjectFile, $coverageData, $execCodeLines, 3)); $result = explode("</div>", $report = $manager->reportCaseHtmlDiff($testObjectFile, $coverageData, $execCodeLines, 3));
foreach ($result as $line) { foreach ($result as $line) {
preg_match('/<span class="line-num">(.*?)<\/span>/', $line, $matches); preg_match('/<span class="line-num">(.*?)<\/span>/', $line, $matches);

View file

@ -46,6 +46,13 @@ class CodeCoverageManager {
* @var string * @var string
*/ */
var $pluginTest = false; var $pluginTest = false;
/**
* Is this a grouptest?
*
* @var string
* @access public
*/
var $groupTest = false;
/** /**
* The test case file to analyze * The test case file to analyze
* *
@ -98,7 +105,9 @@ class CodeCoverageManager {
if (isset($_GET['app'])) { if (isset($_GET['app'])) {
$manager->appTest = true; $manager->appTest = true;
} }
if (isset($_GET['group'])) {
$manager->groupTest = true;
}
if (isset($_GET['plugin'])) { if (isset($_GET['plugin'])) {
$manager->pluginTest = Inflector::underscore($_GET['plugin']); $manager->pluginTest = Inflector::underscore($_GET['plugin']);
} }
@ -114,40 +123,79 @@ class CodeCoverageManager {
function report($output = true) { function report($output = true) {
$manager =& CodeCoverageManager::getInstance(); $manager =& CodeCoverageManager::getInstance();
$testObjectFile = $manager->__testObjectFileFromCaseFile($manager->testCaseFile, $manager->appTest); if (!$manager->groupTest) {
$testObjectFile = $manager->__testObjectFileFromCaseFile($manager->testCaseFile, $manager->appTest);
if (!file_exists($testObjectFile)) { if (!file_exists($testObjectFile)) {
trigger_error('This test object file is invalid: '.$testObjectFile); trigger_error('This test object file is invalid: '.$testObjectFile);
return ; return ;
} }
$dump = xdebug_get_code_coverage(); $dump = xdebug_get_code_coverage();
$coverageData = array(); $coverageData = array();
foreach ($dump as $file => $data) { foreach ($dump as $file => $data) {
if ($file == $testObjectFile) { if ($file == $testObjectFile) {
$coverageData = $data; $coverageData = $data;
break; 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) { if ($output) {
echo $result; echo $result;
} }
@ -161,7 +209,7 @@ class CodeCoverageManager {
* @param string $output * @param string $output
* @return void * @return void
*/ */
function reportHtml($testObjectFile, $coverageData, $execCodeLines) { function reportCaseHtml($testObjectFile, $coverageData, $execCodeLines) {
$manager = CodeCoverageManager::getInstance(); $manager = CodeCoverageManager::getInstance();
$lineCount = $coveredCount = 0; $lineCount = $coveredCount = 0;
$report = ''; $report = '';
@ -198,7 +246,7 @@ class CodeCoverageManager {
* @param string $output * @param string $output
* @return void * @return void
*/ */
function reportHtmlDiff($testObjectFile, $coverageData, $execCodeLines, $numContextLines) { function reportCaseHtmlDiff($testObjectFile, $coverageData, $execCodeLines, $numContextLines) {
$manager = CodeCoverageManager::getInstance(); $manager = CodeCoverageManager::getInstance();
$total = count($testObjectFile); $total = count($testObjectFile);
$lines = array(); $lines = array();
@ -316,7 +364,7 @@ class CodeCoverageManager {
* @param string $output * @param string $output
* @return void * @return void
*/ */
function reportCli($testObjectFile, $coverageData, $execCodeLines) { function reportCaseCli($testObjectFile, $coverageData, $execCodeLines) {
$manager = CodeCoverageManager::getInstance(); $manager = CodeCoverageManager::getInstance();
$lineCount = $coveredCount = 0; $lineCount = $coveredCount = 0;
$report = ''; $report = '';
@ -338,6 +386,85 @@ class CodeCoverageManager {
return $manager->__paintHeaderCli($lineCount, $coveredCount, $report); 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 * 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) { function __testObjectFileFromCaseFile($file, $isApp = true) {
$manager = CodeCoverageManager::getInstance(); $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( $folderPrefixMap = array(
'behaviors' => 'models', 'behaviors' => 'models',
'components' => 'controllers', 'components' => 'controllers',
@ -388,6 +507,53 @@ class CodeCoverageManager {
return $path; 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 * 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 * amount of new lines in order for the code line numbers to stay in sync
@ -397,6 +563,15 @@ class CodeCoverageManager {
* @access private * @access private
*/ */
function __getExecutableLines($content) { 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); $content = h($content);
// arrays are 0-indexed, but we want 1-indexed stuff now as we are talking code lines mind you (**) // 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 = '<h2>Code Coverage: '.$codeCoverage.'%</h2> return $report = '<h2>Code Coverage: '.$codeCoverage.'%</h2>
<div class="code-coverage-results"><pre>'.$report.'</pre></div>'; <div class="code-coverage-results"><pre>'.$report.'</pre></div>';
} }
/**
* Displays a notification concerning group test results
*
* @return void
* @access public
*/
function __paintGroupResultHeader($report) {
return '<div class="code-coverage-results"><p class="note">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.</p><pre>'.$report.'</pre></div>';
}
/**
* 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 '<p>Code Coverage for '.$file.': <span class="'.$class.'">'.$codeCoverage.'%</span></p>';
}
/**
* 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 * Paints the headline for code coverage analysis in the CLI
* *
@ -487,6 +713,26 @@ class CodeCoverageManager {
? round(100*$coveredCount/$lineCount, 2) ? round(100*$coveredCount/$lineCount, 2)
: '0.00'; : '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 * Finds the last element of an array that contains $needle in a strpos computation
* *

View file

@ -458,9 +458,16 @@ if (function_exists('caketestsgetreporter')) {
} elseif (isset($_GET['plugin'])) { } elseif (isset($_GET['plugin'])) {
$query .= '&amp;plugin=' . $_GET['plugin']; $query .= '&amp;plugin=' . $_GET['plugin'];
} }
$query .= '&amp;code_coverage=true'; } else {
echo "<p><a href='" . RUN_TEST_LINK . $query . "'>Analyze Code Coverage</a></p>\n"; $query = '?group='.$_GET['group'];
if (isset($_GET['app'])) {
$query .= '&amp;app=true';
} elseif (isset($_GET['plugin'])) {
$query .= '&amp;plugin=' . $_GET['plugin'];
}
} }
$query .= '&amp;code_coverage=true';
echo "<p><a href='" . RUN_TEST_LINK . $query . "'>Analyze Code Coverage</a></p>\n";
break; break;
} }
} }