<?php /* * $Id$ * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see * <http://www.phpdoctrine.com>. */ /** * Doctrine_UnitTestCase * * @package Doctrine * @author Konsta Vesterinen <kvesteri@cc.hut.fi> * @author Bjarte S. Karlsen <bjartka@pvv.ntnu.no> * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @category Object Relational Mapping * @link www.phpdoctrine.com * @since 1.0 * @version $Revision$ */ class DoctrineTest_Coverage { const COVERED = 1; const MAYBE = -2; const NOTCOVERED = -1; private $covered; private $totallines = 0; private $totalcovered = 0; private $totalmaybe = 0; private $totalnotcovered = 0; private $result; /* * Create a new Coverage object. We read data from a fixed file. */ public function __construct() { $this->result = unserialize(file_get_contents($this->getCoverageDir() . "coverage.txt")); $this->sortBy ="percentage"; // default sort } /** * Get the directory to store coverage report in * * @return string The path to store the coverage in */ public function getCoverageDir(){ $dir = Doctrine::getPath() . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "tests" . DIRECTORY_SEPARATOR . "coverage" . DIRECTORY_SEPARATOR; return $dir; } /* * Show a summary of all files in Doctrine and their coverage data * */ public function showSummary() { if ( isset($_GET['order'])){ $this->sortBy = $_GET['order']; } if ( ! isset($this->result['data'])){ die("Impropper coverage report. Please regenerate"); } $coveredArray = $this->result["data"]; //lets sort it. uasort($coveredArray, array($this,"sortArray")); //and flip if it perhaps? if (isset($_GET["flip"]) && $_GET["flip"] == "true"){ $coveredArray = array_reverse($coveredArray, true); } $totals = $this->result["totals"]; echo '<tr><td>TOTAL</td><td>' , $totals['percentage'] , '%</td><td>' , $totals['lines'] , '</td><td>' , $totals['covered'] , '</td><td>', $totals['maybe'] , '</td><td>', $totals['notcovered'] , '</td><td></tr>'; foreach($coveredArray as $class => $info){ echo '<tr><td>'; if ( $info['type'] == "covered") { echo '<a href="' , $class , '.html">', $class , '</a>'; }else{ echo $class; } echo '<td>' . $info['percentage'] . ' % </td><td>' . $info['total'] . '</td><td>' . $info['covered'] . '</td><td>' . $info['maybe'] . '</td><td>' . $info['notcovered']. '</td></tr>'; } } /** * Return the revision the coverage was made against * *@param int The revision number */ public function getRevision(){ return $this->result["revision"]; } /** * Generate the report. * * This method will analyze the coverage data and create a data array that * contains information about each of the classes in Doctrine/lib. It will * also generate html files for each file that has coverage data with * information about what lines that are covered. * * * @uses generateCoverageInfoCoveredFile * @uses saveFile * @uses generateCoverageInfoNotCoveredFile * @uses getCoverageDir * @uses calculateTotalPercentage * */ public function generateReport(){ $svn_info = explode(" ", exec("svn info | grep Revision")); $this->result["revision"] = $svn_info[1]; //loop through all files and generate coverage files for them $it = new RecursiveDirectoryIterator(Doctrine::getPath()); $notCoveredArray = array(); foreach (new RecursiveIteratorIterator($it) as $file){ if(strpos($file->getPathname(), "config.php")){ continue; } if (strpos($file->getPathname(), ".svn")){ continue; } $class = $this->getClassNameFromFileName($file->getPathname()); if (strpos($class, '_Interface')) { continue; } if ( ! class_exists($class)){ continue; } if (isset($this->result['coverage'][$file->getPathname()])){ $coverageInfo[$class] = $this->generateCoverageInfoCoveredFile($file->getPathname()); $this->saveFile($file->getPathname()); }else{ $coverageInfo[$class] = $this->generateCoverageInfoNotCoveredFile($class); } } $this->result["totals"] = array( "lines" => $this->totallines, "notcovered" => $this->totalnotcovered, "covered" => $this->totalcovered, "maybe" => $this->totalmaybe, "percentage" => $this->calculateTotalPercentage()); $this->result["data"] = $coverageInfo; file_put_contents($this->getCoverageDir() . "coverage.txt", serialize($this->result)); return true; } /** * * Return the name of a class from its filename. * * This method simply removes the Doctrine Path and raplces _ with / and * removes .php to get the classname for a file * * @param string $fileName The name of the file * @return string The name of the class */ public function getClassNameFromFileName($fileName){ $path = Doctrine::getPath() . DIRECTORY_SEPARATOR; $class = str_replace($path, "", $fileName); $class = str_replace(DIRECTORY_SEPARATOR, "_", $class); $class = substr($class, 0,-4); return $class; } /** * Calculate total coverage percentage * *@return double The percetage as a double */ public function calculateTotalPercentage(){ return round((($this->totalcovered + $this->totalmaybe) / $this->totallines) * 100, 2); } /** * Generate Coverage for a class that is not in the coverage report. * * This method will simply check if the method has no lines that should be * tested or not. Then it will return data to be stored for later use. * * @param string $class The name of a class * @return array An associative array with coverage information */ public function generateCoverageInfoNotCoveredFile($class){ try{ $refClass = new ReflectionClass($class); } catch (Exception $e){ echo $e->getMessage(); } $lines = 0; $methodLines = 0; foreach ($refClass->getMethods() as $refMethod){ if ($refMethod->getDeclaringClass() != $refClass){ continue; } $methodLines = $refMethod->getEndLine() - $refMethod->getStartLine(); $lines += $methodLines; } $this->totallines += $lines; $this->totalnotcovered += $lines; if ($lines == 0){ return array("covered" => 0, "maybe" => 0, "notcovered"=>$lines, "total" => $lines, "percentage" => 100, "type" => "notcovered"); } else { return array("covered" => 0, "maybe" => 0, "notcovered"=>$lines, "total" => $lines, "percentage" => 0, "type" => "notcovered"); } } /* * Save a html report for the given filename * * @param string $fileName The name of the file */ public function saveFile($fileName) { $className = $this->getClassNameFromFileName($fileName); $title = "Coverage for " . $className; $html = '<html> <head> <title>' . $title . '</title> <style type="text/css"> .covered{ background: green;} .normal{ background: white;} .red{ background: red;} .orange{ background: #f90;} </style> </head> <body><h1>' . $title . '</h1><p><a href="index.php">Back to coverage report</a></p>'; $coveredLines = $this->result["coverage"][$fileName]; $fileArray = file($fileName); $html .= '<table>' . "\n"; foreach ($fileArray as $num => $line){ $linenum = $num+1; $html .= '<tr><td>' . $linenum . '</td>' . "\n"; $class ="normal"; if (isset($coveredLines[$linenum]) && $coveredLines[$linenum] == self::COVERED){ $class = "covered"; } else if (isset($coveredLines[$linenum]) && $coveredLines[$linenum] == self::NOTCOVERED) { $class ="red"; } else if (isset($coveredLines[$linenum]) && $coveredLines[$linenum] == self::MAYBE) { $class ="orange"; } $line = str_replace(" ", " ", htmlspecialchars($line)); $html .= '<td class="' . $class . '">' . $line . '</td></tr>' . "\n"; } $html .='</table></body></html>'; file_put_contents($this->getCoverageDir() . $className . ".html",$html); } /* * Generate coverage data for tested file * *@return array An array of coverage data */ public function generateCoverageInfoCoveredFile($file) { $lines = $this->result["coverage"][$file]; $total = count($lines) -1; //we have to remove one since it always reports the last line as a hit $covered = 0; $maybe = 0; $notcovered = 0; foreach ($lines as $result){ switch($result){ case self::COVERED: $covered++; break; case self::NOTCOVERED: $notcovered++; break; case self::MAYBE: $maybe++; break; } } $covered--; //again we have to remove that last line. $this->totallines += $total; $this->totalcovered += $covered; $this->totalnotcovered += $notcovered; $this->totalmaybe += $maybe; if ($total === 0) { $total = 1; } $percentage = round((($covered + $maybe) / $total) * 100, 2); return array("covered" => $covered, "maybe" => $maybe, "notcovered"=>$notcovered, "total" => $total, "percentage" => $percentage, "type" => "covered"); } /* * Uasort function to sort the array by key * */ public function sortArray($a, $b) { if ($a[$this->sortBy] == $b[$this->sortBy]) { return 0; } return ( $a[$this->sortBy] < $b[$this->sortBy]) ? 1 : -1; } }