|
| 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 |
0 commit comments