Skip to content

Commit d914c66

Browse files
committed
Add remote coverage by c3
1 parent 958162d commit d914c66

File tree

4 files changed

+379
-0
lines changed

4 files changed

+379
-0
lines changed

c3.php

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
<?php
2+
// @codingStandardsIgnoreFile
3+
// @codeCoverageIgnoreStart
4+
5+
/**
6+
* C3 - Codeception Code Coverage
7+
*
8+
* @author tiger
9+
*/
10+
11+
// $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_DEBUG'] = 1;
12+
13+
if (isset($_COOKIE['CODECEPTION_CODECOVERAGE'])) {
14+
$cookie = json_decode($_COOKIE['CODECEPTION_CODECOVERAGE'], true);
15+
16+
// fix for improperly encoded JSON in Code Coverage cookie with WebDriver.
17+
// @see https://github.com/Codeception/Codeception/issues/874
18+
if (!is_array($cookie)) {
19+
$cookie = json_decode($cookie, true);
20+
}
21+
22+
if ($cookie) {
23+
foreach ($cookie as $key => $value) {
24+
$_SERVER["HTTP_X_CODECEPTION_" . strtoupper($key)] = $value;
25+
}
26+
}
27+
}
28+
29+
if (!array_key_exists('HTTP_X_CODECEPTION_CODECOVERAGE', $_SERVER)) {
30+
return;
31+
}
32+
33+
if (!function_exists('__c3_error')) {
34+
function __c3_error($message)
35+
{
36+
$errorLogFile = defined('C3_CODECOVERAGE_ERROR_LOG_FILE') ?
37+
C3_CODECOVERAGE_ERROR_LOG_FILE :
38+
C3_CODECOVERAGE_MEDIATE_STORAGE . DIRECTORY_SEPARATOR . 'error.txt';
39+
if (is_writable($errorLogFile)) {
40+
file_put_contents($errorLogFile, $message);
41+
} else {
42+
$message = "Could not write error to log file ($errorLogFile), original message: $message";
43+
}
44+
if (!headers_sent()) {
45+
header('X-Codeception-CodeCoverage-Error: ' . str_replace("\n", ' ', $message), true, 500);
46+
}
47+
setcookie('CODECEPTION_CODECOVERAGE_ERROR', $message);
48+
}
49+
}
50+
51+
// phpunit codecoverage shimming
52+
if (!class_exists('PHP_CodeCoverage') and class_exists('SebastianBergmann\CodeCoverage\CodeCoverage')) {
53+
class_alias('SebastianBergmann\CodeCoverage\CodeCoverage', 'PHP_CodeCoverage');
54+
class_alias('SebastianBergmann\CodeCoverage\Report\Text', 'PHP_CodeCoverage_Report_Text');
55+
class_alias('SebastianBergmann\CodeCoverage\Report\PHP', 'PHP_CodeCoverage_Report_PHP');
56+
class_alias('SebastianBergmann\CodeCoverage\Report\Clover', 'PHP_CodeCoverage_Report_Clover');
57+
class_alias('SebastianBergmann\CodeCoverage\Report\Crap4j', 'PHP_CodeCoverage_Report_Crap4j');
58+
class_alias('SebastianBergmann\CodeCoverage\Report\Html\Facade', 'PHP_CodeCoverage_Report_HTML');
59+
class_alias('SebastianBergmann\CodeCoverage\Report\Xml\Facade', 'PHP_CodeCoverage_Report_XML');
60+
class_alias('SebastianBergmann\CodeCoverage\Exception', 'PHP_CodeCoverage_Exception');
61+
}
62+
63+
// Autoload Codeception classes
64+
if (!class_exists('\\Codeception\\Codecept')) {
65+
if (file_exists(__DIR__ . '/codecept.phar')) {
66+
require_once 'phar://' . __DIR__ . '/codecept.phar/autoload.php';
67+
} elseif (stream_resolve_include_path(__DIR__ . '/vendor/autoload.php')) {
68+
require_once __DIR__ . '/vendor/autoload.php';
69+
// Required to load some methods only available at codeception/autoload.php
70+
if (stream_resolve_include_path(__DIR__ . '/vendor/codeception/codeception/autoload.php')) {
71+
require_once __DIR__ . '/vendor/codeception/codeception/autoload.php';
72+
}
73+
} elseif (stream_resolve_include_path('Codeception/autoload.php')) {
74+
require_once 'Codeception/autoload.php';
75+
} else {
76+
__c3_error('Codeception is not loaded. Please check that either PHAR or Composer package can be used');
77+
}
78+
}
79+
80+
// Load Codeception Config
81+
$config_dist_file = realpath(__DIR__) . DIRECTORY_SEPARATOR . 'codeception.dist.yml';
82+
$config_file = realpath(__DIR__) . DIRECTORY_SEPARATOR . 'codeception.yml';
83+
84+
if (isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_CONFIG'])) {
85+
$config_file = realpath(__DIR__) . DIRECTORY_SEPARATOR . $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_CONFIG'];
86+
}
87+
if (file_exists($config_file)) {
88+
// Use codeception.yml for configuration.
89+
} elseif (file_exists($config_dist_file)) {
90+
// Use codeception.dist.yml for configuration.
91+
$config_file = $config_dist_file;
92+
} else {
93+
__c3_error(sprintf("Codeception config file '%s' not found", $config_file));
94+
}
95+
try {
96+
\Codeception\Configuration::config($config_file);
97+
} catch (\Exception $e) {
98+
__c3_error($e->getMessage());
99+
}
100+
101+
if (!defined('C3_CODECOVERAGE_MEDIATE_STORAGE')) {
102+
103+
// workaround for 'zend_mm_heap corrupted' problem
104+
gc_disable();
105+
106+
$memoryLimit = ini_get('memory_limit');
107+
$requiredMemory = '384M';
108+
if ((substr($memoryLimit, -1) === 'M' && (int)$memoryLimit < (int)$requiredMemory)
109+
|| (substr($memoryLimit, -1) === 'K' && (int)$memoryLimit < (int)$requiredMemory * 1024)
110+
|| (ctype_digit($memoryLimit) && (int)$memoryLimit < (int)$requiredMemory * 1024 * 1024)
111+
) {
112+
ini_set('memory_limit', $requiredMemory);
113+
}
114+
115+
define('C3_CODECOVERAGE_MEDIATE_STORAGE', Codeception\Configuration::logDir() . 'c3tmp');
116+
define('C3_CODECOVERAGE_PROJECT_ROOT', Codeception\Configuration::projectDir());
117+
define('C3_CODECOVERAGE_TESTNAME', $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE']);
118+
119+
function __c3_build_html_report(PHP_CodeCoverage $codeCoverage, $path)
120+
{
121+
$writer = new PHP_CodeCoverage_Report_HTML();
122+
$writer->process($codeCoverage, $path . 'html');
123+
124+
if (file_exists($path . '.tar')) {
125+
unlink($path . '.tar');
126+
}
127+
128+
$phar = new PharData($path . '.tar');
129+
$phar->setSignatureAlgorithm(Phar::SHA1);
130+
$files = $phar->buildFromDirectory($path . 'html');
131+
array_map('unlink', $files);
132+
133+
if (in_array('GZ', Phar::getSupportedCompression())) {
134+
if (file_exists($path . '.tar.gz')) {
135+
unlink($path . '.tar.gz');
136+
}
137+
138+
$phar->compress(\Phar::GZ);
139+
140+
// close the file so that we can rename it
141+
unset($phar);
142+
143+
unlink($path . '.tar');
144+
rename($path . '.tar.gz', $path . '.tar');
145+
}
146+
147+
return $path . '.tar';
148+
}
149+
150+
function __c3_build_clover_report(PHP_CodeCoverage $codeCoverage, $path)
151+
{
152+
$writer = new PHP_CodeCoverage_Report_Clover();
153+
$writer->process($codeCoverage, $path . '.clover.xml');
154+
155+
return $path . '.clover.xml';
156+
}
157+
158+
function __c3_build_crap4j_report(PHP_CodeCoverage $codeCoverage, $path)
159+
{
160+
$writer = new PHP_CodeCoverage_Report_Crap4j();
161+
$writer->process($codeCoverage, $path . '.crap4j.xml');
162+
163+
return $path . '.crap4j.xml';
164+
}
165+
166+
function __c3_build_phpunit_report(PHP_CodeCoverage $codeCoverage, $path)
167+
{
168+
$writer = new PHP_CodeCoverage_Report_XML(\PHPUnit_Runner_Version::id());
169+
$writer->process($codeCoverage, $path . 'phpunit');
170+
171+
if (file_exists($path . '.tar')) {
172+
unlink($path . '.tar');
173+
}
174+
175+
$phar = new PharData($path . '.tar');
176+
$phar->setSignatureAlgorithm(Phar::SHA1);
177+
$files = $phar->buildFromDirectory($path . 'phpunit');
178+
array_map('unlink', $files);
179+
180+
if (in_array('GZ', Phar::getSupportedCompression())) {
181+
if (file_exists($path . '.tar.gz')) {
182+
unlink($path . '.tar.gz');
183+
}
184+
185+
$phar->compress(\Phar::GZ);
186+
187+
// close the file so that we can rename it
188+
unset($phar);
189+
190+
unlink($path . '.tar');
191+
rename($path . '.tar.gz', $path . '.tar');
192+
}
193+
194+
return $path . '.tar';
195+
}
196+
197+
function __c3_send_file($filename)
198+
{
199+
if (!headers_sent()) {
200+
readfile($filename);
201+
}
202+
203+
return __c3_exit();
204+
}
205+
206+
/**
207+
* @param $filename
208+
* @param bool $lock Lock the file for writing?
209+
* @return [null|PHP_CodeCoverage|\SebastianBergmann\CodeCoverage\CodeCoverage, resource]
210+
*/
211+
function __c3_factory($filename, $lock=false)
212+
{
213+
$file = null;
214+
if ($filename !== null && is_readable($filename)) {
215+
if ($lock) {
216+
$file = fopen($filename, 'r+');
217+
if (flock($file, LOCK_EX)) {
218+
$phpCoverage = unserialize(stream_get_contents($file));
219+
} else {
220+
__c3_error("Failed to acquire write-lock for $filename");
221+
}
222+
} else {
223+
$phpCoverage = unserialize(file_get_contents($filename));
224+
}
225+
226+
return array($phpCoverage, $file);
227+
} else {
228+
$phpCoverage = new PHP_CodeCoverage();
229+
}
230+
231+
if (isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_SUITE'])) {
232+
$suite = $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_SUITE'];
233+
try {
234+
$settings = \Codeception\Configuration::suiteSettings($suite, \Codeception\Configuration::config());
235+
} catch (Exception $e) {
236+
__c3_error($e->getMessage());
237+
}
238+
} else {
239+
$settings = \Codeception\Configuration::config();
240+
}
241+
242+
try {
243+
\Codeception\Coverage\Filter::setup($phpCoverage)
244+
->whiteList($settings)
245+
->blackList($settings);
246+
} catch (Exception $e) {
247+
__c3_error($e->getMessage());
248+
}
249+
250+
return array($phpCoverage, $file);
251+
}
252+
253+
function __c3_exit()
254+
{
255+
if (!isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_DEBUG'])) {
256+
exit;
257+
}
258+
return null;
259+
}
260+
261+
function __c3_clear()
262+
{
263+
\Codeception\Util\FileSystem::doEmptyDir(C3_CODECOVERAGE_MEDIATE_STORAGE);
264+
}
265+
}
266+
267+
if (!is_dir(C3_CODECOVERAGE_MEDIATE_STORAGE)) {
268+
if (mkdir(C3_CODECOVERAGE_MEDIATE_STORAGE, 0777, true) === false) {
269+
__c3_error('Failed to create directory "' . C3_CODECOVERAGE_MEDIATE_STORAGE . '"');
270+
}
271+
}
272+
273+
// evaluate base path for c3-related files
274+
$path = realpath(C3_CODECOVERAGE_MEDIATE_STORAGE) . DIRECTORY_SEPARATOR . 'codecoverage';
275+
276+
$requested_c3_report = (strpos($_SERVER['REQUEST_URI'], 'c3/report') !== false);
277+
278+
$complete_report = $current_report = $path . '.serialized';
279+
if ($requested_c3_report) {
280+
set_time_limit(0);
281+
282+
$route = ltrim(strrchr($_SERVER['REQUEST_URI'], '/'), '/');
283+
284+
if ($route === 'clear') {
285+
__c3_clear();
286+
return __c3_exit();
287+
}
288+
289+
list($codeCoverage, ) = __c3_factory($complete_report);
290+
291+
switch ($route) {
292+
case 'html':
293+
try {
294+
__c3_send_file(__c3_build_html_report($codeCoverage, $path));
295+
} catch (Exception $e) {
296+
__c3_error($e->getMessage());
297+
}
298+
return __c3_exit();
299+
case 'clover':
300+
try {
301+
__c3_send_file(__c3_build_clover_report($codeCoverage, $path));
302+
} catch (Exception $e) {
303+
__c3_error($e->getMessage());
304+
}
305+
return __c3_exit();
306+
case 'crap4j':
307+
try {
308+
__c3_send_file(__c3_build_crap4j_report($codeCoverage, $path));
309+
} catch (Exception $e) {
310+
__c3_error($e->getMessage());
311+
}
312+
return __c3_exit();
313+
case 'serialized':
314+
try {
315+
__c3_send_file($complete_report);
316+
} catch (Exception $e) {
317+
__c3_error($e->getMessage());
318+
}
319+
return __c3_exit();
320+
case 'phpunit':
321+
try {
322+
__c3_send_file(__c3_build_phpunit_report($codeCoverage, $path));
323+
} catch (Exception $e) {
324+
__c3_error($e->getMessage());
325+
}
326+
return __c3_exit();
327+
}
328+
329+
} else {
330+
list($codeCoverage, ) = __c3_factory(null);
331+
$codeCoverage->start(C3_CODECOVERAGE_TESTNAME);
332+
if (!array_key_exists('HTTP_X_CODECEPTION_CODECOVERAGE_DEBUG', $_SERVER)) {
333+
register_shutdown_function(
334+
function () use ($codeCoverage, $current_report) {
335+
336+
$codeCoverage->stop();
337+
if (!file_exists(dirname($current_report))) { // verify directory exists
338+
if (!mkdir(dirname($current_report), 0777, true)) {
339+
__c3_error("Can't write CodeCoverage report into $current_report");
340+
}
341+
}
342+
343+
// This will either lock the existing report for writing and return it along with a file pointer,
344+
// or return a fresh PHP_CodeCoverage object without a file pointer. We'll merge the current request
345+
// into that coverage object, write it to disk, and release the lock. By doing this in the end of
346+
// the request, we avoid this scenario, where Request 2 overwrites the changes from Request 1:
347+
//
348+
// Time ->
349+
// Request 1 [ <read> <write> ]
350+
// Request 2 [ <read> <write> ]
351+
//
352+
// In addition, by locking the file for exclusive writing, we make sure no other request try to
353+
// read/write to the file at the same time as this request (leading to a corrupt file). flock() is a
354+
// blocking call, so it waits until an exclusive lock can be acquired before continuing.
355+
356+
list($existingCodeCoverage, $file) = __c3_factory($current_report, true);
357+
$existingCodeCoverage->merge($codeCoverage);
358+
359+
if ($file === null) {
360+
file_put_contents($current_report, serialize($existingCodeCoverage), LOCK_EX);
361+
} else {
362+
fseek($file, 0);
363+
fwrite($file, serialize($existingCodeCoverage));
364+
fflush($file);
365+
flock($file, LOCK_UN);
366+
fclose($file);
367+
}
368+
}
369+
);
370+
}
371+
}
372+
373+
// @codeCoverageIgnoreEnd

codeception.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
actor: Tester
22
coverage:
33
enabled: true
4+
remote: true
45
include:
56
- app/Http/*
67
- app/Post.php

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
},
1212
"require-dev": {
1313
"codeception/codeception": "2.3.x-dev",
14+
"codeception/c3": "2.*",
1415
"codeception/mockery-module": "0.2.3",
1516
"barryvdh/laravel-ide-helper": "2.4.1",
1617
"symfony/dom-crawler": "~3.0",

0 commit comments

Comments
 (0)