Skip to content

Commit

Permalink
Merge pull request #298 from JanTvrdik/pr/manual_code_coverage_save
Browse files Browse the repository at this point in the history
Allow manually save partial code coverage to free memory
  • Loading branch information
milo committed Mar 30, 2016
2 parents facb91a + 292df78 commit 590de09
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 42 deletions.
69 changes: 43 additions & 26 deletions src/CodeCoverage/Collector.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ class Collector
/** @var resource */
private static $file;

/** @var string */
private static $collector;


/**
* @return bool
*/
public static function isStarted()
{
return self::$file !== NULL;
}


/**
* Starts gathering the information for code coverage.
Expand All @@ -24,33 +36,55 @@ class Collector
*/
public static function start($file)
{
if (self::$file) {
if (self::isStarted()) {
throw new \LogicException('Code coverage collector has been already started.');
}
self::$file = fopen($file, 'a+');
self::$file = fopen($file, 'c+');

if (defined('PHPDBG_VERSION') && PHP_VERSION_ID >= 70000) {
phpdbg_start_oplog();
$collector = 'collectPhpDbg';
self::$collector = 'collectPhpDbg';

} elseif (extension_loaded('xdebug')) {
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
$collector = 'collectXdebug';
self::$collector = 'collectXdebug';

} else {
$alternative = PHP_VERSION_ID >= 70000 ? ' or phpdbg SAPI' : '';
throw new \Exception("Code coverage functionality requires Xdebug extension$alternative.");
}

register_shutdown_function(function () use ($collector) {
register_shutdown_function(function () use ($collector) {
list($positive, $negative) = call_user_func([__CLASS__, $collector]);
self::save($positive, $negative);
});
register_shutdown_function(function () {
register_shutdown_function([__CLASS__, 'save']);
});
}


/**
* Saves information about code coverage. Can be called repeatedly to free memory.
* @return void
*/
public static function save()
{
list($positive, $negative) = call_user_func([__CLASS__, self::$collector]);

flock(self::$file, LOCK_EX);
fseek(self::$file, 0);
$rawContent = stream_get_contents(self::$file);
$original = $rawContent ? unserialize($rawContent) : [];
$coverage = array_replace_recursive($negative, $original, $positive);

fseek(self::$file, 0);
ftruncate(self::$file, 0);
fwrite(self::$file, serialize($coverage));
flock(self::$file, LOCK_UN);

if (self::$collector === 'collectPhpDbg') {
phpdbg_start_oplog();
}
}


/**
* Collects information about code coverage.
* @return array
Expand Down Expand Up @@ -97,21 +131,4 @@ private static function collectPhpDbg()
return [$positive, $negative];
}


/**
* Saves information about code coverage. Do not call directly.
* @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)) ?: []; // @ file may be empty
$coverage = array_replace_recursive($negative, $original, $positive);

ftruncate(self::$file, 0);
fwrite(self::$file, serialize($coverage));
fclose(self::$file);
}

}
40 changes: 40 additions & 0 deletions tests/CodeCoverage/Collector.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

use Tester\Assert;
use Tester\CodeCoverage;
use Tester\FileMock;

require __DIR__ . '/../bootstrap.php';


if (!extension_loaded('xdebug') && (!defined('PHPDBG_VERSION') || PHP_VERSION_ID < 70000)) {
Tester\Environment::skip('Requires Xdebug or phpdbg SAPI.');
}

if (CodeCoverage\Collector::isStarted()) {
Tester\Environment::skip('Requires running without --coverage.');
}

$outputFile = FileMock::create('');

Assert::false(CodeCoverage\Collector::isStarted());
CodeCoverage\Collector::start($outputFile);
Assert::true(CodeCoverage\Collector::isStarted());

Assert::exception(function () use ($outputFile) {
CodeCoverage\Collector::start($outputFile);
}, 'LogicException', 'Code coverage collector has been already started.');

$content = file_get_contents($outputFile);
Assert::same('', $content);

CodeCoverage\Collector::save();
$coverage = unserialize(file_get_contents($outputFile));
Assert::type('array', $coverage);
Assert::same(1, $coverage[__FILE__][__LINE__ - 3]); // line with 1st Collector::save()

CodeCoverage\Collector::save(); // save() can be called repeatedly
$coverage = unserialize(file_get_contents($outputFile));
Assert::type('array', $coverage);
Assert::same(1, $coverage[__FILE__][__LINE__ - 8]); // line with 1st Collector::save()
Assert::same(1, $coverage[__FILE__][__LINE__ - 4]); // line with 2nd Collector::save()
16 changes: 0 additions & 16 deletions tests/CodeCoverage/Collector.start.phpt

This file was deleted.

0 comments on commit 590de09

Please sign in to comment.