| [ Index ] |
PHP Cross Reference of Limb3 |
[Summary view] [Print] [Text view]
1 <?php 2 /* 3 * $Id: CoverageRecorder.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(__FILE__)); 14 } 15 require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php"; 16 require_once __PHPCOVERAGE_HOME . "/util/Utility.php"; 17 require_once __PHPCOVERAGE_HOME . "/reporter/CoverageReporter.php"; 18 19 /** 20 * 21 * The Coverage Recorder utility 22 * 23 * This is the main class for the CoverageRecorder. User should 24 * instantiate this class and set various parameters of it. 25 * The startInstrumentation and stopInstrumentation methods will 26 * switch code coverage recording on and off respectively. 27 * 28 * The code coverage is recorded using XDebug Zend Extension. Therefore, 29 * it is required to install that extension on the system where 30 * code coverage measurement is going to take place. See 31 * {@link http://www.xdebug.org www.xdebug.org} for more 32 * information. 33 * 34 * @author Nimish Pachapurkar <npac@spikesource.com> 35 * @version $Revision: 14665 $ 36 */ 37 class CoverageRecorder { 38 39 // {{{ Members 40 41 protected $includePaths; 42 protected $excludePaths; 43 protected $reporter; 44 protected $coverageData; 45 protected $isRemote = false; 46 protected $stripped = false; 47 protected $phpCoverageFiles = array("phpcoverage.inc.php"); 48 protected $version; 49 protected $logger; 50 51 /** 52 * What extensions are treated as php files. 53 * 54 * @param "php" Array of extension strings 55 */ 56 protected $phpExtensions; 57 58 // }}} 59 // {{{ Constructor 60 61 /** 62 * Constructor (PHP5 only) 63 * 64 * @param $includePaths Directories to be included in code coverage report 65 * @param $excludePaths Directories to be excluded from code coverage report 66 * @param $reporter Instance of a Reporter subclass 67 * @access public 68 */ 69 public function __construct( 70 $includePaths=array("."), 71 $excludePaths=array(), 72 $reporter="new HtmlCoverageReporter()" 73 ) { 74 75 $this->includePaths = $includePaths; 76 $this->excludePaths = $excludePaths; 77 $this->reporter = $reporter; 78 // Set back reference 79 $this->reporter->setCoverageRecorder($this); 80 $this->excludeCoverageDir(); 81 $this->version = "0.6.6"; 82 83 // Configuration 84 global $spc_config; 85 $this->phpExtensions = $spc_config['extensions']; 86 global $util; 87 $this->logger = $util->getLogger(); 88 } 89 90 // }}} 91 // {{{ public function startInstrumentation() 92 93 /** 94 * Starts the code coverage recording 95 * 96 * @access public 97 */ 98 public function startInstrumentation() { 99 if(extension_loaded("xdebug")) { 100 xdebug_start_code_coverage(); 101 return true; 102 } 103 $this->logger->critical("[CoverageRecorder::startInstrumentation()] " 104 . "ERROR: Xdebug not loaded.", __FILE__, __LINE__); 105 return false; 106 } 107 108 // }}} 109 // {{{ public function stopInstrumentation() 110 111 /** 112 * Stops code coverage recording 113 * 114 * @access public 115 */ 116 public function stopInstrumentation() { 117 if(extension_loaded("xdebug")) { 118 $this->coverageData = xdebug_get_code_coverage(); 119 foreach($this->coverageData as $file => $data) { 120 if (!file_exists($file)) { 121 unset($this->coverageData[$file]); 122 } 123 } 124 125 xdebug_stop_code_coverage(); 126 $this->logger->debug("[CoverageRecorder::stopInstrumentation()] Code coverage: " . print_r($this->coverageData, true), 127 __FILE__, __LINE__); 128 return true; 129 } 130 return false; 131 } 132 133 // }}} 134 // {{{ public function generateReport() 135 136 /** 137 * Generate the code coverage report 138 * 139 * @access public 140 */ 141 public function generateReport() { 142 if($this->isRemote) { 143 $this->logger->info("[CoverageRecorder::generateReport()] " 144 ."Writing report.", __FILE__, __LINE__); 145 } 146 else { 147 $this->logger->info("[CoverageRecorder::generateReport()] " 148 . "Writing report:\t\t", __FILE__, __LINE__); 149 } 150 $this->logger->debug("[CoverageRecoder::generateReport()] " . print_r($this->coverageData, true), 151 __FILE__, __LINE__); 152 $this->unixifyCoverageData(); 153 $this->coverageData = $this->stripCoverageData(); 154 $this->reporter->generateReport($this->coverageData); 155 if($this->isRemote) { 156 $this->logger->info("[CoverageRecorder::generateReport()] [done]", __FILE__, __LINE__); 157 } 158 else { 159 $this->logger->info("[done]", __FILE__, __LINE__); 160 } 161 } 162 163 // }}} 164 /*{{{ protected function removeAbsentPaths() */ 165 166 /** 167 * Remove the directories that do not exist from the input array 168 * 169 * @param &$dirs Array of directory names 170 * @access protected 171 */ 172 protected function removeAbsentPaths(&$dirs) { 173 for($i = 0; $i < count($dirs); $i++) { 174 if(! file_exists($dirs[$i])) { 175 // echo "Not found: " . $dirs[$i] . "\n"; 176 $this->errors[] = "Not found: " . $dirs[$i] 177 . ". Removing ..."; 178 array_splice($dirs, $i, 1); 179 $i--; 180 } 181 else { 182 $dirs[$i] = realpath($dirs[$i]); 183 } 184 } 185 } 186 187 /*}}}*/ 188 // {{{ protected function processSourcePaths() 189 190 /** 191 * Processes and validates the source directories 192 * 193 * @access protected 194 */ 195 protected function processSourcePaths() { 196 $this->removeAbsentPaths($this->includePaths); 197 $this->removeAbsentPaths($this->excludePaths); 198 199 sort($this->includePaths, SORT_STRING); 200 } 201 202 // }}} 203 /*{{{ protected function getFilesAndDirs() */ 204 205 /** 206 * Get the list of files that match the extensions in $this->phpExtensions 207 * 208 * @param $dir Root directory 209 * @param &$files Array of filenames to append to 210 * @access protected 211 */ 212 protected function getFilesAndDirs($dir, &$files) { 213 global $util; 214 $dirs[] = $dir; 215 while(count($dirs) > 0) { 216 $currDir = realpath(array_pop($dirs)); 217 if(!is_readable($currDir)) { 218 continue; 219 } 220 //echo "Current Dir: $currDir \n"; 221 $currFiles = scandir($currDir); 222 //print_r($currFiles); 223 for($j = 0; $j < count($currFiles); $j++) { 224 if($currFiles[$j] == "." || $currFiles[$j] == "..") { 225 continue; 226 } 227 $currFiles[$j] = $currDir . "/" . $currFiles[$j]; 228 //echo "Current File: " . $currFiles[$j] . "\n"; 229 if(is_file($currFiles[$j])) { 230 $pathParts = pathinfo($currFiles[$j]); 231 if(isset($pathParts['extension']) && in_array($pathParts['extension'], $this->phpExtensions)) { 232 $files[] = $util->replaceBackslashes($currFiles[$j]); 233 } 234 } 235 if(is_dir($currFiles[$j])) { 236 $dirs[] = $currFiles[$j]; 237 } 238 } 239 } 240 } 241 242 /*}}}*/ 243 /*{{{ protected function addFiles() */ 244 245 /** 246 * Add all source files to the list of files that need to be parsed. 247 * 248 * @access protected 249 */ 250 protected function addFiles() { 251 global $util; 252 $files = array(); 253 for($i = 0; $i < count($this->includePaths); $i++) { 254 $this->includePaths[$i] = $util->replaceBackslashes($this->includePaths[$i]); 255 if(is_dir($this->includePaths[$i])) { 256 //echo "Calling getFilesAndDirs with " . $this->includePaths[$i] . "\n"; 257 $this->getFilesAndDirs($this->includePaths[$i], $files); 258 } 259 else if(is_file($this->includePaths[$i])) { 260 $files[] = $this->includePaths[$i]; 261 } 262 } 263 264 $this->logger->debug("Found files:" . print_r($files, true), 265 __FILE__, __LINE__); 266 for($i = 0; $i < count($this->excludePaths); $i++) { 267 $this->excludePaths[$i] = $util->replaceBackslashes($this->excludePaths[$i]); 268 } 269 270 for($i = 0; $i < count($files); $i++) { 271 for($j = 0; $j < count($this->excludePaths); $j++) { 272 $this->logger->debug($files[$i] . "\t" . $this->excludePaths[$j] . "\n", __FILE__, __LINE__); 273 if(strpos($files[$i], $this->excludePaths[$j]) === 0) { 274 continue; 275 } 276 } 277 if(!array_key_exists($files[$i], $this->coverageData)) { 278 $this->coverageData[$files[$i]] = array(); 279 } 280 } 281 } 282 283 /*}}}*/ 284 // {{{ protected function stripCoverageData() 285 286 /** 287 * Removes the unwanted coverage data from the recordings 288 * 289 * @return Processed coverage data 290 * @access protected 291 */ 292 protected function stripCoverageData() { 293 if($this->stripped) { 294 $this->logger->debug("[CoverageRecorder::stripCoverageData()] Already stripped!", __FILE__, __LINE__); 295 return $this->coverageData; 296 } 297 $this->stripped = true; 298 if(empty($this->coverageData)) { 299 $this->logger->warn("[CoverageRecorder::stripCoverageData()] No coverage data found.", __FILE__, __LINE__); 300 return $this->coverageData; 301 } 302 $this->processSourcePaths(); 303 $this->logger->debug("!!!!!!!!!!!!! Source Paths !!!!!!!!!!!!!!", 304 __FILE__, __LINE__); 305 $this->logger->debug(print_r($this->includePaths, true), 306 __FILE__, __LINE__); 307 $this->logger->debug(print_r($this->excludePaths, true), 308 __FILE__, __LINE__); 309 $this->logger->debug("!!!!!!!!!!!!! Source Paths !!!!!!!!!!!!!!", 310 __FILE__, __LINE__); 311 $this->addFiles(); 312 $altCoverageData = array(); 313 foreach ($this->coverageData as $filename => &$lines) { 314 $preserve = false; 315 $realFile = $filename; 316 for($i = 0; $i < count($this->includePaths); $i++) { 317 if(strpos($realFile, $this->includePaths[$i]) === 0) { 318 $preserve = true; 319 } 320 else { 321 $this->logger->debug("File: " . $realFile 322 . "\nDoes not match: " . $this->includePaths[$i], 323 __FILE__, __LINE__); 324 } 325 } 326 // Exclude dirs have a precedence over includes. 327 for($i = 0; $i < count($this->excludePaths); $i++) { 328 if(strpos($realFile, $this->excludePaths[$i]) === 0) { 329 $preserve = false; 330 } 331 else if(in_array(basename($realFile), $this->phpCoverageFiles)) { 332 $preserve = false; 333 } 334 } 335 if($preserve) { 336 // Should be preserved 337 $altCoverageData[$filename] = $lines; 338 } 339 } 340 341 array_multisort($altCoverageData, SORT_STRING); 342 return $altCoverageData; 343 } 344 345 // }}} 346 /*{{{ protected function unixifyCoverageData() */ 347 348 /** 349 * Convert filepaths in coverage data to forward slash separated 350 * paths. 351 * 352 * @access protected 353 */ 354 protected function unixifyCoverageData() { 355 global $util; 356 $tmpCoverageData = array(); 357 foreach($this->coverageData as $file => &$lines) { 358 $tmpCoverageData[$util->replaceBackslashes(realpath($file))] = $lines; 359 } 360 $this->coverageData = $tmpCoverageData; 361 } 362 363 /*}}}*/ 364 // {{{ public function getErrors() 365 366 /** 367 * Returns the errors array containing all error encountered so far. 368 * 369 * @return Array of error messages 370 * @access public 371 */ 372 public function getErrors() { 373 return $this->errors; 374 } 375 376 // }}} 377 // {{{ public function logErrors() 378 379 /** 380 * Writes all error messages to error log 381 * 382 * @access public 383 */ 384 public function logErrors() { 385 $this->logger->error(print_r($this->errors, true), 386 __FILE__, __LINE__); 387 } 388 389 // }}} 390 /*{{{ Getters and Setters */ 391 392 public function getIncludePaths() { 393 return $this->includePaths; 394 } 395 396 public function setIncludePaths($includePaths) { 397 $this->includePaths = $includePaths; 398 } 399 400 public function getExcludePaths() { 401 return $this->excludePaths; 402 } 403 404 public function setExcludePaths($excludePaths) { 405 $this->excludePaths = $excludePaths; 406 $this->excludeCoverageDir(); 407 } 408 409 public function getReporter() { 410 return $this->reporter; 411 } 412 413 public function setReporter(&$reporter) { 414 $this->reporter = $reporter; 415 } 416 417 public function getPhpExtensions() { 418 return $this->phpExtensions; 419 } 420 421 public function setPhpExtensions(&$extensions) { 422 $this->phpExtensions = $extensions; 423 } 424 425 public function getVersion() { 426 return $this->version; 427 } 428 429 /*}}}*/ 430 /*{{{ public function excludeCoverageDir() */ 431 432 /** 433 * Exclude the directory containing the coverage measurement code. 434 * 435 * @access public 436 */ 437 public function excludeCoverageDir() { 438 $f = __FILE__; 439 if(is_link($f)) { 440 $f = readlink($f); 441 } 442 $this->excludePaths[] = realpath(dirname($f)); 443 } 444 /*}}}*/ 445 } 446 ?>