From 6a538b9dac78abdc3f5d463fe918cd0e47bc33e9 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Fri, 8 Jan 2016 10:42:27 +0100 Subject: [PATCH] CodeCoverage: added support for phpdbg strategy as alternative to Xdebug --- src/CodeCoverage/Collector.php | 117 +++++++++++++++++++++++++++++---- src/Runner/CliTester.php | 3 - 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/src/CodeCoverage/Collector.php b/src/CodeCoverage/Collector.php index 34c77655..1430f605 100644 --- a/src/CodeCoverage/Collector.php +++ b/src/CodeCoverage/Collector.php @@ -13,6 +13,11 @@ */ class Collector { + const + STRATEGY_AUTO = 'Auto', + STRATEGY_XDEBUG = 'Xdebug', + STRATEGY_PHPDBG = 'PhpDbg'; + /** @var resource */ private static $file; @@ -20,20 +25,66 @@ class Collector /** * Starts gathering the information for code coverage. * @param string + * @param string self::STRATEGY_* * @return void */ - public static function start($file) + public static function start($file, $strategy = self::STRATEGY_AUTO) { - if (!extension_loaded('xdebug')) { - throw new \Exception('Code coverage functionality requires Xdebug extension.'); - } elseif (self::$file) { + if (self::$file) { throw new \LogicException('Code coverage collector has been already started.'); } + $method = "start$strategy"; + if (!method_exists(__CLASS__, $method)) { + throw new \LogicException("Unknown strategy '$strategy'"); + } + self::$file = fopen($file, 'a+'); + self::$method(); + } + + + /** + * @return void + */ + private static function startAuto() + { + if (defined('PHPDBG_VERSION')) { + self::startPhpDbg(); + } else { + self::startXdebug(); + } + } + + + /** + * @return void + */ + private static function startXdebug() + { + if (!extension_loaded('xdebug')) { + throw new \Exception('Code coverage functionality requires Xdebug extension.'); + } + xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); register_shutdown_function(function () { - register_shutdown_function(array(__CLASS__, 'save')); + register_shutdown_function(array(__CLASS__, 'saveXdebug')); + }); + } + + + /** + * @return void + */ + private static function startPhpDbg() + { + if (!defined('PHPDBG_VERSION')) { + throw new \Exception('Code coverage functionality requires phpdbg SAPI.'); + } + + phpdbg_start_oplog(); + register_shutdown_function(function () { + register_shutdown_function(array(__CLASS__, 'savePhpDbg')); }); } @@ -43,23 +94,63 @@ public static function start($file) * @return void * @internal */ - public static function save() + public static function saveXdebug() { - flock(self::$file, LOCK_EX); - fseek(self::$file, 0); - $coverage = @unserialize(stream_get_contents(self::$file)); // @ file may be empty + $positive = array(); + $negative = array(); - foreach (xdebug_get_code_coverage() as $filename => $lines) { - if (!file_exists($filename)) { + foreach (xdebug_get_code_coverage() as $file => $lines) { + if (!file_exists($file)) { continue; } + foreach ($lines as $num => $val) { - if (empty($coverage[$filename][$num]) || $val > 0) { - $coverage[$filename][$num] = $val; // -1 => untested; -2 => dead code + if ($val > 0) { + $positive[$file][$num] = $val; + } else { + $negative[$file][$num] = $val; } } } + self::save($positive, $negative); + } + + + /** + * Saves information about code coverage. Do not call directly. + * @return void + * @internal + */ + public static function savePhpDbg() + { + $positive = phpdbg_end_oplog(); + $negative = phpdbg_get_executable(); + + foreach ($positive as $file => &$lines) { + $lines = array_fill_keys(array_keys($lines), 1); + } + + foreach ($negative as $file => &$lines) { + $lines = array_fill_keys(array_keys($lines), -1); + } + + self::save($positive, $negative); + } + + + /** + * @param array + * @param array + * @return void + */ + private static function save(array $positive, array $negative) + { + flock(self::$file, LOCK_EX); + fseek(self::$file, 0); + $original = @unserialize(stream_get_contents(self::$file)) ?: array(); // @ file may be empty + $coverage = array_replace_recursive($negative, $original, $positive); + ftruncate(self::$file, 0); fwrite(self::$file, serialize($coverage)); fclose(self::$file); diff --git a/src/Runner/CliTester.php b/src/Runner/CliTester.php index 76ad768c..7c742e35 100644 --- a/src/Runner/CliTester.php +++ b/src/Runner/CliTester.php @@ -230,9 +230,6 @@ private function createRunner() /** @return string */ private function prepareCodeCoverage() { - if (!$this->interpreter->hasXdebug()) { - throw new \Exception("Code coverage functionality requires Xdebug extension (used {$this->interpreter->getCommandLine()})"); - } file_put_contents($this->options['--coverage'], ''); $file = realpath($this->options['--coverage']); putenv(Environment::COVERAGE . '=' . $file);