1 | <?php |
||||||
2 | // phpcs:ignoreFile |
||||||
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 | use SebastianBergmann\CodeCoverage\CodeCoverage; |
||||||
14 | use SebastianBergmann\CodeCoverage\Driver\Driver; |
||||||
15 | use SebastianBergmann\CodeCoverage\Driver\Selector; |
||||||
16 | use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter; |
||||||
17 | |||||||
18 | if (isset($_COOKIE['CODECEPTION_CODECOVERAGE'])) { |
||||||
19 | $cookie = json_decode($_COOKIE['CODECEPTION_CODECOVERAGE'], true); |
||||||
20 | |||||||
21 | // fix for improperly encoded JSON in Code Coverage cookie with WebDriver. |
||||||
22 | // @see https://github.com/Codeception/Codeception/issues/874 |
||||||
23 | if (!is_array($cookie)) { |
||||||
24 | $cookie = json_decode($cookie, true); |
||||||
25 | } |
||||||
26 | |||||||
27 | if ($cookie) { |
||||||
28 | foreach ($cookie as $key => $value) { |
||||||
29 | if (!empty($value)) { |
||||||
30 | $_SERVER["HTTP_X_CODECEPTION_" . strtoupper($key)] = $value; |
||||||
31 | } |
||||||
32 | } |
||||||
33 | } |
||||||
34 | } |
||||||
35 | |||||||
36 | if (!array_key_exists('HTTP_X_CODECEPTION_CODECOVERAGE', $_SERVER)) { |
||||||
37 | return; |
||||||
38 | } |
||||||
39 | |||||||
40 | if (!function_exists('__c3_error')) { |
||||||
41 | function __c3_error($message) |
||||||
42 | { |
||||||
43 | $errorLogFile = defined('C3_CODECOVERAGE_ERROR_LOG_FILE') ? |
||||||
44 | C3_CODECOVERAGE_ERROR_LOG_FILE : |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
45 | C3_CODECOVERAGE_MEDIATE_STORAGE . DIRECTORY_SEPARATOR . 'error.txt'; |
||||||
46 | if (is_writable($errorLogFile)) { |
||||||
47 | file_put_contents($errorLogFile, $message); |
||||||
48 | } else { |
||||||
49 | $message = "Could not write error to log file ($errorLogFile), original message: $message"; |
||||||
50 | } |
||||||
51 | if (!headers_sent()) { |
||||||
52 | header('X-Codeception-CodeCoverage-Error: ' . str_replace("\n", ' ', $message), true, 500); |
||||||
53 | setcookie('CODECEPTION_CODECOVERAGE_ERROR', $message); |
||||||
54 | } |
||||||
55 | } |
||||||
56 | } |
||||||
57 | |||||||
58 | // Autoload Codeception classes |
||||||
59 | if (!class_exists('\\Codeception\\Codecept') || !function_exists('codecept_is_path_absolute')) { |
||||||
60 | if (file_exists(__DIR__ . '/codecept.phar')) { |
||||||
61 | require_once 'phar://' . __DIR__ . '/codecept.phar/autoload.php'; |
||||||
62 | } elseif (stream_resolve_include_path(__DIR__ . '/vendor/autoload.php')) { |
||||||
63 | require_once __DIR__ . '/vendor/autoload.php'; |
||||||
64 | // Required to load some methods only available at codeception/autoload.php |
||||||
65 | if (stream_resolve_include_path(__DIR__ . '/vendor/codeception/codeception/autoload.php')) { |
||||||
66 | require_once __DIR__ . '/vendor/codeception/codeception/autoload.php'; |
||||||
67 | } |
||||||
68 | } elseif (stream_resolve_include_path('Codeception/autoload.php')) { |
||||||
69 | require_once 'Codeception/autoload.php'; |
||||||
70 | } else { |
||||||
71 | __c3_error('Codeception is not loaded. Please check that either PHAR or Composer package can be used'); |
||||||
72 | } |
||||||
73 | } |
||||||
74 | |||||||
75 | // phpunit codecoverage shimming |
||||||
76 | if (!class_exists('PHP_CodeCoverage') and class_exists('SebastianBergmann\CodeCoverage\CodeCoverage')) { |
||||||
77 | class_alias('SebastianBergmann\CodeCoverage\CodeCoverage', 'PHP_CodeCoverage'); |
||||||
78 | class_alias('SebastianBergmann\CodeCoverage\Report\Text', 'PHP_CodeCoverage_Report_Text'); |
||||||
79 | class_alias('SebastianBergmann\CodeCoverage\Report\PHP', 'PHP_CodeCoverage_Report_PHP'); |
||||||
80 | class_alias('SebastianBergmann\CodeCoverage\Report\Clover', 'PHP_CodeCoverage_Report_Clover'); |
||||||
81 | class_alias('SebastianBergmann\CodeCoverage\Report\Crap4j', 'PHP_CodeCoverage_Report_Crap4j'); |
||||||
82 | class_alias('SebastianBergmann\CodeCoverage\Report\Html\Facade', 'PHP_CodeCoverage_Report_HTML'); |
||||||
83 | class_alias('SebastianBergmann\CodeCoverage\Report\Xml\Facade', 'PHP_CodeCoverage_Report_XML'); |
||||||
84 | class_alias('SebastianBergmann\CodeCoverage\Exception', 'PHP_CodeCoverage_Exception'); |
||||||
85 | } |
||||||
86 | // phpunit version |
||||||
87 | if (!class_exists('PHPUnit_Runner_Version') && class_exists('PHPUnit\Runner\Version')) { |
||||||
88 | class_alias('PHPUnit\Runner\Version', 'PHPUnit_Runner_Version'); |
||||||
89 | } |
||||||
90 | |||||||
91 | // Load Codeception Config |
||||||
92 | $configDistFile = realpath(__DIR__) . DIRECTORY_SEPARATOR . 'codeception.dist.yml'; |
||||||
93 | $configFile = realpath(__DIR__) . DIRECTORY_SEPARATOR . 'codeception.yml'; |
||||||
94 | |||||||
95 | if (isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_CONFIG'])) { |
||||||
96 | $configFile = realpath(__DIR__) . DIRECTORY_SEPARATOR . $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_CONFIG']; |
||||||
97 | } |
||||||
98 | if (file_exists($configFile)) { |
||||||
99 | // Use codeception.yml for configuration. |
||||||
100 | } elseif (file_exists($configDistFile)) { |
||||||
101 | // Use codeception.dist.yml for configuration. |
||||||
102 | $configFile = $configDistFile; |
||||||
103 | } else { |
||||||
104 | __c3_error(sprintf("Codeception config file '%s' not found", $configFile)); |
||||||
105 | } |
||||||
106 | try { |
||||||
107 | \Codeception\Configuration::config($configFile); |
||||||
108 | } catch (\Exception $e) { |
||||||
109 | __c3_error($e->getMessage()); |
||||||
110 | } |
||||||
111 | |||||||
112 | if (!defined('C3_CODECOVERAGE_MEDIATE_STORAGE')) { |
||||||
113 | |||||||
114 | // workaround for 'zend_mm_heap corrupted' problem |
||||||
115 | gc_disable(); |
||||||
116 | |||||||
117 | $memoryLimit = ini_get('memory_limit'); |
||||||
118 | $requiredMemory = '384M'; |
||||||
119 | if ((substr($memoryLimit, -1) === 'M' && (int)$memoryLimit < (int)$requiredMemory) |
||||||
120 | || (substr($memoryLimit, -1) === 'K' && (int)$memoryLimit < (int)$requiredMemory * 1024) |
||||||
121 | || (ctype_digit($memoryLimit) && (int)$memoryLimit < (int)$requiredMemory * 1024 * 1024) |
||||||
122 | ) { |
||||||
123 | ini_set('memory_limit', $requiredMemory); |
||||||
124 | } |
||||||
125 | |||||||
126 | define('C3_CODECOVERAGE_MEDIATE_STORAGE', Codeception\Configuration::outputDir() . 'c3tmp'); |
||||||
127 | define('C3_CODECOVERAGE_PROJECT_ROOT', Codeception\Configuration::projectDir()); |
||||||
128 | define('C3_CODECOVERAGE_TESTNAME', $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE']); |
||||||
129 | |||||||
130 | function __c3_build_html_report(PHP_CodeCoverage $codeCoverage, $path) |
||||||
131 | { |
||||||
132 | $writer = new PHP_CodeCoverage_Report_HTML(); |
||||||
133 | $writer->process($codeCoverage, $path . 'html'); |
||||||
134 | |||||||
135 | if (file_exists($path . '.tar')) { |
||||||
136 | unlink($path . '.tar'); |
||||||
137 | } |
||||||
138 | |||||||
139 | $phar = new PharData($path . '.tar'); |
||||||
140 | $phar->setSignatureAlgorithm(Phar::SHA1); |
||||||
141 | $files = $phar->buildFromDirectory($path . 'html'); |
||||||
142 | array_map('unlink', $files); |
||||||
143 | |||||||
144 | if (in_array('GZ', Phar::getSupportedCompression())) { |
||||||
145 | if (file_exists($path . '.tar.gz')) { |
||||||
146 | unlink($path . '.tar.gz'); |
||||||
147 | } |
||||||
148 | |||||||
149 | $phar->compress(\Phar::GZ); |
||||||
150 | |||||||
151 | // close the file so that we can rename it |
||||||
152 | unset($phar); |
||||||
153 | |||||||
154 | unlink($path . '.tar'); |
||||||
155 | rename($path . '.tar.gz', $path . '.tar'); |
||||||
156 | } |
||||||
157 | |||||||
158 | return $path . '.tar'; |
||||||
159 | } |
||||||
160 | |||||||
161 | function __c3_build_clover_report(PHP_CodeCoverage $codeCoverage, $path) |
||||||
162 | { |
||||||
163 | $writer = new PHP_CodeCoverage_Report_Clover(); |
||||||
164 | $writer->process($codeCoverage, $path . '.clover.xml'); |
||||||
165 | |||||||
166 | return $path . '.clover.xml'; |
||||||
167 | } |
||||||
168 | |||||||
169 | function __c3_build_crap4j_report(PHP_CodeCoverage $codeCoverage, $path) |
||||||
170 | { |
||||||
171 | $writer = new PHP_CodeCoverage_Report_Crap4j(); |
||||||
172 | $writer->process($codeCoverage, $path . '.crap4j.xml'); |
||||||
173 | |||||||
174 | return $path . '.crap4j.xml'; |
||||||
175 | } |
||||||
176 | |||||||
177 | function __c3_build_cobertura_report(PHP_CodeCoverage $codeCoverage, $path) |
||||||
178 | { |
||||||
179 | if (!class_exists(\SebastianBergmann\CodeCoverage\Report\Cobertura::class)) { |
||||||
180 | throw new Exception("Cobertura report requires php-code-coverage >= 9.2"); |
||||||
181 | } |
||||||
182 | $writer = new \SebastianBergmann\CodeCoverage\Report\Cobertura(); |
||||||
183 | $writer->process($codeCoverage, $path . '.cobertura.xml'); |
||||||
184 | |||||||
185 | return $path . '.cobertura.xml'; |
||||||
186 | } |
||||||
187 | |||||||
188 | function __c3_build_phpunit_report(PHP_CodeCoverage $codeCoverage, $path) |
||||||
189 | { |
||||||
190 | $writer = new PHP_CodeCoverage_Report_XML(\PHPUnit_Runner_Version::id()); |
||||||
191 | $writer->process($codeCoverage, $path . 'phpunit'); |
||||||
192 | |||||||
193 | if (file_exists($path . '.tar')) { |
||||||
194 | unlink($path . '.tar'); |
||||||
195 | } |
||||||
196 | |||||||
197 | $phar = new PharData($path . '.tar'); |
||||||
198 | $phar->setSignatureAlgorithm(Phar::SHA1); |
||||||
199 | $files = $phar->buildFromDirectory($path . 'phpunit'); |
||||||
200 | array_map('unlink', $files); |
||||||
201 | |||||||
202 | if (in_array('GZ', Phar::getSupportedCompression())) { |
||||||
203 | if (file_exists($path . '.tar.gz')) { |
||||||
204 | unlink($path . '.tar.gz'); |
||||||
205 | } |
||||||
206 | |||||||
207 | $phar->compress(\Phar::GZ); |
||||||
208 | |||||||
209 | // close the file so that we can rename it |
||||||
210 | unset($phar); |
||||||
211 | |||||||
212 | unlink($path . '.tar'); |
||||||
213 | rename($path . '.tar.gz', $path . '.tar'); |
||||||
214 | } |
||||||
215 | |||||||
216 | return $path . '.tar'; |
||||||
217 | } |
||||||
218 | |||||||
219 | function __c3_send_file($filename) |
||||||
220 | { |
||||||
221 | if (!headers_sent()) { |
||||||
222 | readfile($filename); |
||||||
223 | } |
||||||
224 | |||||||
225 | return __c3_exit(); |
||||||
0 ignored issues
–
show
Are you sure the usage of
__c3_exit() is correct as it seems to always return null .
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
226 | } |
||||||
227 | |||||||
228 | /** |
||||||
229 | * @param $filename |
||||||
230 | * @param bool $lock Lock the file for writing? |
||||||
231 | * @return [null|PHP_CodeCoverage|\SebastianBergmann\CodeCoverage\CodeCoverage, resource] |
||||||
0 ignored issues
–
show
|
|||||||
232 | */ |
||||||
233 | function __c3_factory($filename, $lock = false) |
||||||
234 | { |
||||||
235 | $file = null; |
||||||
236 | if ($filename !== null && is_readable($filename)) { |
||||||
237 | if ($lock) { |
||||||
238 | $file = fopen($filename, 'r+'); |
||||||
239 | if (flock($file, LOCK_EX)) { |
||||||
240 | $phpCoverage = unserialize(stream_get_contents($file)); |
||||||
241 | } else { |
||||||
242 | __c3_error("Failed to acquire write-lock for $filename"); |
||||||
243 | } |
||||||
244 | } else { |
||||||
245 | $phpCoverage = unserialize(file_get_contents($filename)); |
||||||
246 | } |
||||||
247 | |||||||
248 | return [$phpCoverage, $file]; |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
249 | } |
||||||
250 | |||||||
251 | if (isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_SUITE'])) { |
||||||
252 | $suite = $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_SUITE']; |
||||||
253 | try { |
||||||
254 | $settings = \Codeception\Configuration::suiteSettings($suite, \Codeception\Configuration::config()); |
||||||
255 | } catch (Exception $e) { |
||||||
256 | __c3_error($e->getMessage()); |
||||||
257 | $settings = []; |
||||||
258 | } |
||||||
259 | } else { |
||||||
260 | $settings = \Codeception\Configuration::config(); |
||||||
261 | } |
||||||
262 | |||||||
263 | $pathCoverage = false; |
||||||
264 | if (isset($settings['coverage']['path_coverage'])) { |
||||||
265 | $pathCoverage = (bool)$settings['coverage']['path_coverage']; |
||||||
266 | } |
||||||
267 | |||||||
268 | if (class_exists(Selector::class)) { |
||||||
269 | //php-code-coverage >= 9.1.10 |
||||||
270 | $filter = new CodeCoverageFilter(); |
||||||
271 | if ($pathCoverage) { |
||||||
272 | $driver = (new Selector())->forLineAndPathCoverage($filter); |
||||||
273 | } else { |
||||||
274 | $driver = (new Selector())->forLineCoverage($filter); |
||||||
275 | } |
||||||
276 | $phpCoverage = new CodeCoverage($driver, $filter); |
||||||
277 | } elseif (method_exists(Driver::class, 'forLineCoverage')) { |
||||||
278 | //php-code-coverage 9.0.0 - 9.1.9 |
||||||
279 | $filter = new CodeCoverageFilter(); |
||||||
280 | if ($pathCoverage) { |
||||||
281 | $driver = Driver::forLineAndPathCoverage($filter); |
||||||
0 ignored issues
–
show
The method
forLineAndPathCoverage() does not exist on SebastianBergmann\CodeCoverage\Driver\Driver .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
282 | } else { |
||||||
283 | $driver = Driver::forLineCoverage($filter); |
||||||
0 ignored issues
–
show
The method
forLineCoverage() does not exist on SebastianBergmann\CodeCoverage\Driver\Driver .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
284 | } |
||||||
285 | $phpCoverage = new CodeCoverage($driver, $filter); |
||||||
286 | } else { |
||||||
287 | //php-code-coverage 8 or older |
||||||
288 | $phpCoverage = new PHP_CodeCoverage(); |
||||||
0 ignored issues
–
show
The call to
PHP_CodeCoverage::__construct() has too few arguments starting with driver .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
289 | } |
||||||
290 | |||||||
291 | try { |
||||||
292 | \Codeception\Coverage\Filter::setup($phpCoverage) |
||||||
293 | ->whiteList($settings) |
||||||
294 | ->blackList($settings); |
||||||
295 | } catch (Exception $e) { |
||||||
296 | __c3_error($e->getMessage()); |
||||||
297 | } |
||||||
298 | |||||||
299 | return [$phpCoverage, $file]; |
||||||
300 | } |
||||||
301 | |||||||
302 | function __c3_exit() |
||||||
303 | { |
||||||
304 | if (!isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_DEBUG'])) { |
||||||
305 | exit; |
||||||
0 ignored issues
–
show
|
|||||||
306 | } |
||||||
307 | return null; |
||||||
308 | } |
||||||
309 | |||||||
310 | function __c3_clear() |
||||||
311 | { |
||||||
312 | \Codeception\Util\FileSystem::doEmptyDir(C3_CODECOVERAGE_MEDIATE_STORAGE); |
||||||
313 | } |
||||||
314 | } |
||||||
315 | |||||||
316 | if (!is_dir(C3_CODECOVERAGE_MEDIATE_STORAGE)) { |
||||||
317 | if (mkdir(C3_CODECOVERAGE_MEDIATE_STORAGE, 0777, true) === false) { |
||||||
318 | __c3_error('Failed to create directory "' . C3_CODECOVERAGE_MEDIATE_STORAGE . '"'); |
||||||
319 | } |
||||||
320 | } |
||||||
321 | |||||||
322 | // evaluate base path for c3-related files |
||||||
323 | $path = realpath(C3_CODECOVERAGE_MEDIATE_STORAGE) . DIRECTORY_SEPARATOR . 'codecoverage'; |
||||||
324 | |||||||
325 | $requestedC3Report = (strpos($_SERVER['REQUEST_URI'], 'c3/report') !== false); |
||||||
326 | |||||||
327 | $completeReport = $currentReport = $path . '.serialized'; |
||||||
328 | if ($requestedC3Report) { |
||||||
329 | set_time_limit(0); |
||||||
330 | |||||||
331 | $route = ltrim(strrchr(rtrim($_SERVER['REQUEST_URI'], '/'), '/'), '/'); |
||||||
332 | |||||||
333 | if ($route === 'clear') { |
||||||
334 | __c3_clear(); |
||||||
335 | return __c3_exit(); |
||||||
0 ignored issues
–
show
Are you sure the usage of
__c3_exit() is correct as it seems to always return null .
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
336 | } |
||||||
337 | |||||||
338 | list($codeCoverage, ) = __c3_factory($completeReport); |
||||||
339 | |||||||
340 | switch ($route) { |
||||||
341 | case 'html': |
||||||
342 | try { |
||||||
343 | __c3_send_file(__c3_build_html_report($codeCoverage, $path)); |
||||||
344 | } catch (Exception $e) { |
||||||
345 | __c3_error($e->getMessage()); |
||||||
346 | } |
||||||
347 | return __c3_exit(); |
||||||
348 | case 'clover': |
||||||
349 | try { |
||||||
350 | __c3_send_file(__c3_build_clover_report($codeCoverage, $path)); |
||||||
351 | } catch (Exception $e) { |
||||||
352 | __c3_error($e->getMessage()); |
||||||
353 | } |
||||||
354 | return __c3_exit(); |
||||||
355 | case 'crap4j': |
||||||
356 | try { |
||||||
357 | __c3_send_file(__c3_build_crap4j_report($codeCoverage, $path)); |
||||||
358 | } catch (Exception $e) { |
||||||
359 | __c3_error($e->getMessage()); |
||||||
360 | } |
||||||
361 | return __c3_exit(); |
||||||
362 | case 'serialized': |
||||||
363 | try { |
||||||
364 | __c3_send_file($completeReport); |
||||||
365 | } catch (Exception $e) { |
||||||
366 | __c3_error($e->getMessage()); |
||||||
367 | } |
||||||
368 | return __c3_exit(); |
||||||
369 | case 'phpunit': |
||||||
370 | try { |
||||||
371 | __c3_send_file(__c3_build_phpunit_report($codeCoverage, $path)); |
||||||
372 | } catch (Exception $e) { |
||||||
373 | __c3_error($e->getMessage()); |
||||||
374 | } |
||||||
375 | return __c3_exit(); |
||||||
376 | case 'cobertura': |
||||||
377 | try { |
||||||
378 | __c3_send_file(__c3_build_cobertura_report($codeCoverage, $path)); |
||||||
379 | } catch (Exception $e) { |
||||||
380 | __c3_error($e->getMessage()); |
||||||
381 | } |
||||||
382 | return __c3_exit(); |
||||||
383 | } |
||||||
384 | } else { |
||||||
385 | list($codeCoverage, ) = __c3_factory(null); |
||||||
386 | $codeCoverage->start(C3_CODECOVERAGE_TESTNAME); |
||||||
387 | if (!array_key_exists('HTTP_X_CODECEPTION_CODECOVERAGE_DEBUG', $_SERVER)) { |
||||||
388 | register_shutdown_function( |
||||||
389 | function () use ($codeCoverage, $currentReport) { |
||||||
390 | $codeCoverage->stop(); |
||||||
391 | if (!file_exists(dirname($currentReport))) { // verify directory exists |
||||||
392 | if (!mkdir(dirname($currentReport), 0777, true)) { |
||||||
393 | __c3_error("Can't write CodeCoverage report into $currentReport"); |
||||||
394 | } |
||||||
395 | } |
||||||
396 | |||||||
397 | // This will either lock the existing report for writing and return it along with a file pointer, |
||||||
398 | // or return a fresh PHP_CodeCoverage object without a file pointer. We'll merge the current request |
||||||
399 | // into that coverage object, write it to disk, and release the lock. By doing this in the end of |
||||||
400 | // the request, we avoid this scenario, where Request 2 overwrites the changes from Request 1: |
||||||
401 | // |
||||||
402 | // Time -> |
||||||
403 | // Request 1 [ <read> <write> ] |
||||||
404 | // Request 2 [ <read> <write> ] |
||||||
405 | // |
||||||
406 | // In addition, by locking the file for exclusive writing, we make sure no other request try to |
||||||
407 | // read/write to the file at the same time as this request (leading to a corrupt file). flock() is a |
||||||
408 | // blocking call, so it waits until an exclusive lock can be acquired before continuing. |
||||||
409 | |||||||
410 | list($existingCodeCoverage, $file) = __c3_factory($currentReport, true); |
||||||
411 | $existingCodeCoverage->merge($codeCoverage); |
||||||
412 | |||||||
413 | if ($file === null) { |
||||||
414 | file_put_contents($currentReport, serialize($existingCodeCoverage), LOCK_EX); |
||||||
415 | } else { |
||||||
416 | fseek($file, 0); |
||||||
417 | fwrite($file, serialize($existingCodeCoverage)); |
||||||
418 | fflush($file); |
||||||
419 | flock($file, LOCK_UN); |
||||||
420 | fclose($file); |
||||||
421 | } |
||||||
422 | } |
||||||
423 | ); |
||||||
424 | } |
||||||
425 | } |
||||||
426 | |||||||
427 | // @codeCoverageIgnoreEnd |
||||||
428 |