[ '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->mergeToPrint($coverage); } 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('/'); } $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. $c3Host = isset($c3Url['host']) ? $c3Url['host'] : 'localhost'; $this->module->setCookie(self::COVERAGE_COOKIE, $value, ['domain' => $c3Host]); // 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; } } if (!$found) { $cookies[] = [ 'Name' => self::COVERAGE_COOKIE, 'Value' => $value ]; } $this->module->_setConfig(['cookies' => $cookies]); } protected function fetchErrors() { 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']); } } }