--- /dev/null
+<?php
+/*
+ * This file is part of the PHP_CodeCoverage package.
+ *
+ * (c) Sebastian Bergmann <sebastian@phpunit.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use SebastianBergmann\Environment\Runtime;
+
+/**
+ * Provides collection functionality for PHP code coverage information.
+ *
+ * @since Class available since Release 1.0.0
+ */
+class PHP_CodeCoverage
+{
+ /**
+ * @var PHP_CodeCoverage_Driver
+ */
+ private $driver;
+
+ /**
+ * @var PHP_CodeCoverage_Filter
+ */
+ private $filter;
+
+ /**
+ * @var bool
+ */
+ private $cacheTokens = false;
+
+ /**
+ * @var bool
+ */
+ private $checkForUnintentionallyCoveredCode = false;
+
+ /**
+ * @var bool
+ */
+ private $forceCoversAnnotation = false;
+
+ /**
+ * @var bool
+ */
+ private $mapTestClassNameToCoveredClassName = false;
+
+ /**
+ * @var bool
+ */
+ private $addUncoveredFilesFromWhitelist = true;
+
+ /**
+ * @var bool
+ */
+ private $processUncoveredFilesFromWhitelist = false;
+
+ /**
+ * @var mixed
+ */
+ private $currentId;
+
+ /**
+ * Code coverage data.
+ *
+ * @var array
+ */
+ private $data = array();
+
+ /**
+ * @var array
+ */
+ private $ignoredLines = array();
+
+ /**
+ * @var bool
+ */
+ private $disableIgnoredLines = false;
+
+ /**
+ * Test data.
+ *
+ * @var array
+ */
+ private $tests = array();
+
+ /**
+ * Constructor.
+ *
+ * @param PHP_CodeCoverage_Driver $driver
+ * @param PHP_CodeCoverage_Filter $filter
+ * @throws PHP_CodeCoverage_Exception
+ */
+ public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null)
+ {
+ if ($driver === null) {
+ $driver = $this->selectDriver();
+ }
+
+ if ($filter === null) {
+ $filter = new PHP_CodeCoverage_Filter;
+ }
+
+ $this->driver = $driver;
+ $this->filter = $filter;
+ }
+
+ /**
+ * Returns the PHP_CodeCoverage_Report_Node_* object graph
+ * for this PHP_CodeCoverage object.
+ *
+ * @return PHP_CodeCoverage_Report_Node_Directory
+ * @since Method available since Release 1.1.0
+ */
+ public function getReport()
+ {
+ $factory = new PHP_CodeCoverage_Report_Factory;
+
+ return $factory->create($this);
+ }
+
+ /**
+ * Clears collected code coverage data.
+ */
+ public function clear()
+ {
+ $this->currentId = null;
+ $this->data = array();
+ $this->tests = array();
+ }
+
+ /**
+ * Returns the PHP_CodeCoverage_Filter used.
+ *
+ * @return PHP_CodeCoverage_Filter
+ */
+ public function filter()
+ {
+ return $this->filter;
+ }
+
+ /**
+ * Returns the collected code coverage data.
+ * Set $raw = true to bypass all filters.
+ *
+ * @param bool $raw
+ * @return array
+ * @since Method available since Release 1.1.0
+ */
+ public function getData($raw = false)
+ {
+ if (!$raw && $this->addUncoveredFilesFromWhitelist) {
+ $this->addUncoveredFilesFromWhitelist();
+ }
+
+ // We need to apply the blacklist filter a second time
+ // when no whitelist is used.
+ if (!$raw && !$this->filter->hasWhitelist()) {
+ $this->applyListsFilter($this->data);
+ }
+
+ return $this->data;
+ }
+
+ /**
+ * Sets the coverage data.
+ *
+ * @param array $data
+ * @since Method available since Release 2.0.0
+ */
+ public function setData(array $data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * Returns the test data.
+ *
+ * @return array
+ * @since Method available since Release 1.1.0
+ */
+ public function getTests()
+ {
+ return $this->tests;
+ }
+
+ /**
+ * Sets the test data.
+ *
+ * @param array $tests
+ * @since Method available since Release 2.0.0
+ */
+ public function setTests(array $tests)
+ {
+ $this->tests = $tests;
+ }
+
+ /**
+ * Start collection of code coverage information.
+ *
+ * @param mixed $id
+ * @param bool $clear
+ * @throws PHP_CodeCoverage_Exception
+ */
+ public function start($id, $clear = false)
+ {
+ if (!is_bool($clear)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'boolean'
+ );
+ }
+
+ if ($clear) {
+ $this->clear();
+ }
+
+ $this->currentId = $id;
+
+ $this->driver->start();
+ }
+
+ /**
+ * Stop collection of code coverage information.
+ *
+ * @param bool $append
+ * @param mixed $linesToBeCovered
+ * @param array $linesToBeUsed
+ * @return array
+ * @throws PHP_CodeCoverage_Exception
+ */
+ public function stop($append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
+ {
+ if (!is_bool($append)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'boolean'
+ );
+ }
+
+ if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 2,
+ 'array or false'
+ );
+ }
+
+ $data = $this->driver->stop();
+ $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed);
+
+ $this->currentId = null;
+
+ return $data;
+ }
+
+ /**
+ * Appends code coverage data.
+ *
+ * @param array $data
+ * @param mixed $id
+ * @param bool $append
+ * @param mixed $linesToBeCovered
+ * @param array $linesToBeUsed
+ * @throws PHP_CodeCoverage_Exception
+ */
+ public function append(array $data, $id = null, $append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
+ {
+ if ($id === null) {
+ $id = $this->currentId;
+ }
+
+ if ($id === null) {
+ throw new PHP_CodeCoverage_Exception;
+ }
+
+ $this->applyListsFilter($data);
+ $this->applyIgnoredLinesFilter($data);
+ $this->initializeFilesThatAreSeenTheFirstTime($data);
+
+ if (!$append) {
+ return;
+ }
+
+ if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') {
+ $this->applyCoversAnnotationFilter(
+ $data,
+ $linesToBeCovered,
+ $linesToBeUsed
+ );
+ }
+
+ if (empty($data)) {
+ return;
+ }
+
+ $size = 'unknown';
+ $status = null;
+
+ if ($id instanceof PHPUnit_Framework_TestCase) {
+ $_size = $id->getSize();
+
+ if ($_size == PHPUnit_Util_Test::SMALL) {
+ $size = 'small';
+ } elseif ($_size == PHPUnit_Util_Test::MEDIUM) {
+ $size = 'medium';
+ } elseif ($_size == PHPUnit_Util_Test::LARGE) {
+ $size = 'large';
+ }
+
+ $status = $id->getStatus();
+ $id = get_class($id) . '::' . $id->getName();
+ } elseif ($id instanceof PHPUnit_Extensions_PhptTestCase) {
+ $size = 'large';
+ $id = $id->getName();
+ }
+
+ $this->tests[$id] = array('size' => $size, 'status' => $status);
+
+ foreach ($data as $file => $lines) {
+ if (!$this->filter->isFile($file)) {
+ continue;
+ }
+
+ foreach ($lines as $k => $v) {
+ if ($v == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
+ if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) {
+ $this->data[$file][$k][] = $id;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Merges the data from another instance of PHP_CodeCoverage.
+ *
+ * @param PHP_CodeCoverage $that
+ */
+ public function merge(PHP_CodeCoverage $that)
+ {
+ $this->filter->setBlacklistedFiles(
+ array_merge($this->filter->getBlacklistedFiles(), $that->filter()->getBlacklistedFiles())
+ );
+
+ $this->filter->setWhitelistedFiles(
+ array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
+ );
+
+ foreach ($that->data as $file => $lines) {
+ if (!isset($this->data[$file])) {
+ if (!$this->filter->isFiltered($file)) {
+ $this->data[$file] = $lines;
+ }
+
+ continue;
+ }
+
+ foreach ($lines as $line => $data) {
+ if ($data !== null) {
+ if (!isset($this->data[$file][$line])) {
+ $this->data[$file][$line] = $data;
+ } else {
+ $this->data[$file][$line] = array_unique(
+ array_merge($this->data[$file][$line], $data)
+ );
+ }
+ }
+ }
+ }
+
+ $this->tests = array_merge($this->tests, $that->getTests());
+
+ }
+
+ /**
+ * @param bool $flag
+ * @throws PHP_CodeCoverage_Exception
+ * @since Method available since Release 1.1.0
+ */
+ public function setCacheTokens($flag)
+ {
+ if (!is_bool($flag)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'boolean'
+ );
+ }
+
+ $this->cacheTokens = $flag;
+ }
+
+ /**
+ * @since Method available since Release 1.1.0
+ */
+ public function getCacheTokens()
+ {
+ return $this->cacheTokens;
+ }
+
+ /**
+ * @param bool $flag
+ * @throws PHP_CodeCoverage_Exception
+ * @since Method available since Release 2.0.0
+ */
+ public function setCheckForUnintentionallyCoveredCode($flag)
+ {
+ if (!is_bool($flag)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'boolean'
+ );
+ }
+
+ $this->checkForUnintentionallyCoveredCode = $flag;
+ }
+
+ /**
+ * @param bool $flag
+ * @throws PHP_CodeCoverage_Exception
+ */
+ public function setForceCoversAnnotation($flag)
+ {
+ if (!is_bool($flag)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'boolean'
+ );
+ }
+
+ $this->forceCoversAnnotation = $flag;
+ }
+
+ /**
+ * @param bool $flag
+ * @throws PHP_CodeCoverage_Exception
+ */
+ public function setMapTestClassNameToCoveredClassName($flag)
+ {
+ if (!is_bool($flag)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'boolean'
+ );
+ }
+
+ $this->mapTestClassNameToCoveredClassName = $flag;
+ }
+
+ /**
+ * @param bool $flag
+ * @throws PHP_CodeCoverage_Exception
+ */
+ public function setAddUncoveredFilesFromWhitelist($flag)
+ {
+ if (!is_bool($flag)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'boolean'
+ );
+ }
+
+ $this->addUncoveredFilesFromWhitelist = $flag;
+ }
+
+ /**
+ * @param bool $flag
+ * @throws PHP_CodeCoverage_Exception
+ */
+ public function setProcessUncoveredFilesFromWhitelist($flag)
+ {
+ if (!is_bool($flag)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'boolean'
+ );
+ }
+
+ $this->processUncoveredFilesFromWhitelist = $flag;
+ }
+
+ /**
+ * @param bool $flag
+ * @throws PHP_CodeCoverage_Exception
+ */
+ public function setDisableIgnoredLines($flag)
+ {
+ if (!is_bool($flag)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'boolean'
+ );
+ }
+
+ $this->disableIgnoredLines = $flag;
+ }
+
+ /**
+ * Applies the @covers annotation filtering.
+ *
+ * @param array $data
+ * @param mixed $linesToBeCovered
+ * @param array $linesToBeUsed
+ * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
+ */
+ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed)
+ {
+ if ($linesToBeCovered === false ||
+ ($this->forceCoversAnnotation && empty($linesToBeCovered))) {
+ $data = array();
+
+ return;
+ }
+
+ if (empty($linesToBeCovered)) {
+ return;
+ }
+
+ if ($this->checkForUnintentionallyCoveredCode) {
+ $this->performUnintentionallyCoveredCodeCheck(
+ $data,
+ $linesToBeCovered,
+ $linesToBeUsed
+ );
+ }
+
+ $data = array_intersect_key($data, $linesToBeCovered);
+
+ foreach (array_keys($data) as $filename) {
+ $_linesToBeCovered = array_flip($linesToBeCovered[$filename]);
+
+ $data[$filename] = array_intersect_key(
+ $data[$filename],
+ $_linesToBeCovered
+ );
+ }
+ }
+
+ /**
+ * Applies the blacklist/whitelist filtering.
+ *
+ * @param array $data
+ */
+ private function applyListsFilter(array &$data)
+ {
+ foreach (array_keys($data) as $filename) {
+ if ($this->filter->isFiltered($filename)) {
+ unset($data[$filename]);
+ }
+ }
+ }
+
+ /**
+ * Applies the "ignored lines" filtering.
+ *
+ * @param array $data
+ */
+ private function applyIgnoredLinesFilter(array &$data)
+ {
+ foreach (array_keys($data) as $filename) {
+ if (!$this->filter->isFile($filename)) {
+ continue;
+ }
+
+ foreach ($this->getLinesToBeIgnored($filename) as $line) {
+ unset($data[$filename][$line]);
+ }
+ }
+ }
+
+ /**
+ * @param array $data
+ * @since Method available since Release 1.1.0
+ */
+ private function initializeFilesThatAreSeenTheFirstTime(array $data)
+ {
+ foreach ($data as $file => $lines) {
+ if ($this->filter->isFile($file) && !isset($this->data[$file])) {
+ $this->data[$file] = array();
+
+ foreach ($lines as $k => $v) {
+ $this->data[$file][$k] = $v == -2 ? null : array();
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes whitelisted files that are not covered.
+ */
+ private function addUncoveredFilesFromWhitelist()
+ {
+ $data = array();
+ $uncoveredFiles = array_diff(
+ $this->filter->getWhitelist(),
+ array_keys($this->data)
+ );
+
+ foreach ($uncoveredFiles as $uncoveredFile) {
+ if (!file_exists($uncoveredFile)) {
+ continue;
+ }
+
+ if ($this->processUncoveredFilesFromWhitelist) {
+ $this->processUncoveredFileFromWhitelist(
+ $uncoveredFile,
+ $data,
+ $uncoveredFiles
+ );
+ } else {
+ $data[$uncoveredFile] = array();
+
+ $lines = count(file($uncoveredFile));
+
+ for ($i = 1; $i <= $lines; $i++) {
+ $data[$uncoveredFile][$i] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
+ }
+ }
+ }
+
+ $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
+ }
+
+ /**
+ * @param string $uncoveredFile
+ * @param array $data
+ * @param array $uncoveredFiles
+ */
+ private function processUncoveredFileFromWhitelist($uncoveredFile, array &$data, array $uncoveredFiles)
+ {
+ $this->driver->start();
+ include_once $uncoveredFile;
+ $coverage = $this->driver->stop();
+
+ foreach ($coverage as $file => $fileCoverage) {
+ if (!isset($data[$file]) &&
+ in_array($file, $uncoveredFiles)) {
+ foreach (array_keys($fileCoverage) as $key) {
+ if ($fileCoverage[$key] == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
+ $fileCoverage[$key] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
+ }
+ }
+
+ $data[$file] = $fileCoverage;
+ }
+ }
+ }
+
+ /**
+ * Returns the lines of a source file that should be ignored.
+ *
+ * @param string $filename
+ * @return array
+ * @throws PHP_CodeCoverage_Exception
+ * @since Method available since Release 2.0.0
+ */
+ private function getLinesToBeIgnored($filename)
+ {
+ if (!is_string($filename)) {
+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+ 1,
+ 'string'
+ );
+ }
+
+ if (!isset($this->ignoredLines[$filename])) {
+ $this->ignoredLines[$filename] = array();
+
+ if ($this->disableIgnoredLines) {
+ return $this->ignoredLines[$filename];
+ }
+
+ $ignore = false;
+ $stop = false;
+ $lines = file($filename);
+ $numLines = count($lines);
+
+ foreach ($lines as $index => $line) {
+ if (!trim($line)) {
+ $this->ignoredLines[$filename][] = $index + 1;
+ }
+ }
+
+ if ($this->cacheTokens) {
+ $tokens = PHP_Token_Stream_CachingFactory::get($filename);
+ } else {
+ $tokens = new PHP_Token_Stream($filename);
+ }
+
+ $classes = array_merge($tokens->getClasses(), $tokens->getTraits());
+ $tokens = $tokens->tokens();
+
+ foreach ($tokens as $token) {
+ switch (get_class($token)) {
+ case 'PHP_Token_COMMENT':
+ case 'PHP_Token_DOC_COMMENT':
+ $_token = trim($token);
+ $_line = trim($lines[$token->getLine() - 1]);
+
+ if ($_token == '// @codeCoverageIgnore' ||
+ $_token == '//@codeCoverageIgnore') {
+ $ignore = true;
+ $stop = true;
+ } elseif ($_token == '// @codeCoverageIgnoreStart' ||
+ $_token == '//@codeCoverageIgnoreStart') {
+ $ignore = true;
+ } elseif ($_token == '// @codeCoverageIgnoreEnd' ||
+ $_token == '//@codeCoverageIgnoreEnd') {
+ $stop = true;
+ }
+
+ if (!$ignore) {
+ $start = $token->getLine();
+ $end = $start + substr_count($token, "\n");
+
+ // Do not ignore the first line when there is a token
+ // before the comment
+ if (0 !== strpos($_token, $_line)) {
+ $start++;
+ }
+
+ for ($i = $start; $i < $end; $i++) {
+ $this->ignoredLines[$filename][] = $i;
+ }
+
+ // A DOC_COMMENT token or a COMMENT token starting with "/*"
+ // does not contain the final \n character in its text
+ if (isset($lines[$i-1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i-1]), -2)) {
+ $this->ignoredLines[$filename][] = $i;
+ }
+ }
+ break;
+
+ case 'PHP_Token_INTERFACE':
+ case 'PHP_Token_TRAIT':
+ case 'PHP_Token_CLASS':
+ case 'PHP_Token_FUNCTION':
+ $docblock = $token->getDocblock();
+
+ $this->ignoredLines[$filename][] = $token->getLine();
+
+ if (strpos($docblock, '@codeCoverageIgnore') || strpos($docblock, '@deprecated')) {
+ $endLine = $token->getEndLine();
+
+ for ($i = $token->getLine(); $i <= $endLine; $i++) {
+ $this->ignoredLines[$filename][] = $i;
+ }
+ } elseif ($token instanceof PHP_Token_INTERFACE ||
+ $token instanceof PHP_Token_TRAIT ||
+ $token instanceof PHP_Token_CLASS) {
+ if (empty($classes[$token->getName()]['methods'])) {
+ for ($i = $token->getLine();
+ $i <= $token->getEndLine();
+ $i++) {
+ $this->ignoredLines[$filename][] = $i;
+ }
+ } else {
+ $firstMethod = array_shift(
+ $classes[$token->getName()]['methods']
+ );
+
+ do {
+ $lastMethod = array_pop(
+ $classes[$token->getName()]['methods']
+ );
+ } while ($lastMethod !== null &&
+ substr($lastMethod['signature'], 0, 18) == 'anonymous function');
+
+ if ($lastMethod === null) {
+ $lastMethod = $firstMethod;
+ }
+
+ for ($i = $token->getLine();
+ $i < $firstMethod['startLine'];
+ $i++) {
+ $this->ignoredLines[$filename][] = $i;
+ }
+
+ for ($i = $token->getEndLine();
+ $i > $lastMethod['endLine'];
+ $i--) {
+ $this->ignoredLines[$filename][] = $i;
+ }
+ }
+ }
+ break;
+
+ case 'PHP_Token_NAMESPACE':
+ $this->ignoredLines[$filename][] = $token->getEndLine();
+
+ // Intentional fallthrough
+ case 'PHP_Token_OPEN_TAG':
+ case 'PHP_Token_CLOSE_TAG':
+ case 'PHP_Token_USE':
+ $this->ignoredLines[$filename][] = $token->getLine();
+ break;
+ }
+
+ if ($ignore) {
+ $this->ignoredLines[$filename][] = $token->getLine();
+
+ if ($stop) {
+ $ignore = false;
+ $stop = false;
+ }
+ }
+ }
+
+ $this->ignoredLines[$filename][] = $numLines + 1;
+
+ $this->ignoredLines[$filename] = array_unique(
+ $this->ignoredLines[$filename]
+ );
+
+ sort($this->ignoredLines[$filename]);
+ }
+
+ return $this->ignoredLines[$filename];
+ }
+
+ /**
+ * @param array $data
+ * @param array $linesToBeCovered
+ * @param array $linesToBeUsed
+ * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
+ * @since Method available since Release 2.0.0
+ */
+ private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
+ {
+ $allowedLines = $this->getAllowedLines(
+ $linesToBeCovered,
+ $linesToBeUsed
+ );
+
+ $message = '';
+
+ foreach ($data as $file => $_data) {
+ foreach ($_data as $line => $flag) {
+ if ($flag == 1 &&
+ (!isset($allowedLines[$file]) ||
+ !isset($allowedLines[$file][$line]))) {
+ $message .= sprintf(
+ '- %s:%d' . PHP_EOL,
+ $file,
+ $line
+ );
+ }
+ }
+ }
+
+ if (!empty($message)) {
+ throw new PHP_CodeCoverage_Exception_UnintentionallyCoveredCode(
+ $message
+ );
+ }
+ }
+
+ /**
+ * @param array $linesToBeCovered
+ * @param array $linesToBeUsed
+ * @return array
+ * @since Method available since Release 2.0.0
+ */
+ private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed)
+ {
+ $allowedLines = array();
+
+ foreach (array_keys($linesToBeCovered) as $file) {
+ if (!isset($allowedLines[$file])) {
+ $allowedLines[$file] = array();
+ }
+
+ $allowedLines[$file] = array_merge(
+ $allowedLines[$file],
+ $linesToBeCovered[$file]
+ );
+ }
+
+ foreach (array_keys($linesToBeUsed) as $file) {
+ if (!isset($allowedLines[$file])) {
+ $allowedLines[$file] = array();
+ }
+
+ $allowedLines[$file] = array_merge(
+ $allowedLines[$file],
+ $linesToBeUsed[$file]
+ );
+ }
+
+ foreach (array_keys($allowedLines) as $file) {
+ $allowedLines[$file] = array_flip(
+ array_unique($allowedLines[$file])
+ );
+ }
+
+ return $allowedLines;
+ }
+
+ /**
+ * @return PHP_CodeCoverage_Driver
+ * @throws PHP_CodeCoverage_Exception
+ */
+ private function selectDriver()
+ {
+ $runtime = new Runtime;
+
+ if (!$runtime->canCollectCodeCoverage()) {
+ throw new PHP_CodeCoverage_Exception('No code coverage driver available');
+ }
+
+ if ($runtime->isHHVM()) {
+ return new PHP_CodeCoverage_Driver_HHVM;
+ } elseif ($runtime->isPHPDBG()) {
+ return new PHP_CodeCoverage_Driver_PHPDBG;
+ } else {
+ return new PHP_CodeCoverage_Driver_Xdebug;
+ }
+ }
+}