From 641e0e56a5dbeb6afa247888e823e87a94685997 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 15 Jul 2015 18:03:57 +0200 Subject: [PATCH 1/4] Added a phpdbg based code coverage driver. Uses phpdbg to create code coverage reports, without x-debug. Phpdbg works on opcode basis therefore code coverage numbers differ in comparison to x-debug. --- src/CodeCoverage/Driver.php | 6 ++ src/CodeCoverage/Driver/Phpdbg.php | 108 +++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/CodeCoverage/Driver/Phpdbg.php diff --git a/src/CodeCoverage/Driver.php b/src/CodeCoverage/Driver.php index 0c5f6d5ac..71d4b83a4 100644 --- a/src/CodeCoverage/Driver.php +++ b/src/CodeCoverage/Driver.php @@ -15,6 +15,12 @@ */ interface PHP_CodeCoverage_Driver { + // const values map to the defaults provided by xdebug-code-coverage + // see http://xdebug.org/docs/code_coverage + const LINE_EXECUTED = 1; + const LINE_NOT_EXECUTED = -1; + const LINE_NOT_EXECUTABLE = -2; + /** * Start collection of code coverage information. */ diff --git a/src/CodeCoverage/Driver/Phpdbg.php b/src/CodeCoverage/Driver/Phpdbg.php new file mode 100644 index 000000000..61632b625 --- /dev/null +++ b/src/CodeCoverage/Driver/Phpdbg.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Driver for Phpdbg's code coverage functionality. + * + * @since Class available since Release 2.2.0 + * @codeCoverageIgnore + */ +class PHP_CodeCoverage_Driver_Phpdbg implements PHP_CodeCoverage_Driver +{ + /** + * Constructor. + */ + public function __construct() + { + if (PHP_SAPI !== 'phpdbg') { + throw new PHP_CodeCoverage_Exception('This driver requires the phpdbg sapi'); + } + + if (version_compare(phpversion(), '7.0', '<')) { + // actually we require the phpdbg version shipped with php7, not php7 itself + throw new PHP_CodeCoverage_Exception( + 'phpdbg based code coverage requires at least php7' + ); + } + } + + /** + * Start collection of code coverage information. + */ + public function start() + { + phpdbg_start_oplog(); + } + + /** + * Stop collection of code coverage information. + * + * @return array + */ + public function stop() + { + $sourceLines = $this->fetchSourceLines(); + + $dbgData = phpdbg_end_oplog(array('show_unexecuted' => true)); + $data = $this->detectExecutedLines($sourceLines, $dbgData); + + return $data; + } + + /** + * Fetches all lines loaded at the time of calling, each source line marked as not executed. + * + * @return string + */ + private function fetchSourceLines() + { + $sourceLines = array(); + + foreach(get_included_files() as $file) { + foreach(token_get_all(file_get_contents($file)) as $token) { + + if (is_array($token)) { + list($name, $data, $lineNo) = $token; + + switch($name) { + case T_COMMENT: + case T_DOC_COMMENT: + case T_WHITESPACE: + // comments and whitespaces can never be executed, therefore skip them. + break; + default: { + $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; + } + } + } + } + } + + return $sourceLines; + } + + /** + * Convert phpdbg based data into the format CodeCoverage expects + * + * @param array $sourceLines + * @param array $dbgData + * @return array + */ + private function detectExecutedLines(array $sourceLines, array $dbgData) + { + foreach ($dbgData as $file => $coveredLines) { + foreach($coveredLines as $lineNo => $numExecuted) { + $sourceLines[$file][$lineNo] = $numExecuted > 0 ? self::LINE_EXECUTED : self::LINE_NOT_EXECUTED; + } + } + + return $sourceLines; + } +} From c2e14c49255e0c5231addcd62cc9074d1337b3ea Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Wed, 22 Jul 2015 17:35:36 +0200 Subject: [PATCH 2/4] Better coverage info --- src/CodeCoverage/Driver/Phpdbg.php | 46 +++++++----------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/src/CodeCoverage/Driver/Phpdbg.php b/src/CodeCoverage/Driver/Phpdbg.php index 61632b625..ac06d8ed1 100644 --- a/src/CodeCoverage/Driver/Phpdbg.php +++ b/src/CodeCoverage/Driver/Phpdbg.php @@ -48,44 +48,18 @@ public function start() */ public function stop() { - $sourceLines = $this->fetchSourceLines(); + $dbgData = phpdbg_end_oplog(); - $dbgData = phpdbg_end_oplog(array('show_unexecuted' => true)); - $data = $this->detectExecutedLines($sourceLines, $dbgData); - - return $data; - } - - /** - * Fetches all lines loaded at the time of calling, each source line marked as not executed. - * - * @return string - */ - private function fetchSourceLines() - { - $sourceLines = array(); - - foreach(get_included_files() as $file) { - foreach(token_get_all(file_get_contents($file)) as $token) { - - if (is_array($token)) { - list($name, $data, $lineNo) = $token; - - switch($name) { - case T_COMMENT: - case T_DOC_COMMENT: - case T_WHITESPACE: - // comments and whitespaces can never be executed, therefore skip them. - break; - default: { - $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; - } - } - } + $sourceLines = phpdbg_get_executable(); + foreach ($sourceLines as &$lines) { + foreach ($lines as &$line) { + $line = self::LINE_NOT_EXECUTED; } } - return $sourceLines; + $data = $this->detectExecutedLines($sourceLines, $dbgData); + + return $data; } /** @@ -98,8 +72,8 @@ private function fetchSourceLines() private function detectExecutedLines(array $sourceLines, array $dbgData) { foreach ($dbgData as $file => $coveredLines) { - foreach($coveredLines as $lineNo => $numExecuted) { - $sourceLines[$file][$lineNo] = $numExecuted > 0 ? self::LINE_EXECUTED : self::LINE_NOT_EXECUTED; + foreach ($coveredLines as $lineNo => $numExecuted) { + $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; } } From 11cc2f22cca62fb4a989fd86285cdf2c97f2c6e1 Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Wed, 22 Jul 2015 18:26:05 +0200 Subject: [PATCH 3/4] Add caching of source line fetching --- src/CodeCoverage/Driver/Phpdbg.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/CodeCoverage/Driver/Phpdbg.php b/src/CodeCoverage/Driver/Phpdbg.php index ac06d8ed1..ccdc9a342 100644 --- a/src/CodeCoverage/Driver/Phpdbg.php +++ b/src/CodeCoverage/Driver/Phpdbg.php @@ -48,16 +48,30 @@ public function start() */ public function stop() { + static $fetchedLines = array(); + $dbgData = phpdbg_end_oplog(); - $sourceLines = phpdbg_get_executable(); + if ($fetchedLines == array()) { + $sourceLines = phpdbg_get_executable(); + } else { + $newFiles = array_diff(get_included_files(), array_keys($fetchedLines)); + if ($newFiles) { + $sourceLines = phpdbg_get_executable(array("files" => $newFiles)); + } else { + $sourceLines = array(); + } + } + foreach ($sourceLines as &$lines) { foreach ($lines as &$line) { $line = self::LINE_NOT_EXECUTED; } } - $data = $this->detectExecutedLines($sourceLines, $dbgData); + $fetchedLines += $sourceLines; + + $data = $this->detectExecutedLines($fetchedLines, $dbgData); return $data; } From 6820e10b235856bd5bc564c98353e84c0a489e43 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 24 Jul 2015 15:26:12 +0200 Subject: [PATCH 4/4] detect phpdbg and use the driver subsequently TODO: Move this check into Environment\Runtime --- composer.json | 11 +++++++++-- src/CodeCoverage.php | 14 +++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 77d41ce79..0bcff5bce 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "phpunit/php-file-iterator": "~1.3", "phpunit/php-token-stream": "~1.3", "phpunit/php-text-template": "~1.2", - "sebastian/environment": "~1.0", + "sebastian/environment": "dev-phpdbg_cc", "sebastian/version": "~1.0" }, "require-dev": { @@ -46,5 +46,12 @@ "branch-alias": { "dev-master": "2.2.x-dev" } - } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/staabm/environment" + } + ] + } diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index b7e8fea91..086f00c14 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -96,13 +96,17 @@ class PHP_CodeCoverage public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null) { if ($driver === null) { - $runtime = new Runtime; + if (PHP_SAPI === 'phpdbg') { + $driver = new PHP_CodeCoverage_Driver_Phpdbg; + } else { + $runtime = new Runtime; - if (!$runtime->hasXdebug()) { - throw new PHP_CodeCoverage_Exception('No code coverage driver available'); - } + if (!$runtime->hasXdebug()) { + throw new PHP_CodeCoverage_Exception('No code coverage driver available'); + } - $driver = new PHP_CodeCoverage_Driver_Xdebug; + $driver = new PHP_CodeCoverage_Driver_Xdebug; + } } if ($filter === null) {