Skip to content

Commit

Permalink
CodeCoverage: added support for phpdbg strategy as alternative to Xdebug
Browse files Browse the repository at this point in the history
  • Loading branch information
JanTvrdik committed Jan 8, 2016
1 parent c6d8165 commit 6a538b9
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 16 deletions.
117 changes: 104 additions & 13 deletions src/CodeCoverage/Collector.php
Expand Up @@ -13,27 +13,78 @@
*/
class Collector
{
const
STRATEGY_AUTO = 'Auto',
STRATEGY_XDEBUG = 'Xdebug',
STRATEGY_PHPDBG = 'PhpDbg';

/** @var resource */
private static $file;


/**
* 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'));
});
}

Expand All @@ -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);
Expand Down
3 changes: 0 additions & 3 deletions src/Runner/CliTester.php
Expand Up @@ -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);
Expand Down

0 comments on commit 6a538b9

Please sign in to comment.