Mercurial > hg > isophonics-drupal-site
diff vendor/phpunit/php-code-coverage/src/CodeCoverage.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/phpunit/php-code-coverage/src/CodeCoverage.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,920 @@ +<?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; + } + } +}