Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: Chris@14: namespace SebastianBergmann\CodeCoverage\Driver; Chris@14: Chris@14: use SebastianBergmann\CodeCoverage\RuntimeException; Chris@14: Chris@14: /** Chris@14: * Driver for PHPDBG's code coverage functionality. Chris@14: * Chris@14: * @codeCoverageIgnore Chris@14: */ Chris@14: class PHPDBG implements Driver Chris@14: { Chris@14: /** Chris@14: * Constructor. Chris@14: */ Chris@14: public function __construct() Chris@14: { Chris@14: if (PHP_SAPI !== 'phpdbg') { Chris@14: throw new RuntimeException( Chris@14: 'This driver requires the PHPDBG SAPI' Chris@14: ); Chris@14: } Chris@14: Chris@14: if (!\function_exists('phpdbg_start_oplog')) { Chris@14: throw new RuntimeException( Chris@14: 'This build of PHPDBG does not support code coverage' Chris@14: ); Chris@14: } Chris@14: } Chris@14: Chris@14: /** Chris@14: * Start collection of code coverage information. Chris@14: * Chris@14: * @param bool $determineUnusedAndDead Chris@14: */ Chris@14: public function start($determineUnusedAndDead = true) Chris@14: { Chris@14: phpdbg_start_oplog(); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Stop collection of code coverage information. Chris@14: * Chris@14: * @return array Chris@14: */ Chris@14: public function stop() Chris@14: { Chris@14: static $fetchedLines = []; Chris@14: Chris@14: $dbgData = phpdbg_end_oplog(); Chris@14: Chris@14: if ($fetchedLines == []) { Chris@14: $sourceLines = phpdbg_get_executable(); Chris@14: } else { Chris@14: $newFiles = \array_diff( Chris@14: \get_included_files(), Chris@14: \array_keys($fetchedLines) Chris@14: ); Chris@14: Chris@14: if ($newFiles) { Chris@14: $sourceLines = phpdbg_get_executable( Chris@14: ['files' => $newFiles] Chris@14: ); Chris@14: } else { Chris@14: $sourceLines = []; Chris@14: } Chris@14: } Chris@14: Chris@14: foreach ($sourceLines as $file => $lines) { Chris@14: foreach ($lines as $lineNo => $numExecuted) { Chris@14: $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; Chris@14: } Chris@14: } Chris@14: Chris@14: $fetchedLines = \array_merge($fetchedLines, $sourceLines); Chris@14: Chris@14: return $this->detectExecutedLines($fetchedLines, $dbgData); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Convert phpdbg based data into the format CodeCoverage expects Chris@14: * Chris@14: * @param array $sourceLines Chris@14: * @param array $dbgData Chris@14: * Chris@14: * @return array Chris@14: */ Chris@14: private function detectExecutedLines(array $sourceLines, array $dbgData) Chris@14: { Chris@14: foreach ($dbgData as $file => $coveredLines) { Chris@14: foreach ($coveredLines as $lineNo => $numExecuted) { Chris@14: // phpdbg also reports $lineNo=0 when e.g. exceptions get thrown. Chris@14: // make sure we only mark lines executed which are actually executable. Chris@14: if (isset($sourceLines[$file][$lineNo])) { Chris@14: $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; Chris@14: } Chris@14: } Chris@14: } Chris@14: Chris@14: return $sourceLines; Chris@14: } Chris@14: }