This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
khosb/includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage.php
2012-12-21 12:59:09 +11:00

797 lines
22 KiB
PHP

<?php
/**
* PHP_CodeCoverage
*
* Copyright (c) 2009-2012, Sebastian Bergmann <sb@sebastian-bergmann.de>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Sebastian Bergmann nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @category PHP
* @package CodeCoverage
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright 2009-2012 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License
* @link http://github.com/sebastianbergmann/php-code-coverage
* @since File available since Release 1.0.0
*/
// @codeCoverageIgnoreStart
// @codingStandardsIgnoreStart
/**
* @SuppressWarnings(PHPMD)
*/
if (!function_exists('trait_exists')) {
function trait_exists($name)
{
return FALSE;
}
}
// @codingStandardsIgnoreEnd
// @codeCoverageIgnoreEnd
/**
* Provides collection functionality for PHP code coverage information.
*
* @category PHP
* @package CodeCoverage
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright 2009-2012 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License
* @link http://github.com/sebastianbergmann/php-code-coverage
* @since Class available since Release 1.0.0
*/
class PHP_CodeCoverage
{
/**
* @var PHP_CodeCoverage_Driver
*/
protected $driver;
/**
* @var PHP_CodeCoverage_Filter
*/
protected $filter;
/**
* @var boolean
*/
protected $cacheTokens = FALSE;
/**
* @var boolean
*/
protected $forceCoversAnnotation = FALSE;
/**
* @var boolean
*/
protected $mapTestClassNameToCoveredClassName = FALSE;
/**
* @var boolean
*/
protected $addUncoveredFilesFromWhitelist = TRUE;
/**
* @var boolean
*/
protected $processUncoveredFilesFromWhitelist = FALSE;
/**
* @var mixed
*/
protected $currentId;
/**
* Code coverage data.
*
* @var array
*/
protected $data = array();
/**
* Test data.
*
* @var array
*/
protected $tests = array();
/**
* Constructor.
*
* @param PHP_CodeCoverage_Driver $driver
* @param PHP_CodeCoverage_Filter $filter
*/
public function __construct(PHP_CodeCoverage_Driver $driver = NULL, PHP_CodeCoverage_Filter $filter = NULL)
{
if ($driver === NULL) {
$driver = new PHP_CodeCoverage_Driver_Xdebug;
}
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.
*
* @return array
* @since Method available since Release 1.1.0
*/
public function getData()
{
if ($this->addUncoveredFilesFromWhitelist) {
$this->addUncoveredFilesFromWhitelist();
}
// We need to apply the blacklist filter a second time
// when no whitelist is used.
if (!$this->filter->hasWhitelist()) {
$this->applyListsFilter($this->data);
}
return $this->data;
}
/**
* Returns the test data.
*
* @return array
* @since Method available since Release 1.1.0
*/
public function getTests()
{
return $this->tests;
}
/**
* Start collection of code coverage information.
*
* @param mixed $id
* @param boolean $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 boolean $append
* @return array
* @throws PHP_CodeCoverage_Exception
*/
public function stop($append = TRUE)
{
if (!is_bool($append)) {
throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
1, 'boolean'
);
}
$data = $this->driver->stop();
$this->append($data, NULL, $append);
$this->currentId = NULL;
return $data;
}
/**
* Appends code coverage data.
*
* @param array $data
* @param mixed $id
* @param boolean $append
*/
public function append(array $data, $id = NULL, $append = TRUE)
{
if ($id === NULL) {
$id = $this->currentId;
}
if ($id === NULL) {
throw new PHP_CodeCoverage_Exception;
}
$this->applyListsFilter($data);
$this->initializeFilesThatAreSeenTheFirstTime($data);
if (!$append) {
return;
}
if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') {
$this->applyCoversAnnotationFilter($data, $id);
}
if (empty($data)) {
return;
}
$status = NULL;
if ($id instanceof PHPUnit_Framework_TestCase) {
$status = $id->getStatus();
$id = get_class($id) . '::' . $id->getName();
}
else if ($id instanceof PHPUnit_Extensions_PhptTestCase) {
$id = $id->getName();
}
$this->tests[$id] = $status;
foreach ($data as $file => $lines) {
if (!$this->filter->isFile($file)) {
continue;
}
foreach ($lines as $k => $v) {
if ($v == 1) {
$this->data[$file][$k][] = $id;
}
}
}
}
/**
* Merges the data from another instance of PHP_CodeCoverage.
*
* @param PHP_CodeCoverage $that
*/
public function merge(PHP_CodeCoverage $that)
{
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 boolean $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;
}
/**
* @param boolean $flag
* @since Method available since Release 1.1.0
*/
public function getCacheTokens()
{
return $this->cacheTokens;
}
/**
* @param boolean $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 boolean $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 boolean $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 boolean $flag
* @throws PHP_CodeCoverage_Exception
*/
public function setProcessUncoveredFilesFromWhitelist($flag)
{
if (!is_bool($flag)) {
throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
1, 'boolean'
);
}
$this->processUncoveredFilesFromWhitelist = $flag;
}
/**
* Applies the @covers annotation filtering.
*
* @param array $data
* @param mixed $id
*/
protected function applyCoversAnnotationFilter(&$data, $id)
{
if ($id instanceof PHPUnit_Framework_TestCase) {
$testClassName = get_class($id);
$linesToBeCovered = $this->getLinesToBeCovered(
$testClassName, $id->getName()
);
if ($this->mapTestClassNameToCoveredClassName &&
empty($linesToBeCovered)) {
$testedClass = substr($testClassName, 0, -4);
if (class_exists($testedClass)) {
$class = new ReflectionClass($testedClass);
$linesToBeCovered = array(
$class->getFileName() => range(
$class->getStartLine(), $class->getEndLine()
)
);
}
}
} else {
$linesToBeCovered = array();
}
if (!empty($linesToBeCovered)) {
$data = array_intersect_key($data, $linesToBeCovered);
foreach (array_keys($data) as $filename) {
$data[$filename] = array_intersect_key(
$data[$filename], array_flip($linesToBeCovered[$filename])
);
}
}
else if ($this->forceCoversAnnotation) {
$data = array();
}
}
/**
* Applies the blacklist/whitelist filtering.
*
* @param array $data
*/
protected function applyListsFilter(&$data)
{
foreach (array_keys($data) as $filename) {
if ($this->filter->isFiltered($filename)) {
unset($data[$filename]);
}
}
}
/**
* @since Method available since Release 1.1.0
*/
protected function initializeFilesThatAreSeenTheFirstTime($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.
*/
protected 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] = -1;
}
}
}
$this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
}
/**
* @param string $uncoveredFile
* @param array $data
* @param array $uncoveredFiles
*/
protected 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] == 1) {
$fileCoverage[$key] = -1;
}
}
$data[$file] = $fileCoverage;
}
}
}
/**
* Returns the files and lines a test method wants to cover.
*
* @param string $className
* @param string $methodName
* @return array
* @since Method available since Release 1.2.0
*/
protected function getLinesToBeCovered($className, $methodName)
{
$codeToCoverList = array();
$result = array();
// @codeCoverageIgnoreStart
if (($pos = strpos($methodName, ' ')) !== FALSE) {
$methodName = substr($methodName, 0, $pos);
}
// @codeCoverageIgnoreEnd
$class = new ReflectionClass($className);
try {
$method = new ReflectionMethod($className, $methodName);
}
catch (ReflectionException $e) {
return array();
}
$docComment = substr($class->getDocComment(), 3, -2) . PHP_EOL . substr($method->getDocComment(), 3, -2);
$templateMethods = array(
'setUp', 'assertPreConditions', 'assertPostConditions', 'tearDown'
);
foreach ($templateMethods as $templateMethod) {
if ($class->hasMethod($templateMethod)) {
$reflector = $class->getMethod($templateMethod);
$docComment .= PHP_EOL . substr($reflector->getDocComment(), 3, -2);
unset($reflector);
}
}
if (strpos($docComment, '@coversNothing') !== FALSE) {
return $result;
}
$classShortcut = preg_match_all(
'(@coversDefaultClass\s+(?P<coveredClass>.*?)\s*$)m',
$class->getDocComment(),
$matches
);
if ($classShortcut) {
if ($classShortcut > 1) {
throw new PHP_CodeCoverage_Exception(
sprintf(
'More than one @coversClass annotation in class or interface "%s".',
$className
)
);
}
$classShortcut = $matches['coveredClass'][0];
}
$match = preg_match_all(
'(@covers\s+(?P<coveredElement>.*?)\s*(\(\s*\))?\s*$)m',
$docComment,
$matches
);
if ($match) {
foreach ($matches['coveredElement'] as $coveredElement) {
if ($classShortcut && strncmp($coveredElement, '::', 2) === 0) {
$coveredElement = $classShortcut . $coveredElement;
}
$codeToCoverList = array_merge(
$codeToCoverList,
$this->resolveCoversToReflectionObjects($coveredElement)
);
}
foreach ($codeToCoverList as $codeToCover) {
$fileName = $codeToCover->getFileName();
if (!isset($result[$fileName])) {
$result[$fileName] = array();
}
$result[$fileName] = array_unique(
array_merge(
$result[$fileName],
range(
$codeToCover->getStartLine(), $codeToCover->getEndLine()
)
)
);
}
}
return $result;
}
/**
* @param string $coveredElement
* @return array
* @since Method available since Release 1.2.0
*/
protected function resolveCoversToReflectionObjects($coveredElement)
{
$codeToCoverList = array();
if (strpos($coveredElement, '::') !== FALSE) {
list($className, $methodName) = explode('::', $coveredElement);
if (isset($methodName[0]) && $methodName[0] == '<') {
$classes = array($className);
foreach ($classes as $className) {
if (!class_exists($className) &&
!interface_exists($className)) {
throw new PHP_CodeCoverage_Exception(
sprintf(
'Trying to @cover not existing class or ' .
'interface "%s".',
$className
)
);
}
$class = new ReflectionClass($className);
$methods = $class->getMethods();
$inverse = isset($methodName[1]) && $methodName[1] == '!';
if (strpos($methodName, 'protected')) {
$visibility = 'isProtected';
}
else if (strpos($methodName, 'private')) {
$visibility = 'isPrivate';
}
else if (strpos($methodName, 'public')) {
$visibility = 'isPublic';
}
foreach ($methods as $method) {
if ($inverse && !$method->$visibility()) {
$codeToCoverList[] = $method;
}
else if (!$inverse && $method->$visibility()) {
$codeToCoverList[] = $method;
}
}
}
} else {
$classes = array($className);
foreach ($classes as $className) {
if ($className == '' && function_exists($methodName)) {
$codeToCoverList[] = new ReflectionFunction(
$methodName
);
} else {
if (!((class_exists($className) ||
interface_exists($className) ||
trait_exists($className)) &&
method_exists($className, $methodName))) {
throw new PHP_CodeCoverage_Exception(
sprintf(
'Trying to @cover not existing method "%s::%s".',
$className,
$methodName
)
);
}
$codeToCoverList[] = new ReflectionMethod(
$className, $methodName
);
}
}
}
} else {
$extended = FALSE;
if (strpos($coveredElement, '<extended>') !== FALSE) {
$coveredElement = str_replace(
'<extended>', '', $coveredElement
);
$extended = TRUE;
}
$classes = array($coveredElement);
if ($extended) {
$classes = array_merge(
$classes,
class_implements($coveredElement),
class_parents($coveredElement)
);
}
foreach ($classes as $className) {
if (!class_exists($className) &&
!interface_exists($className) &&
!trait_exists($className)) {
throw new PHP_CodeCoverage_Exception(
sprintf(
'Trying to @cover not existing class or ' .
'interface "%s".',
$className
)
);
}
$codeToCoverList[] = new ReflectionClass($className);
}
}
return $codeToCoverList;
}
}