Thursday, November 6, 2008

Enhancements to cakephp testing and code coverage

Hello,

I have been using cakephp for a while, and I have made some updates to
the cakephp testing and code coverage framework which I would like to
submit for review. If there is a better way to achieve this, or this
already exists in cakephp, I would be grateful if someone can send me
pointers.

Changes included in the patch
- Add test hook functions to email sending, Session flash display and
page redirection to enable asserting these operations in the test
suite.

- Remove caching of DB schema during test suite execution. Without
this test suite fails when there are many models & controllers that
use different fixtures

- Enhancements to allow code coverage when executing group test cases,
specifically ALL
test cases, and provide a clickable file list for code coverage
summary.

- Enable code coverage tracking only while running the actual test
cases, instead of all code.
Reason: Much quicker execution test suite execution with coverage.

Also is there a good way of checking is the test suite is running as
opposed to real code? I have noticed in different places where they
check the url for the pattern 'test.php', which might fail if this
pattern is part of the url.
I am using a define called 'TEST_SUITE' which I am setting at the
start of the test suite execution, but there should be a better way of
doing this.

The patch follows below.

Cheers,
/Chandan


diff -r 16c58dcbc810 cake/libs/controller/components/email.php
--- a/cake/libs/controller/components/email.php Fri Nov 07 00:17:46
2008 +1100
+++ b/cake/libs/controller/components/email.php Fri Nov 07 01:06:33
2008 +1100
@@ -665,6 +665,18 @@
}
return @mail($this->to, $this->__encode($this->subject), $this-
>__message, $this->__header, $this->additionalParams);
}
+
+/**
+ * Wrapper for Test Suite email testing.
+ * Expects a function called testmail() to handle mail delivery.
+ *
+ * @return bool Success
+ * @access private
+ */
+ function __test() {
+ return @testmail($this->to, $this->__encode($this->subject), $this-
>__message, $this->__header, $this->additionalParams);
+ }
+
/**
* Sends out email via SMTP
*
diff -r 16c58dcbc810 cake/libs/controller/components/session.php
--- a/cake/libs/controller/components/session.php Fri Nov 07 00:17:46
2008 +1100
+++ b/cake/libs/controller/components/session.php Fri Nov 07 01:06:33
2008 +1100
@@ -229,10 +229,14 @@
* @access public
*/
function setFlash($message, $layout = 'default', $params = array(),
$key = 'flash') {
- if ($this->__active === true) {
- $this->__start();
- $this->write('Message.' . $key, compact('message', 'layout',
'params'));
- }
+ if(defined('TEST_SUITE')) {
+ testFlashCallback($message, $params);
+ }
+
+ if ($this->__active === true) {
+ $this->__start();
+ $this->write('Message.' . $key, compact('message', 'layout',
'params'));
+ }
}
/**
* Used to renew a session id
diff -r 16c58dcbc810 cake/libs/controller/controller.php
--- a/cake/libs/controller/controller.php Fri Nov 07 00:17:46 2008
+1100
+++ b/cake/libs/controller/controller.php Fri Nov 07 01:06:33 2008
+1100
@@ -472,6 +472,11 @@
* @access public
*/
function redirect($url, $status = null, $exit = true) {
+ if (defined('TEST_SUITE')) {
+ setRedirectURL($url);
+ return;
+ }
+
$this->autoRender = false;

if (is_array($status)) {
@@ -1077,4 +1082,4 @@
return false;
}
}
-?>
\ No newline at end of file
+?>
diff -r 16c58dcbc810 cake/libs/model/datasources/dbo/dbo_mysql.php
--- a/cake/libs/model/datasources/dbo/dbo_mysql.php Fri Nov 07
00:17:46 2008 +1100
+++ b/cake/libs/model/datasources/dbo/dbo_mysql.php Fri Nov 07
01:06:33 2008 +1100
@@ -158,6 +158,12 @@
*/
function listSources() {
$cache = parent::listSources();
+ if (defined('TEST_SUITE')) {
+ /* During testing we keep adding and dropping tables.
+ * Relying on cache can cause tests to fail
+ */
+ $cache = null;
+ }
if ($cache != null) {
return $cache;
}
@@ -589,4 +595,4 @@
return $out;
}
}
-?>
\ No newline at end of file
+?>
diff -r 16c58dcbc810 cake/tests/lib/code_coverage_manager.php
--- a/cake/tests/lib/code_coverage_manager.php Fri Nov 07 00:17:46
2008 +1100
+++ b/cake/tests/lib/code_coverage_manager.php Fri Nov 07 01:06:33
2008 +1100
@@ -116,6 +116,65 @@
$manager->testCaseFile = $testCaseFile;
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
}
+
+ /* Added by CK for group coverage testing */
+ var $groupCoverageData = array();
+ var $groupTestCase = '';
+ function startGroupCoverage($testCaseGroup, &$reporter) {
+ $manager =& CodeCoverageManager::getInstance();
+ if (!$manager->testCaseFile)
+ $manager->testCaseFile = $testCaseGroup;
+ if (!$manager->reporter)
+ $manager->reporter = $reporter;
+
+ if (isset($_GET['app'])) {
+ $manager->appTest = true;
+ }
+
+ if (isset($_GET['group'])) {
+ $manager->groupTest = true;
+ }
+
+ if (isset($_GET['plugin'])) {
+ $manager->pluginTest = Inflector::underscore($_GET['plugin']);
+ }
+
+ xdebug_start_code_coverage(XDEBUG_CC_UNUSED );
+ }
+
+ function pauseGroupCoverage() {
+ $dump = xdebug_get_code_coverage();
+ $manager =& CodeCoverageManager::getInstance();
+ $manager->mergeCoverage($dump);
+
+ xdebug_stop_code_coverage();
+ }
+
+ function reportGroupCoverage() {
+ $fd = fopen('/tmp/xdd.log', 'w');
+ $manager =& CodeCoverageManager::getInstance();
+ $manager->mergeCoverage($manager->groupCoverageData);
+ fwrite($fd, print_r($manager->groupCoverageData, true));
+ fclose($fd);
+ }
+
+ function mergeCoverage($dump) {
+ foreach ($dump as $file => $cov) {
+ if (isset($this->groupCoverageData[$file])) {
+ $so_far = &$this->groupCoverageData[$file];
+ foreach ($cov as $line => $count) {
+ if (isset($so_far[$line])) {
+ $so_far[$line] += $count;
+ } else {
+ $so_far[$line] = $count;
+ }
+ }
+ } else {
+ $this->groupCoverageData[$file] = $cov;
+ }
+ }
+ }
+
/**
* Stops the current code coverage analyzation and dumps a nice
report depending on the reporter that was passed to start()
*
@@ -131,8 +190,11 @@
trigger_error('This test object file is invalid: ' .
$testObjectFile);
return ;
}
+ /*
$dump = xdebug_get_code_coverage();
xdebug_stop_code_coverage();
+ */
+ $dump = $manager->groupCoverageData;
$coverageData = array();

foreach ($dump as $file => $data) {
@@ -150,7 +212,7 @@

switch (get_class($manager->reporter)) {
case 'CakeHtmlReporter':
- $result = $manager->reportCaseHtmlDiff(@file($testObjectFile),
$coverageData, $execCodeLines, $manager->numDiffContextLines);
+ $result = $manager->reportCaseHtmlDiff(@file($testObjectFile),
$coverageData, $execCodeLines, $manager->numDiffContextLines, $tmp1,
$tmp2);
break;
case 'CLIReporter':
$result = $manager->reportCaseCli(@file($testObjectFile),
$coverageData, $execCodeLines, $manager->numDiffContextLines);
@@ -160,8 +222,27 @@
break;
}
} else {
+ $coverageData = array();
+ $testObjectFiles = array();
+ if ($manager->testCaseFile == 'all') {
+ $dump = $manager->groupCoverageData;
+
+ $app_path = ROOT . DS. APP_DIR;
+ $test_path = $app_path . DS . 'tests';
+ $webroot_path = $app_path . DS . 'webroot';
+
+ foreach ($dump as $file => $data) {
+ if (preg_match('#' . $app_path . '#', $file) &&
+ !preg_match('#' . $test_path . '#', $file) &&
+ !preg_match('#' . $webroot_path . '#', $file)) {
+ $coverageData[$file] = $data;
+ $testObjectFiles[] = $file;
+ }
+ }
+ /* Sort for a better presentation */
+ sort($testObjectFiles, SORT_REGULAR);
+ } 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);
@@ -170,34 +251,34 @@
}
$dump = xdebug_get_code_coverage();
xdebug_stop_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 = '';

- 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;
- }
+ 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) {
- echo $result;
+ echo $result;
}
}
/**
@@ -244,7 +325,8 @@
* @param string $output
* @return void
*/
- function reportCaseHtmlDiff($testObjectFile, $coverageData,
$execCodeLines, $numContextLines) {
+ function reportCaseHtmlDiff($testObjectFile, $coverageData,
$execCodeLines, $numContextLines,
+ &$lineCountOut, &$coveredCountOut) {
$manager = CodeCoverageManager::getInstance();
$total = count($testObjectFile);
$lines = array();
@@ -292,7 +374,7 @@
}
}

- if ($j == $numContextLines) {
+ if ($j == $numContextLines && isset($lines[$key-1])) {
$lineBeforeIsEndBlock = strpos($lines[$key-1], 'end') !==
false;
$lineBeforeIsShown = strpos($lines[$key-1], 'show') !== false;
$lineBeforeIsUncovered = strpos($lines[$key-1], 'uncovered') !
== false;
@@ -348,6 +430,11 @@
$report .= $manager->__paintCodeline($class, $num, $line);
}
}
+
+ // Return coverage info
+ $lineCountOut = $lineCount;
+ $coveredCountOut = $coveredCount;
+
return $manager->__paintHeader($lineCount, $coveredCount, $report);
}
/**
@@ -410,11 +497,24 @@
$class = 'covered';
$coveredCount++;
}
+
} else {
$class = 'ignored';
}
}
- $report .= $manager->__paintGroupResultLine($objFilename,
$lineCount, $coveredCount);
+ $file_id = preg_replace('#[/.]#', '_', $objFilename);
+
+
+ $file_result = $manager->reportCaseHtmlDiff(@file($objFilename),
$coverageData[$objFilename],
+ $execCodeLines[$objFilename], $manager-
>numDiffContextLines,
+ $lineCount, $coveredCount);
+
+ $file_summary = $manager->__paintGroupResultLine($objFilename,
$lineCount, $coveredCount);
+
+ $report .= "<span class='link' onclick='var elem =
document.getElementById(\"$file_id\");
elem.style.display=(elem.style.display == \"inline\")?\"none\":\"inline
\"'>$file_summary</span>";
+
+ $report .= "<span style='display:none' id='$file_id' onclick='var
elem = document.getElementById(\"$file_id\"); elem.style.display=\"none
\"'>$file_result</span>";
+
}
return $manager->__paintGroupResultHeader($report);
}
@@ -482,14 +582,12 @@
$folder =& new Folder();
$folder->cd(ROOT . DS . CAKE_TESTS_LIB);
$contents = $folder->ls();
-
if (in_array(basename($testFile), $contents[1])) {
$testFile = basename($testFile);
$path = ROOT . DS . CAKE_TESTS_LIB;
}
$path .= $testFile;
$realpath = realpath($path);
-
if ($realpath) {
return $realpath;
}
@@ -627,7 +725,7 @@
if ($codeCoverage > 80) {
$class = 'result-good';
}
- return '<p>Code Coverage for ' . $file . ': <span class="' .
$class . '">' . $codeCoverage . '%</span></p>';
+ return '<p class="' . $class . '">Code Coverage for ' . $file . ':
<span>' . $codeCoverage . '%</span></p>';
}
/**
* Paints the headline for code coverage analysis
@@ -691,7 +789,7 @@
}
return ($lineCount != 0)
? round(100 * $coveredCount / $lineCount, 2)
- : '0.00';
+ : '100.00';
}
/**
* Gets us the base path to look for the test files

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "CakePHP" group.
To post to this group, send email to cake-php@googlegroups.com
To unsubscribe from this group, send email to cake-php+unsubscribe@googlegroups.com
For more options, visit this group at http://groups.google.com/group/cake-php?hl=en
-~----------~----~----~----~------~----~------~--~---

No comments: