| [ Index ] |
PHP Cross Reference of Limb3 |
[Summary view] [Print] [Text view]
1 <?php 2 /* 3 * $Id: HtmlCoverageReporter.php 14665 2005-03-23 19:37:50Z npac $ 4 * 5 * Copyright(c) 2004-2005, SpikeSource Inc. All Rights Reserved. 6 * Licensed under the Open Source License version 2.1 7 * (See http://www.spikesource.com/license.html) 8 */ 9 ?> 10 <?php 11 12 if(!defined("__PHPCOVERAGE_HOME")) { 13 define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__))); 14 } 15 require_once __PHPCOVERAGE_HOME . "/reporter/CoverageReporter.php"; 16 require_once __PHPCOVERAGE_HOME . "/parser/PHPParser.php"; 17 require_once __PHPCOVERAGE_HOME . "/util/Utility.php"; 18 19 /** 20 * Class that implements HTML Coverage Reporter. 21 * 22 * @author Nimish Pachapurkar <npac@spikesource.com> 23 * @version $Revision: 14665 $ 24 * @package tests_runner 25 */ 26 class HtmlCoverageReporter extends CoverageReporter { 27 28 /*{{{ Members */ 29 30 private $coverageData; 31 private $htmlFile; 32 private $body; 33 private $header = "html/header.html"; 34 private $footer = "html/footer.html"; 35 private $indexHeader = "html/indexheader.html"; 36 private $indexFooter = "html/indexfooter.html"; 37 38 /*}}}*/ 39 /*{{{ public function __construct() */ 40 41 /** 42 * Constructor method (PHP5 only) 43 * 44 * @param $heading Heading of the report (shown as title) 45 * @param $style Name of the stylesheet file 46 * @param $dir Directory where the report files should be dumped 47 * @access public 48 */ 49 public function __construct( 50 $heading="Coverage Report", 51 $style="", 52 $dir="report" 53 ) { 54 parent::__construct($heading, $style, $dir); 55 } 56 57 /*}}}*/ 58 /*{{{ public function generateReport() */ 59 60 /** 61 * Implementaion of generateReport abstract function. 62 * This is the only function that will be called 63 * by the instrumentor. 64 * 65 * @param &$data Reference to Coverage Data 66 * @access public 67 */ 68 public function generateReport(&$data) { 69 if(!file_exists($this->outputDir)) { 70 mkdir($this->outputDir); 71 } 72 $this->coverageData =& $data; 73 $this->grandTotalFiles = count($this->coverageData); 74 $ret = $this->writeIndexFile(); 75 if($ret === FALSE) { 76 $this->logger->error("Error occured!!!", __FILE__, __LINE__); 77 } 78 $this->logger->debug(print_r($data, true), __FILE__, __LINE__); 79 } 80 81 /*}}}*/ 82 /*{{{ private function writeIndexFileHeader() */ 83 84 /** 85 * Write the index file header to a string 86 * 87 * @return string String containing HTML code for the index file header 88 * @access private 89 */ 90 private function writeIndexFileHeader() { 91 $str = false; 92 $dir = realpath(dirname(__FILE__)); 93 if($dir !== false) { 94 $str = file_get_contents($dir . "/" . $this->indexHeader); 95 if($str == false) { 96 return $str; 97 } 98 $str = str_replace("%%heading%%", $this->heading, $str); 99 $str = str_replace("%%style%%", $this->style, $str); 100 } 101 return $str; 102 } 103 104 /*}}}*/ 105 /*{{{ private function writeIndexFileFooter() */ 106 107 /** 108 * Write the index file footer to a string 109 * 110 * @return string String containing HTML code for the index file footer. 111 * @access private 112 */ 113 private function writeIndexFileFooter() { 114 $str = false; 115 $dir = realpath(dirname(__FILE__)); 116 if($dir !== false) { 117 $str = file_get_contents($dir . "/" . $this->indexFooter); 118 if($str == false) { 119 return $str; 120 } 121 } 122 return $str; 123 } 124 125 /*}}}*/ 126 /*{{{ private function createJSDir() */ 127 128 /** 129 * Create a directory for storing Javascript for the report 130 * 131 * @access private 132 */ 133 private function createJSDir() { 134 $jsDir = $this->outputDir . "/js"; 135 if(file_exists($this->outputDir) && !file_exists($jsDir)) { 136 mkdir($jsDir); 137 } 138 $jsSortFile = realpath(dirname(__FILE__)) . "/js/sort_spikesource.js"; 139 copy($jsSortFile, $jsDir . "/" . "sort_spikesource.js"); 140 return true; 141 } 142 143 /*}}}*/ 144 /*{{{ private function createImagesDir() */ 145 146 /** 147 * Create a directory for storing images for the report 148 * 149 * @access private 150 */ 151 private function createImagesDir() { 152 $imagesDir = $this->outputDir . "/images"; 153 if(file_exists($this->outputDir) && !file_exists($imagesDir)) { 154 mkdir($imagesDir); 155 } 156 $imagesSpikeDir = $imagesDir . "/spikesource"; 157 if(!file_exists($imagesSpikeDir)) { 158 mkdir($imagesSpikeDir); 159 } 160 $imagesArrowUpFile = realpath(dirname(__FILE__)) . "/images/arrow_up.gif"; 161 $imagesArrowDownFile = realpath(dirname(__FILE__)) . "/images/arrow_down.gif"; 162 $imagesPHPCoverageLogoFile = realpath(dirname(__FILE__)) . "/images/spikesource/phpcoverage.gif"; 163 $imagesSpacerFile = realpath(dirname(__FILE__)) . "/images/spacer.gif"; 164 copy($imagesArrowUpFile, $imagesDir . "/" . "arrow_up.gif"); 165 copy($imagesArrowDownFile, $imagesDir . "/" . "arrow_down.gif"); 166 copy($imagesSpacerFile, $imagesDir . "/" . "spacer.gif"); 167 copy($imagesPHPCoverageLogoFile, $imagesSpikeDir . "/" . "phpcoverage.gif"); 168 return true; 169 } 170 171 /*}}}*/ 172 /*{{{ private function createStyleDir() */ 173 174 private function createStyleDir() { 175 if(empty($this->style)) { 176 $this->style = "spikesource.css"; 177 } 178 $styleDir = $this->outputDir . "/css"; 179 if(file_exists($this->outputDir) && !file_exists($styleDir)) { 180 mkdir($styleDir); 181 } 182 $styleFile = realpath(dirname(__FILE__)) . "/css/" . $this->style; 183 copy($styleFile, $styleDir . "/" . $this->style); 184 return true; 185 } 186 187 /*}}}*/ 188 /*{{{ protected function writeIndexFileTableHead() */ 189 190 /** 191 * Writes the table heading for index.html 192 * 193 * @return string Table heading row code 194 * @access protected 195 */ 196 protected function writeIndexFileTableHead() { 197 $str = ""; 198 $str .= '<h1>Details</h1> <table class="spikeDataTable" cellpadding="4" cellspacing="0" border="0" id="table2sort" width="800">'; 199 $str .= '<thead>'; 200 $str .= '<tr><td class="spikeDataTableHeadLeft" id="sortCell0" rowspan="2" style="white-space:nowrap" width="52%"><a id="sortCellLink0" class="headerlink" href="javascript:sort(0)" title="Sort Ascending">File Name </a></td>'; 201 $str .= '<td colspan="4" class="spikeDataTableHeadCenter">Lines</td>'; 202 $str .= '<td class="spikeDataTableHeadCenterLast" id="sortCell5" rowspan="2" width="16%" style="white-space:nowrap"><a id="sortCellLink5" class="headerlink" href="javascript:sort(5, \'percentage\')" title="Sort Ascending">Code Coverage </a></td>'; 203 $str .= '</tr>'; 204 205 // Second row - subheadings 206 $str .= '<tr>'; 207 $str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell1" style="white-space:nowrap" width="8%"><a id="sortCellLink1" title="Sort Ascending" class="headerlink" href="javascript:sort(1, \'number\')">Total </a></td>'; 208 $str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell2" style="white-space:nowrap" width="9%"><a id="sortCellLink2" title="Sort Ascending" class="headerlink" href="javascript:sort(2, \'number\')">Covered </a></td>'; 209 $str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell3" style="white-space:nowrap" width="8%"><a id="sortCellLink3" title="Sort Ascending" class="headerlink" href="javascript:sort(3, \'number\')">Missed </a></td>'; 210 $str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell4" style="white-space:nowrap" width="10%"><a id="sortCellLink4" title="Sort Ascending" class="headerlink" href="javascript:sort(4, \'number\')">Executable </a></td>'; 211 $str .= '</tr>'; 212 $str .= '</thead>'; 213 return $str; 214 } 215 216 /*}}}*/ 217 /*{{{ protected function writeIndexFileTableRow() */ 218 219 /** 220 * Writes one row in the index.html table to display filename 221 * and coverage recording. 222 * 223 * @param $fileLink link to html details file. 224 * @param $realFile path to real PHP file. 225 * @param $fileCoverage Coverage recording for that file. 226 * @return string HTML code for a single row. 227 * @access protected 228 */ 229 protected function writeIndexFileTableRow($fileLink, $realFile, $fileCoverage) { 230 231 global $util; 232 $fileLink = $this->makeRelative($fileLink); 233 $realFileShort = $util->shortenFilename($realFile); 234 $str = ""; 235 236 $str .= '<tr><td class="spikeDataTableCellLeft">'; 237 $str .= '<a class="contentlink" href="' . $util->unixifyPath($fileLink) . '" title="' 238 . $realFile .'">' . $realFileShort. '</a>' . '</td>'; 239 $str .= '<td class="spikeDataTableCellCenter">' . $fileCoverage['total'] . "</td>"; 240 $str .= '<td class="spikeDataTableCellCenter">' . $fileCoverage['covered'] . "</td>"; 241 $str .= '<td class="spikeDataTableCellCenter">' . $fileCoverage['uncovered'] . "</td>"; 242 $str .= '<td class="spikeDataTableCellCenter">' . ($fileCoverage['covered']+$fileCoverage['uncovered']) . "</td>"; 243 if($fileCoverage['uncovered'] + $fileCoverage['covered'] == 0) { 244 $str .= '<td class="spikeDataTableCellCenter">0%</td></tr>'; 245 } 246 else { 247 $str .= '<td class="spikeDataTableCellCenter">' 248 . round(($fileCoverage['covered']/($fileCoverage['uncovered'] 249 + $fileCoverage['covered']))*100.0, 2) 250 . '%</td></tr>'; 251 } 252 return $str; 253 } 254 255 /*}}}*/ 256 /*{{{ protected function writeIndexFileGrandTotalPercentage() */ 257 258 /** 259 * Writes the grand total for coverage recordings on the index.html 260 * 261 * @return string HTML code for grand total row 262 * @access protected 263 */ 264 protected function writeIndexFileGrandTotalPercentage() { 265 $str = ""; 266 267 $str .= "<br/><h1>" . $this->heading . "</h1><br/>"; 268 269 $str .= '<table border="0" cellpadding="0" cellspacing="0" id="contentBox" width="800"> <tr>'; 270 $str .= '<td align="left" valign="top"><h1>Summary</h1>'; 271 $str .= '<table class="spikeVerticalTable" cellpadding="4" cellspacing="0" width="800" style="margin-bottom:10px" border="0">'; 272 $str .= '<td width="380" class="spikeVerticalTableHead" style="font-size:14px">Overall Code Coverage </td>'; 273 $str .= '<td class="spikeVerticalTableCell" style="font-size:14px" colspan="2"><strong>' . $this->getGrandCodeCoveragePercentage() . '%</td>'; 274 275 $str .= '</tr><tr>'; 276 277 $str .= '<td class="spikeVerticalTableHead">Total Covered Lines of Code </td>'; 278 $str .= '<td width="30" class="spikeVerticalTableCell"><span class="emphasis">' . $this->grandTotalCoveredLines.'</span></td>'; 279 $str .= '<td class="spikeVerticalTableCell"><span class="note">(' . TOTAL_COVERED_LINES_EXPLAIN . ')</span></td>'; 280 281 $str .= '</tr><tr>'; 282 283 $str .= '<td class="spikeVerticalTableHead">Total Missed Lines of Code </td>'; 284 $str .= '<td class="spikeVerticalTableCell"><span class="emphasis">' . $this->grandTotalUncoveredLines.'</span></td>'; 285 $str .= '<td class="spikeVerticalTableCell"><span class="note">(' . TOTAL_UNCOVERED_LINES_EXPLAIN . ')</span></td>'; 286 287 $str .= '</tr><tr>'; 288 289 $str .= '<td class="spikeVerticalTableHead">Total Lines of Code </td>'; 290 $str .= '<td class="spikeVerticalTableCell"><span class="emphasis">' . ($this->grandTotalCoveredLines + $this->grandTotalUncoveredLines) .'</span></td>'; 291 $str .= '<td class="spikeVerticalTableCell"><span class="note">(' . 292 TOTAL_LINES_OF_CODE_EXPLAIN . ')</span></td>'; 293 294 $str .= '</tr><tr>'; 295 296 $str .= '<td class="spikeVerticalTableHead" >Total Lines </td>'; 297 $str .= '<td class="spikeVerticalTableCell"><span class="emphasis">' . $this->grandTotalLines.'</span></td>'; 298 $str .= '<td class="spikeVerticalTableCell"><span class="note">(' . TOTAL_LINES_EXPLAIN . ')</span></td>'; 299 300 $str .= '</tr><tr>'; 301 302 $str .= '<td class="spikeVerticalTableHeadLast" >Total Files </td>'; 303 $str .= '<td class="spikeVerticalTableCellLast"><span class="emphasis">' . $this->grandTotalFiles.'</span></td>'; 304 $str .= '<td class="spikeVerticalTableCellLast"><span class="note">(' . TOTAL_FILES_EXPLAIN . ')</span></td>'; 305 306 $str .= '</tr></table>'; 307 308 return $str; 309 } 310 311 /*}}}*/ 312 /*{{{ protected function writeIndexFile() */ 313 314 /** 315 * Writes index.html file from all coverage recordings. 316 * 317 * @return boolean FALSE on failure 318 * @access protected 319 */ 320 protected function writeIndexFile() { 321 global $util; 322 $str = ""; 323 $this->createJSDir(); 324 $this->createImagesDir(); 325 $this->createStyleDir(); 326 $this->htmlFile = $this->outputDir . "/index.html"; 327 $indexFile = fopen($this->htmlFile, "w"); 328 if(empty($indexFile)) { 329 $this->logger->error("Cannot open file for writing: $this->htmlFile", 330 __FILE__, __LINE__); 331 return false; 332 } 333 334 $strHead = $this->writeIndexFileHeader(); 335 if($strHead == false) { 336 return false; 337 } 338 $str .= $this->writeIndexFileTableHead(); 339 $str .= '<tbody>'; 340 if(!empty($this->coverageData)) { 341 foreach($this->coverageData as $filename => &$lines) { 342 $realFile = realpath($filename); 343 $fileLink = $this->outputDir . $util->unixifyPath($realFile). ".html"; 344 $fileCoverage = $this->markFile($realFile, $fileLink, $lines); 345 if(empty($fileCoverage)) { 346 return false; 347 } 348 $this->recordFileCoverageInfo($fileCoverage); 349 $this->updateGrandTotals($fileCoverage); 350 351 $str .= $this->writeIndexFileTableRow($fileLink, $realFile, $fileCoverage); 352 unset($this->coverageData[$filename]); 353 } 354 } 355 $str .= '</tbody>'; 356 $str .= "</table></td></tr>"; 357 358 $str .= "<tr><td><p align=\"right\" class=\"content\">Report Generated On: " . $util->getTimeStamp() . "<br/>"; 359 $str .= "Generated using Spike PHPCoverage " . $this->recorder->getVersion() . "</p></td></tr></table>"; 360 361 // Get the summary 362 $strSummary = $this->writeIndexFileGrandTotalPercentage(); 363 364 // Merge them - with summary on top 365 $str = $strHead . $strSummary . $str; 366 367 $str .= $this->writeIndexFileFooter(); 368 fwrite($indexFile, $str); 369 fclose($indexFile); 370 return TRUE; 371 } 372 373 /*}}}*/ 374 /*{{{ private function writePhpFileHeader() */ 375 376 /** 377 * Write the header for the source file with mark-up 378 * 379 * @param $filename Name of the php file 380 * @return string String containing the HTML for PHP file header 381 * @access private 382 */ 383 private function writePhpFileHeader($filename, $fileLink) { 384 $fileLink = $this->makeRelative($fileLink); 385 $str = false; 386 $dir = realpath(dirname(__FILE__)); 387 if($dir !== false) { 388 $str = file_get_contents($dir . "/" . $this->header); 389 if($str == false) { 390 return $str; 391 } 392 $str = str_replace("%%filename%%", $filename, $str); 393 // Get the path to parent CSS directory 394 $relativeCssPath = $this->getRelativeOutputDirPath($fileLink); 395 $relativeCssPath .= "/css/" . $this->style; 396 $str = str_replace("%%style%%", $relativeCssPath, $str); 397 } 398 return $str; 399 } 400 401 /*}}}*/ 402 /*{{{ private function writePhpFileFooter() */ 403 404 /** 405 * Write the footer for the source file with mark-up 406 * 407 * @return string String containing the HTML for PHP file footer 408 * @access private 409 */ 410 private function writePhpFileFooter() { 411 $str = false; 412 $dir = realpath(dirname(__FILE__)); 413 if($dir !== false) { 414 $str = file_get_contents($dir . "/" . $this->footer); 415 if($str == false) { 416 return $str; 417 } 418 } 419 return $str; 420 } 421 422 /*}}}*/ 423 /*{{{ protected function markFile() */ 424 425 /** 426 * Mark a source code file based on the coverage data gathered 427 * 428 * @param $phpFile Name of the actual source file 429 * @param $fileLink Link to the html mark-up file for the $phpFile 430 * @param &$coverageLines Coverage recording for $phpFile 431 * @return boolean FALSE on failure 432 * @access protected 433 */ 434 protected function markFile($phpFile, $fileLink, &$coverageLines) { 435 global $util; 436 $fileLink = $util->replaceBackslashes($fileLink); 437 $parentDir = $util->replaceBackslashes(dirname($fileLink)); 438 if(!file_exists($parentDir)) { 439 //echo "\nCreating dir: $parentDir\n"; 440 $util->makeDirRecursive($parentDir, 0755); 441 } 442 $writer = fopen($fileLink, "w"); 443 444 if(empty($writer)) { 445 $this->logger->error("Could not open file for writing: $fileLink", 446 __FILE__, __LINE__); 447 return false; 448 } 449 450 // Get the header for file 451 $filestr = $this->writePhpFileHeader(basename($phpFile), $fileLink); 452 453 // Add header for table 454 $filestr .= '<table width="100%" border="0" cellpadding="2" cellspacing="0">'; 455 $filestr .= $this->writeFileTableHead(); 456 457 $lineCnt = $coveredCnt = $uncoveredCnt = 0; 458 $parser = new PHPParser(); 459 $parser->parse($phpFile); 460 $lastLineType = "non-exec"; 461