| Current Path : /var/www/html/vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/ |
| Current File : /var/www/html/vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/LocalServer.php |
<?php
namespace Codeception\Coverage\Subscriber;
use Codeception\Configuration;
use Codeception\Coverage\SuiteSubscriber;
use Codeception\Event\StepEvent;
use Codeception\Event\SuiteEvent;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\Exception\ModuleException;
use Codeception\Exception\RemoteException;
use Facebook\WebDriver\Exception\NoSuchAlertException;
/**
* When collecting code coverage data from local server HTTP requests are sent to c3.php file.
* Coverage Collection is started by sending cookies/headers.
* Result is taken from the local file and merged with local code coverage results.
*
* Class LocalServer
* @package Codeception\Coverage\Subscriber
*/
class LocalServer extends SuiteSubscriber
{
// headers
const COVERAGE_HEADER = 'X-Codeception-CodeCoverage';
const COVERAGE_HEADER_ERROR = 'X-Codeception-CodeCoverage-Error';
const COVERAGE_HEADER_CONFIG = 'X-Codeception-CodeCoverage-Config';
const COVERAGE_HEADER_SUITE = 'X-Codeception-CodeCoverage-Suite';
// cookie names
const COVERAGE_COOKIE = 'CODECEPTION_CODECOVERAGE';
const COVERAGE_COOKIE_ERROR = 'CODECEPTION_CODECOVERAGE_ERROR';
protected $suiteName;
protected $c3Access = [
'http' => [
'method' => "GET",
'header' => ''
]
];
/**
* @var \Codeception\Lib\Interfaces\Web
*/
protected $module;
public static $events = [
Events::SUITE_BEFORE => 'beforeSuite',
Events::TEST_BEFORE => 'beforeTest',
Events::STEP_AFTER => 'afterStep',
Events::SUITE_AFTER => 'afterSuite',
];
protected function isEnabled()
{
return $this->module && !$this->settings['remote'] && $this->settings['enabled'];
}
public function beforeSuite(SuiteEvent $e)
{
$this->module = $this->getServerConnectionModule($e->getSuite()->getModules());
$this->applySettings($e->getSettings());
if (!$this->isEnabled()) {
return;
}
$this->suiteName = $e->getSuite()->getBaseName();
if ($this->settings['remote_config']) {
$this->addC3AccessHeader(self::COVERAGE_HEADER_CONFIG, $this->settings['remote_config']);
$knock = $this->c3Request('clear');
if ($knock === false) {
throw new RemoteException(
'
CodeCoverage Error.
Check the file "c3.php" is included in your application.
We tried to access "/c3/report/clear" but this URI was not accessible.
You can review actual error messages in c3tmp dir.
'
);
}
}
}
public function beforeTest(TestEvent $e)
{
if (!$this->isEnabled()) {
return;
}
$this->startCoverageCollection($e->getTest()->getName());
}
public function afterStep(StepEvent $e)
{
if (!$this->isEnabled()) {
return;
}
$this->fetchErrors();
}
public function afterSuite(SuiteEvent $e)
{
if (!$this->isEnabled()) {
return;
}
$coverageFile = Configuration::outputDir() . 'c3tmp/codecoverage.serialized';
$retries = 5;
while (!file_exists($coverageFile) && --$retries >= 0) {
usleep(0.5 * 1000000); // 0.5 sec
}
if (!file_exists($coverageFile)) {
if (file_exists(Configuration::outputDir() . 'c3tmp/error.txt')) {
throw new \RuntimeException(file_get_contents(Configuration::outputDir() . 'c3tmp/error.txt'));
}
return;
}
$contents = file_get_contents($coverageFile);
$coverage = @unserialize($contents);
if ($coverage === false) {
return;
}
$this->preProcessCoverage($coverage)
->mergeToPrint($coverage);
}
/**
* Allows Translating Remote Paths To Local (IE: When Using Docker)
*
* @param \SebastianBergmann\CodeCoverage\CodeCoverage $coverage
* @return $this
*/
protected function preProcessCoverage($coverage)
{
//Only Process If Work Directory Set
if ($this->settings['work_dir'] === null) {
return $this;
}
$workDir = rtrim($this->settings['work_dir'], '/\\') . DIRECTORY_SEPARATOR;
$projectDir = Configuration::projectDir();
$data = $coverage->getData(true); //We only want covered files, not all whitelisted ones.
codecept_debug("Replacing all instances of {$workDir} with {$projectDir}");
foreach ($data as $path => $datum) {
unset($data[$path]);
$path = str_replace($workDir, $projectDir, $path);
$data[$path] = $datum;
}
$coverage->setData($data);
return $this;
}
protected function c3Request($action)
{
$this->addC3AccessHeader(self::COVERAGE_HEADER, 'remote-access');
$context = stream_context_create($this->c3Access);
$c3Url = $this->settings['c3_url'] ? $this->settings['c3_url'] : $this->module->_getUrl();
$contents = file_get_contents($c3Url . '/c3/report/' . $action, false, $context);
$okHeaders = array_filter(
$http_response_header,
function ($h) {
return preg_match('~^HTTP(.*?)\s200~', $h);
}
);
if (empty($okHeaders)) {
throw new RemoteException("Request was not successful. See response header: " . $http_response_header[0]);
}
if ($contents === false) {
$this->getRemoteError($http_response_header);
}
return $contents;
}
protected function startCoverageCollection($testName)
{
$value = [
'CodeCoverage' => $testName,
'CodeCoverage_Suite' => $this->suiteName,
'CodeCoverage_Config' => $this->settings['remote_config']
];
$value = json_encode($value);
if ($this->module instanceof \Codeception\Module\WebDriver) {
$this->module->amOnPage('/');
}
$cookieDomain = isset($this->settings['cookie_domain']) ? $this->settings['cookie_domain'] : null;
if (!$cookieDomain) {
$c3Url = parse_url($this->settings['c3_url'] ? $this->settings['c3_url'] : $this->module->_getUrl());
// we need to separate coverage cookies by host; we can't separate cookies by port.
$cookieDomain = isset($c3Url['host']) ? $c3Url['host'] : 'localhost';
}
$cookieParams = [];
if ($cookieDomain !== 'localhost') {
$cookieParams['domain'] = $cookieDomain;
}
$this->module->setCookie(self::COVERAGE_COOKIE, $value, $cookieParams);
// putting in configuration ensures the cookie is used for all sessions of a MultiSession test
$cookies = $this->module->_getConfig('cookies');
if (!$cookies || !is_array($cookies)) {
$cookies = [];
}
$found = false;
foreach ($cookies as &$cookie) {
if (!is_array($cookie) || !array_key_exists('Name', $cookie) || !array_key_exists('Value', $cookie)) {
// \Codeception\Lib\InnerBrowser will complain about this
continue;
}
if ($cookie['Name'] === self::COVERAGE_COOKIE) {
$found = true;
$cookie['Value'] = $value;
break;
}
}
unset($cookie);
if (!$found) {
$cookies[] = [
'Name' => self::COVERAGE_COOKIE,
'Value' => $value
];
}
$this->module->_setConfig(['cookies' => $cookies]);
}
protected function fetchErrors()
{
// Calling grabCookie() while an alert is present dismisses the alert
// @see https://github.com/Codeception/Codeception/issues/1485
if ($this->module instanceof \Codeception\Module\WebDriver) {
try {
$alert = $this->module->webDriver->switchTo()->alert();
$alert->getText();
// If this succeeds an alert is present, abort
return;
} catch (NoSuchAlertException $e) {
// No alert present, continue
}
}
try {
$error = $this->module->grabCookie(self::COVERAGE_COOKIE_ERROR);
} catch (ModuleException $e) {
// when a new session is started we can't get cookies because there is no
// current page, but there can be no code coverage error either
$error = null;
}
if (!empty($error)) {
$this->module->resetCookie(self::COVERAGE_COOKIE_ERROR);
throw new RemoteException($error);
}
}
protected function getRemoteError($headers)
{
foreach ($headers as $header) {
if (strpos($header, self::COVERAGE_HEADER_ERROR) === 0) {
throw new RemoteException($header);
}
}
}
protected function addC3AccessHeader($header, $value)
{
$headerString = "$header: $value\r\n";
if (strpos($this->c3Access['http']['header'], $headerString) === false) {
$this->c3Access['http']['header'] .= $headerString;
}
}
protected function applySettings($settings)
{
parent::applySettings($settings);
if (isset($settings['coverage']['remote_context_options'])) {
$this->c3Access = array_replace_recursive($this->c3Access, $settings['coverage']['remote_context_options']);
}
}
}