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

@ -456,3 +456,18 @@ 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;
}

View file

@ -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) {

View file

@ -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

View file

@ -1,5 +1,5 @@
<?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.
*
@ -230,7 +230,7 @@ PHP;
43 => -1,
);
$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) {
$num++;
@ -483,7 +483,7 @@ PHP;
72 => 'ignored show end',
);
$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) {
preg_match('/<span class="line-num">(.*?)<\/span>/', $line, $matches);

View file

@ -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,38 +123,77 @@ 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 ;
}
$dump = xdebug_get_code_coverage();
$coverageData = array();
foreach ($dump as $file => $data) {
if ($file == $testObjectFile) {
$coverageData = $data;
break;
if (!file_exists($testObjectFile)) {
trigger_error('This test object file is invalid: '.$testObjectFile);
return ;
}
}
if (empty($coverageData) && $output) {
echo 'The test object file is never loaded.';
}
$dump = xdebug_get_code_coverage();
$coverageData = array();
foreach ($dump as $file => $data) {
if ($file == $testObjectFile) {
$coverageData = $data;
break;
}
}
$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 (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 ($output) {
@ -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,15 +475,7 @@ class CodeCoverageManager {
*/
function __testObjectFileFromCaseFile($file, $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;
}
$path = $manager->__getTestFilesPath($isApp);
$folderPrefixMap = array(
'behaviors' => 'models',
@ -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 = '<h2>Code Coverage: '.$codeCoverage.'%</h2>
<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
*
@ -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
*

View file

@ -458,9 +458,16 @@ if (function_exists('caketestsgetreporter')) {
} 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";
} else {
$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;
}
}