CodeCoverage.php (26995B)
1 <?php 2 /* 3 * This file is part of the PHP_CodeCoverage package. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11 use SebastianBergmann\Environment\Runtime; 12 13 /** 14 * Provides collection functionality for PHP code coverage information. 15 * 16 * @since Class available since Release 1.0.0 17 */ 18 class PHP_CodeCoverage 19 { 20 /** 21 * @var PHP_CodeCoverage_Driver 22 */ 23 private $driver; 24 25 /** 26 * @var PHP_CodeCoverage_Filter 27 */ 28 private $filter; 29 30 /** 31 * @var bool 32 */ 33 private $cacheTokens = false; 34 35 /** 36 * @var bool 37 */ 38 private $checkForUnintentionallyCoveredCode = false; 39 40 /** 41 * @var bool 42 */ 43 private $forceCoversAnnotation = false; 44 45 /** 46 * @var bool 47 */ 48 private $mapTestClassNameToCoveredClassName = false; 49 50 /** 51 * @var bool 52 */ 53 private $addUncoveredFilesFromWhitelist = true; 54 55 /** 56 * @var bool 57 */ 58 private $processUncoveredFilesFromWhitelist = false; 59 60 /** 61 * @var mixed 62 */ 63 private $currentId; 64 65 /** 66 * Code coverage data. 67 * 68 * @var array 69 */ 70 private $data = array(); 71 72 /** 73 * @var array 74 */ 75 private $ignoredLines = array(); 76 77 /** 78 * @var bool 79 */ 80 private $disableIgnoredLines = false; 81 82 /** 83 * Test data. 84 * 85 * @var array 86 */ 87 private $tests = array(); 88 89 /** 90 * Constructor. 91 * 92 * @param PHP_CodeCoverage_Driver $driver 93 * @param PHP_CodeCoverage_Filter $filter 94 * @throws PHP_CodeCoverage_Exception 95 */ 96 public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null) 97 { 98 if ($driver === null) { 99 $driver = $this->selectDriver(); 100 } 101 102 if ($filter === null) { 103 $filter = new PHP_CodeCoverage_Filter; 104 } 105 106 $this->driver = $driver; 107 $this->filter = $filter; 108 } 109 110 /** 111 * Returns the PHP_CodeCoverage_Report_Node_* object graph 112 * for this PHP_CodeCoverage object. 113 * 114 * @return PHP_CodeCoverage_Report_Node_Directory 115 * @since Method available since Release 1.1.0 116 */ 117 public function getReport() 118 { 119 $factory = new PHP_CodeCoverage_Report_Factory; 120 121 return $factory->create($this); 122 } 123 124 /** 125 * Clears collected code coverage data. 126 */ 127 public function clear() 128 { 129 $this->currentId = null; 130 $this->data = array(); 131 $this->tests = array(); 132 } 133 134 /** 135 * Returns the PHP_CodeCoverage_Filter used. 136 * 137 * @return PHP_CodeCoverage_Filter 138 */ 139 public function filter() 140 { 141 return $this->filter; 142 } 143 144 /** 145 * Returns the collected code coverage data. 146 * Set $raw = true to bypass all filters. 147 * 148 * @param bool $raw 149 * @return array 150 * @since Method available since Release 1.1.0 151 */ 152 public function getData($raw = false) 153 { 154 if (!$raw && $this->addUncoveredFilesFromWhitelist) { 155 $this->addUncoveredFilesFromWhitelist(); 156 } 157 158 // We need to apply the blacklist filter a second time 159 // when no whitelist is used. 160 if (!$raw && !$this->filter->hasWhitelist()) { 161 $this->applyListsFilter($this->data); 162 } 163 164 return $this->data; 165 } 166 167 /** 168 * Sets the coverage data. 169 * 170 * @param array $data 171 * @since Method available since Release 2.0.0 172 */ 173 public function setData(array $data) 174 { 175 $this->data = $data; 176 } 177 178 /** 179 * Returns the test data. 180 * 181 * @return array 182 * @since Method available since Release 1.1.0 183 */ 184 public function getTests() 185 { 186 return $this->tests; 187 } 188 189 /** 190 * Sets the test data. 191 * 192 * @param array $tests 193 * @since Method available since Release 2.0.0 194 */ 195 public function setTests(array $tests) 196 { 197 $this->tests = $tests; 198 } 199 200 /** 201 * Start collection of code coverage information. 202 * 203 * @param mixed $id 204 * @param bool $clear 205 * @throws PHP_CodeCoverage_Exception 206 */ 207 public function start($id, $clear = false) 208 { 209 if (!is_bool($clear)) { 210 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 211 1, 212 'boolean' 213 ); 214 } 215 216 if ($clear) { 217 $this->clear(); 218 } 219 220 $this->currentId = $id; 221 222 $this->driver->start(); 223 } 224 225 /** 226 * Stop collection of code coverage information. 227 * 228 * @param bool $append 229 * @param mixed $linesToBeCovered 230 * @param array $linesToBeUsed 231 * @return array 232 * @throws PHP_CodeCoverage_Exception 233 */ 234 public function stop($append = true, $linesToBeCovered = array(), array $linesToBeUsed = array()) 235 { 236 if (!is_bool($append)) { 237 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 238 1, 239 'boolean' 240 ); 241 } 242 243 if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) { 244 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 245 2, 246 'array or false' 247 ); 248 } 249 250 $data = $this->driver->stop(); 251 $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed); 252 253 $this->currentId = null; 254 255 return $data; 256 } 257 258 /** 259 * Appends code coverage data. 260 * 261 * @param array $data 262 * @param mixed $id 263 * @param bool $append 264 * @param mixed $linesToBeCovered 265 * @param array $linesToBeUsed 266 * @throws PHP_CodeCoverage_Exception 267 */ 268 public function append(array $data, $id = null, $append = true, $linesToBeCovered = array(), array $linesToBeUsed = array()) 269 { 270 if ($id === null) { 271 $id = $this->currentId; 272 } 273 274 if ($id === null) { 275 throw new PHP_CodeCoverage_Exception; 276 } 277 278 $this->applyListsFilter($data); 279 $this->applyIgnoredLinesFilter($data); 280 $this->initializeFilesThatAreSeenTheFirstTime($data); 281 282 if (!$append) { 283 return; 284 } 285 286 if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') { 287 $this->applyCoversAnnotationFilter( 288 $data, 289 $linesToBeCovered, 290 $linesToBeUsed 291 ); 292 } 293 294 if (empty($data)) { 295 return; 296 } 297 298 $size = 'unknown'; 299 $status = null; 300 301 if ($id instanceof PHPUnit_Framework_TestCase) { 302 $_size = $id->getSize(); 303 304 if ($_size == PHPUnit_Util_Test::SMALL) { 305 $size = 'small'; 306 } elseif ($_size == PHPUnit_Util_Test::MEDIUM) { 307 $size = 'medium'; 308 } elseif ($_size == PHPUnit_Util_Test::LARGE) { 309 $size = 'large'; 310 } 311 312 $status = $id->getStatus(); 313 $id = get_class($id) . '::' . $id->getName(); 314 } elseif ($id instanceof PHPUnit_Extensions_PhptTestCase) { 315 $size = 'large'; 316 $id = $id->getName(); 317 } 318 319 $this->tests[$id] = array('size' => $size, 'status' => $status); 320 321 foreach ($data as $file => $lines) { 322 if (!$this->filter->isFile($file)) { 323 continue; 324 } 325 326 foreach ($lines as $k => $v) { 327 if ($v == PHP_CodeCoverage_Driver::LINE_EXECUTED) { 328 if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) { 329 $this->data[$file][$k][] = $id; 330 } 331 } 332 } 333 } 334 } 335 336 /** 337 * Merges the data from another instance of PHP_CodeCoverage. 338 * 339 * @param PHP_CodeCoverage $that 340 */ 341 public function merge(PHP_CodeCoverage $that) 342 { 343 $this->filter->setBlacklistedFiles( 344 array_merge($this->filter->getBlacklistedFiles(), $that->filter()->getBlacklistedFiles()) 345 ); 346 347 $this->filter->setWhitelistedFiles( 348 array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles()) 349 ); 350 351 foreach ($that->data as $file => $lines) { 352 if (!isset($this->data[$file])) { 353 if (!$this->filter->isFiltered($file)) { 354 $this->data[$file] = $lines; 355 } 356 357 continue; 358 } 359 360 foreach ($lines as $line => $data) { 361 if ($data !== null) { 362 if (!isset($this->data[$file][$line])) { 363 $this->data[$file][$line] = $data; 364 } else { 365 $this->data[$file][$line] = array_unique( 366 array_merge($this->data[$file][$line], $data) 367 ); 368 } 369 } 370 } 371 } 372 373 $this->tests = array_merge($this->tests, $that->getTests()); 374 375 } 376 377 /** 378 * @param bool $flag 379 * @throws PHP_CodeCoverage_Exception 380 * @since Method available since Release 1.1.0 381 */ 382 public function setCacheTokens($flag) 383 { 384 if (!is_bool($flag)) { 385 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 386 1, 387 'boolean' 388 ); 389 } 390 391 $this->cacheTokens = $flag; 392 } 393 394 /** 395 * @since Method available since Release 1.1.0 396 */ 397 public function getCacheTokens() 398 { 399 return $this->cacheTokens; 400 } 401 402 /** 403 * @param bool $flag 404 * @throws PHP_CodeCoverage_Exception 405 * @since Method available since Release 2.0.0 406 */ 407 public function setCheckForUnintentionallyCoveredCode($flag) 408 { 409 if (!is_bool($flag)) { 410 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 411 1, 412 'boolean' 413 ); 414 } 415 416 $this->checkForUnintentionallyCoveredCode = $flag; 417 } 418 419 /** 420 * @param bool $flag 421 * @throws PHP_CodeCoverage_Exception 422 */ 423 public function setForceCoversAnnotation($flag) 424 { 425 if (!is_bool($flag)) { 426 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 427 1, 428 'boolean' 429 ); 430 } 431 432 $this->forceCoversAnnotation = $flag; 433 } 434 435 /** 436 * @param bool $flag 437 * @throws PHP_CodeCoverage_Exception 438 */ 439 public function setMapTestClassNameToCoveredClassName($flag) 440 { 441 if (!is_bool($flag)) { 442 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 443 1, 444 'boolean' 445 ); 446 } 447 448 $this->mapTestClassNameToCoveredClassName = $flag; 449 } 450 451 /** 452 * @param bool $flag 453 * @throws PHP_CodeCoverage_Exception 454 */ 455 public function setAddUncoveredFilesFromWhitelist($flag) 456 { 457 if (!is_bool($flag)) { 458 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 459 1, 460 'boolean' 461 ); 462 } 463 464 $this->addUncoveredFilesFromWhitelist = $flag; 465 } 466 467 /** 468 * @param bool $flag 469 * @throws PHP_CodeCoverage_Exception 470 */ 471 public function setProcessUncoveredFilesFromWhitelist($flag) 472 { 473 if (!is_bool($flag)) { 474 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 475 1, 476 'boolean' 477 ); 478 } 479 480 $this->processUncoveredFilesFromWhitelist = $flag; 481 } 482 483 /** 484 * @param bool $flag 485 * @throws PHP_CodeCoverage_Exception 486 */ 487 public function setDisableIgnoredLines($flag) 488 { 489 if (!is_bool($flag)) { 490 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 491 1, 492 'boolean' 493 ); 494 } 495 496 $this->disableIgnoredLines = $flag; 497 } 498 499 /** 500 * Applies the @covers annotation filtering. 501 * 502 * @param array $data 503 * @param mixed $linesToBeCovered 504 * @param array $linesToBeUsed 505 * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode 506 */ 507 private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed) 508 { 509 if ($linesToBeCovered === false || 510 ($this->forceCoversAnnotation && empty($linesToBeCovered))) { 511 $data = array(); 512 513 return; 514 } 515 516 if (empty($linesToBeCovered)) { 517 return; 518 } 519 520 if ($this->checkForUnintentionallyCoveredCode) { 521 $this->performUnintentionallyCoveredCodeCheck( 522 $data, 523 $linesToBeCovered, 524 $linesToBeUsed 525 ); 526 } 527 528 $data = array_intersect_key($data, $linesToBeCovered); 529 530 foreach (array_keys($data) as $filename) { 531 $_linesToBeCovered = array_flip($linesToBeCovered[$filename]); 532 533 $data[$filename] = array_intersect_key( 534 $data[$filename], 535 $_linesToBeCovered 536 ); 537 } 538 } 539 540 /** 541 * Applies the blacklist/whitelist filtering. 542 * 543 * @param array $data 544 */ 545 private function applyListsFilter(array &$data) 546 { 547 foreach (array_keys($data) as $filename) { 548 if ($this->filter->isFiltered($filename)) { 549 unset($data[$filename]); 550 } 551 } 552 } 553 554 /** 555 * Applies the "ignored lines" filtering. 556 * 557 * @param array $data 558 */ 559 private function applyIgnoredLinesFilter(array &$data) 560 { 561 foreach (array_keys($data) as $filename) { 562 if (!$this->filter->isFile($filename)) { 563 continue; 564 } 565 566 foreach ($this->getLinesToBeIgnored($filename) as $line) { 567 unset($data[$filename][$line]); 568 } 569 } 570 } 571 572 /** 573 * @param array $data 574 * @since Method available since Release 1.1.0 575 */ 576 private function initializeFilesThatAreSeenTheFirstTime(array $data) 577 { 578 foreach ($data as $file => $lines) { 579 if ($this->filter->isFile($file) && !isset($this->data[$file])) { 580 $this->data[$file] = array(); 581 582 foreach ($lines as $k => $v) { 583 $this->data[$file][$k] = $v == -2 ? null : array(); 584 } 585 } 586 } 587 } 588 589 /** 590 * Processes whitelisted files that are not covered. 591 */ 592 private function addUncoveredFilesFromWhitelist() 593 { 594 $data = array(); 595 $uncoveredFiles = array_diff( 596 $this->filter->getWhitelist(), 597 array_keys($this->data) 598 ); 599 600 foreach ($uncoveredFiles as $uncoveredFile) { 601 if (!file_exists($uncoveredFile)) { 602 continue; 603 } 604 605 if ($this->processUncoveredFilesFromWhitelist) { 606 $this->processUncoveredFileFromWhitelist( 607 $uncoveredFile, 608 $data, 609 $uncoveredFiles 610 ); 611 } else { 612 $data[$uncoveredFile] = array(); 613 614 $lines = count(file($uncoveredFile)); 615 616 for ($i = 1; $i <= $lines; $i++) { 617 $data[$uncoveredFile][$i] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED; 618 } 619 } 620 } 621 622 $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); 623 } 624 625 /** 626 * @param string $uncoveredFile 627 * @param array $data 628 * @param array $uncoveredFiles 629 */ 630 private function processUncoveredFileFromWhitelist($uncoveredFile, array &$data, array $uncoveredFiles) 631 { 632 $this->driver->start(); 633 include_once $uncoveredFile; 634 $coverage = $this->driver->stop(); 635 636 foreach ($coverage as $file => $fileCoverage) { 637 if (!isset($data[$file]) && 638 in_array($file, $uncoveredFiles)) { 639 foreach (array_keys($fileCoverage) as $key) { 640 if ($fileCoverage[$key] == PHP_CodeCoverage_Driver::LINE_EXECUTED) { 641 $fileCoverage[$key] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED; 642 } 643 } 644 645 $data[$file] = $fileCoverage; 646 } 647 } 648 } 649 650 /** 651 * Returns the lines of a source file that should be ignored. 652 * 653 * @param string $filename 654 * @return array 655 * @throws PHP_CodeCoverage_Exception 656 * @since Method available since Release 2.0.0 657 */ 658 private function getLinesToBeIgnored($filename) 659 { 660 if (!is_string($filename)) { 661 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 662 1, 663 'string' 664 ); 665 } 666 667 if (!isset($this->ignoredLines[$filename])) { 668 $this->ignoredLines[$filename] = array(); 669 670 if ($this->disableIgnoredLines) { 671 return $this->ignoredLines[$filename]; 672 } 673 674 $ignore = false; 675 $stop = false; 676 $lines = file($filename); 677 $numLines = count($lines); 678 679 foreach ($lines as $index => $line) { 680 if (!trim($line)) { 681 $this->ignoredLines[$filename][] = $index + 1; 682 } 683 } 684 685 if ($this->cacheTokens) { 686 $tokens = PHP_Token_Stream_CachingFactory::get($filename); 687 } else { 688 $tokens = new PHP_Token_Stream($filename); 689 } 690 691 $classes = array_merge($tokens->getClasses(), $tokens->getTraits()); 692 $tokens = $tokens->tokens(); 693 694 foreach ($tokens as $token) { 695 switch (get_class($token)) { 696 case 'PHP_Token_COMMENT': 697 case 'PHP_Token_DOC_COMMENT': 698 $_token = trim($token); 699 $_line = trim($lines[$token->getLine() - 1]); 700 701 if ($_token == '// @codeCoverageIgnore' || 702 $_token == '//@codeCoverageIgnore') { 703 $ignore = true; 704 $stop = true; 705 } elseif ($_token == '// @codeCoverageIgnoreStart' || 706 $_token == '//@codeCoverageIgnoreStart') { 707 $ignore = true; 708 } elseif ($_token == '// @codeCoverageIgnoreEnd' || 709 $_token == '//@codeCoverageIgnoreEnd') { 710 $stop = true; 711 } 712 713 if (!$ignore) { 714 $start = $token->getLine(); 715 $end = $start + substr_count($token, "\n"); 716 717 // Do not ignore the first line when there is a token 718 // before the comment 719 if (0 !== strpos($_token, $_line)) { 720 $start++; 721 } 722 723 for ($i = $start; $i < $end; $i++) { 724 $this->ignoredLines[$filename][] = $i; 725 } 726 727 // A DOC_COMMENT token or a COMMENT token starting with "/*" 728 // does not contain the final \n character in its text 729 if (isset($lines[$i-1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i-1]), -2)) { 730 $this->ignoredLines[$filename][] = $i; 731 } 732 } 733 break; 734 735 case 'PHP_Token_INTERFACE': 736 case 'PHP_Token_TRAIT': 737 case 'PHP_Token_CLASS': 738 case 'PHP_Token_FUNCTION': 739 $docblock = $token->getDocblock(); 740 741 $this->ignoredLines[$filename][] = $token->getLine(); 742 743 if (strpos($docblock, '@codeCoverageIgnore') || strpos($docblock, '@deprecated')) { 744 $endLine = $token->getEndLine(); 745 746 for ($i = $token->getLine(); $i <= $endLine; $i++) { 747 $this->ignoredLines[$filename][] = $i; 748 } 749 } elseif ($token instanceof PHP_Token_INTERFACE || 750 $token instanceof PHP_Token_TRAIT || 751 $token instanceof PHP_Token_CLASS) { 752 if (empty($classes[$token->getName()]['methods'])) { 753 for ($i = $token->getLine(); 754 $i <= $token->getEndLine(); 755 $i++) { 756 $this->ignoredLines[$filename][] = $i; 757 } 758 } else { 759 $firstMethod = array_shift( 760 $classes[$token->getName()]['methods'] 761 ); 762 763 do { 764 $lastMethod = array_pop( 765 $classes[$token->getName()]['methods'] 766 ); 767 } while ($lastMethod !== null && 768 substr($lastMethod['signature'], 0, 18) == 'anonymous function'); 769 770 if ($lastMethod === null) { 771 $lastMethod = $firstMethod; 772 } 773 774 for ($i = $token->getLine(); 775 $i < $firstMethod['startLine']; 776 $i++) { 777 $this->ignoredLines[$filename][] = $i; 778 } 779 780 for ($i = $token->getEndLine(); 781 $i > $lastMethod['endLine']; 782 $i--) { 783 $this->ignoredLines[$filename][] = $i; 784 } 785 } 786 } 787 break; 788 789 case 'PHP_Token_NAMESPACE': 790 $this->ignoredLines[$filename][] = $token->getEndLine(); 791 792 // Intentional fallthrough 793 case 'PHP_Token_OPEN_TAG': 794 case 'PHP_Token_CLOSE_TAG': 795 case 'PHP_Token_USE': 796 $this->ignoredLines[$filename][] = $token->getLine(); 797 break; 798 } 799 800 if ($ignore) { 801 $this->ignoredLines[$filename][] = $token->getLine(); 802 803 if ($stop) { 804 $ignore = false; 805 $stop = false; 806 } 807 } 808 } 809 810 $this->ignoredLines[$filename][] = $numLines + 1; 811 812 $this->ignoredLines[$filename] = array_unique( 813 $this->ignoredLines[$filename] 814 ); 815 816 sort($this->ignoredLines[$filename]); 817 } 818 819 return $this->ignoredLines[$filename]; 820 } 821 822 /** 823 * @param array $data 824 * @param array $linesToBeCovered 825 * @param array $linesToBeUsed 826 * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode 827 * @since Method available since Release 2.0.0 828 */ 829 private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed) 830 { 831 $allowedLines = $this->getAllowedLines( 832 $linesToBeCovered, 833 $linesToBeUsed 834 ); 835 836 $message = ''; 837 838 foreach ($data as $file => $_data) { 839 foreach ($_data as $line => $flag) { 840 if ($flag == 1 && 841 (!isset($allowedLines[$file]) || 842 !isset($allowedLines[$file][$line]))) { 843 $message .= sprintf( 844 '- %s:%d' . PHP_EOL, 845 $file, 846 $line 847 ); 848 } 849 } 850 } 851 852 if (!empty($message)) { 853 throw new PHP_CodeCoverage_Exception_UnintentionallyCoveredCode( 854 $message 855 ); 856 } 857 } 858 859 /** 860 * @param array $linesToBeCovered 861 * @param array $linesToBeUsed 862 * @return array 863 * @since Method available since Release 2.0.0 864 */ 865 private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed) 866 { 867 $allowedLines = array(); 868 869 foreach (array_keys($linesToBeCovered) as $file) { 870 if (!isset($allowedLines[$file])) { 871 $allowedLines[$file] = array(); 872 } 873 874 $allowedLines[$file] = array_merge( 875 $allowedLines[$file], 876 $linesToBeCovered[$file] 877 ); 878 } 879 880 foreach (array_keys($linesToBeUsed) as $file) { 881 if (!isset($allowedLines[$file])) { 882 $allowedLines[$file] = array(); 883 } 884 885 $allowedLines[$file] = array_merge( 886 $allowedLines[$file], 887 $linesToBeUsed[$file] 888 ); 889 } 890 891 foreach (array_keys($allowedLines) as $file) { 892 $allowedLines[$file] = array_flip( 893 array_unique($allowedLines[$file]) 894 ); 895 } 896 897 return $allowedLines; 898 } 899 900 /** 901 * @return PHP_CodeCoverage_Driver 902 * @throws PHP_CodeCoverage_Exception 903 */ 904 private function selectDriver() 905 { 906 $runtime = new Runtime; 907 908 if (!$runtime->canCollectCodeCoverage()) { 909 throw new PHP_CodeCoverage_Exception('No code coverage driver available'); 910 } 911 912 if ($runtime->isHHVM()) { 913 return new PHP_CodeCoverage_Driver_HHVM; 914 } elseif ($runtime->isPHPDBG()) { 915 return new PHP_CodeCoverage_Driver_PHPDBG; 916 } else { 917 return new PHP_CodeCoverage_Driver_Xdebug; 918 } 919 } 920 }