/src/Codeception/Coverage/Subscriber/LocalServer.php
PHP | 296 lines | 237 code | 31 blank | 28 comment | 27 complexity | 1dccbba2066d4a17195905b0d5e4b286 MD5 | raw file
- <?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']);
- }
- }
- }