Codeception / c3

Remote CodeCoverage for Codeception. Part of Codeception testing framework.
http://codeception.com
71 stars 46 forks source link

Fatal error from c3.php file #67

Closed Pi-George closed 3 years ago

Pi-George commented 3 years ago

Stepping through my codeception Actor, my code uses amOnPage() to try and load the login page, then grabResponse() and I get this:

<br />
<b>Fatal error</b>:  Uncaught Error: Call to undefined function Codeception\codecept_is_path_absolute() in /srv/www/vendor/codeception/codeception/src/Codeception/Configuration.php:529
Stack trace:
#0 /srv/www/vendor/codeception/codeception/src/Codeception/Configuration.php(557): Codeception\Configuration::outputDir()
#1 /srv/www/c3.php(122): Codeception\Configuration::logDir()
#2 /srv/www/app-loader.php(46): include('...')
#3 /srv/www/public/index.php(2): require_once('...')
#4 {main}
  thrown in <b>/srv/www/vendor/codeception/codeception/src/Codeception/Configuration.php</b> on line <b>529</b><br />

It's been an issue since we upgraded to XDebug 3 and PHP 8 and only happens if running codeception with code coverage enabled

It appears to be an autoload issue of some kind. Here's my app-loader.php code (up until where it fails)


<?php // phpcs:ignore

use Bugsnag\Client as BugsnagClient;
use Pi\Core\Bootstrap\Autoloader;
use Pi\Core\Bootstrap\ConfigBuilder;
use Pi\Core\Bootstrap\ContainerBootstrap;
use Pi\Core\Bootstrap\EntryPointResolver;
use Pi\Core\Bootstrap\SessionInitialiser;
use Pi\Core\Bootstrap\ZendApplicationBootstrapProxy;
use Pi\Core\ErrorHandling\ErrorHandler;

/**
 * Need to set this here as some of the logic which executes before bootstrap requires a date
 */
date_default_timezone_set('UTC');

/**
 * Base directory of entire application
 */
define('APPLICATION_ROOT', dirname(__FILE__));

/**
 * Location of the "application" directory - required by Zend
 */
define('APPLICATION_PATH', APPLICATION_ROOT . '/application');

/**
 * Defines whether the app was invoked through the web or command line
 */
define('INVOCATION_MODE', PHP_SAPI === 'cli' ? 'cli' : 'http');

require_once APPLICATION_ROOT . '/vendor/autoload.php';
require_once APPLICATION_PATH . '/configs/environment.php';

// We need our own autoloader to handle our non-psr-4 module structure
// It may make sense to load this in composer.json instead
require APPLICATION_PATH . '/modules/core/code/Bootstrap/Autoloader.php';
spl_autoload_register([new Autoloader(), 'autoload']);

$entryPoint = (new EntryPointResolver())->determineEntryPoint();

$config = (new ConfigBuilder())->initConfig($entryPoint);

if ($config->codeception->codecoverage === '1') {
    define('C3_CODECOVERAGE_ERROR_LOG_FILE', APPLICATION_ROOT . '/var/log/c3_error.log');
    include APPLICATION_ROOT . '/c3.php';
    define('MY_APP_STARTED', true);
}
Naktibalda commented 3 years ago

It seems that this issue existed since Codeception 2.4.4, three years ago, but it only happens if there is an extends section in codeception.yml or suite.yml file - https://github.com/Codeception/Codeception/blob/4.1/src/Codeception/Configuration.php#L192-L199 and vendor/autoload.php is included before c3.php, so it must be a rare issue.

The problem is that codecept_ functions are defined in autoload.php file in Codeception 2 - 4, and that file is not included by Composer, but by codecept entry point when Codeception is executed, and by c3.php if vendor/autoload.php wasn't included before.

https://github.com/Codeception/c3/blob/2.6.2/c3.php#L54-L62

This issue won't exist in Codeception 5 because function file will be included by composer.

A quick fix for your issue is to add require_once APPLICATION_ROOT . '/vendor/codeception/codeception/autoload.php'; beforeinclude APPLICATION_ROOT . '/c3.php';`

Naktibalda commented 3 years ago

@Pi-George Could you try to use this c3.php file instead and let me know if it works? https://raw.githubusercontent.com/Codeception/c3/issue-67/c3.php

Pi-George commented 3 years ago

I updated to the latest c3 file after your recent merge and new hotfix release. Unfortunately I now just get a completely different error:


<br />
<b>Fatal error</b>:  Uncaught SebastianBergmann\CodeCoverage\Driver\Xdebug3NotEnabledException: XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set in /srv/www/vendor/phpunit/php-code-coverage/src/Driver/Xdebug3Driver.php:65
Stack trace:
#0 /srv/www/vendor/phpunit/php-code-coverage/src/Driver/Selector.php(43): SebastianBergmann\CodeCoverage\Driver\Xdebug3Driver-&gt;__construct()
#1 /srv/www/vendor/phpunit/php-code-coverage/src/Driver/Driver.php(82): SebastianBergmann\CodeCoverage\Driver\Selector-&gt;forLineCoverage()
#2 /srv/www/c3.php(249): SebastianBergmann\CodeCoverage\Driver\Driver::forLineCoverage()
#3 /srv/www/c3.php(362): __c3_factory()
#4 /srv/www/app-loader.php(46): include('...')
#5 /srv/www/public/index.php(2): require_once('...')
#6 {main}
  thrown in <b>/srv/www/vendor/phpunit/php-code-coverage/src/Driver/Xdebug3Driver.php</b> on line <b>65</b><br />

XDebug mode is set to coverage (in my cli, using $XDEBUG_MODE=coverage,debug). I'm unsure if this is a C3 issue where it isn't setting the xdebug mode correctly or if it's completely unrelated to this project.

Naktibalda commented 3 years ago

Xdebug configuration must be set in php.ini file of the website you are testing : https://xdebug.org/docs/code_coverage#mode

Pi-George commented 3 years ago

I just tried setting the mode to coverage,debug in the php.ini and I got the same error. I've confirmed with php -i | grep xdebug.mode that I didn't mess up the config somehow

Naktibalda commented 3 years ago

You confirmed that it is enable in cli configuration, your web server could be using different php.ini file, unless you are using PHP builtin server with php -S

Pi-George commented 3 years ago

You're right, for some reason I had it in my head that I didn't need to restart PHP FPM for ini settings to take effect. A look at phpinfo() showed me that I was mistaken.

I'm now dealing with my app disliking headers that are sent while codeception is trying to gather the coverage. This one is an issue within our software though I'm pretty sure, just need it to ignore the fact the header doesn't have a string or numeric value.

An error has occured<br />
<b>Fatal error</b>:  Uncaught Laminas\Diactoros\Exception\InvalidArgumentException: Invalid header value type; must be a string or numeric; received NULL in /srv/www/vendor/laminas/laminas-diactoros/src/HeaderSecurity.php:140
Stack trace:
#0 /srv/www/vendor/laminas/laminas-diactoros/src/MessageTrait.php(399): Laminas\Diactoros\HeaderSecurity::assertValid()
#1 [internal function]: Laminas\Diactoros\ServerRequest-&gt;Laminas\Diactoros\{closure}()
#2 /srv/www/vendor/laminas/laminas-diactoros/src/MessageTrait.php(402): array_map()
#3 /srv/www/vendor/laminas/laminas-diactoros/src/MessageTrait.php(339): Laminas\Diactoros\ServerRequest-&gt;filterHeaderValue()
#4 /srv/www/vendor/laminas/laminas-diactoros/src/RequestTrait.php(81): Laminas\Diactoros\ServerRequest-&gt;setHeaders()
#5 /srv/www/vendor/laminas/laminas-diactoros/src/ServerRequest.php(100): Laminas\Diactoros\ServerRequest-&gt;initialize()
#6 /srv/www/vendor/laminas/laminas-diactoros/src/ServerRequestFactory.php(81): Laminas\Diactoros\ServerRequest-&gt;__construct()
#7 /srv/www/application/modules/core/configs/di-config.php(129): Laminas\Diactoros\ServerRequestFactory::fromGlobals()
#8 [internal function]: DI\Definition\Source\DefinitionFile-&gt;{closure}()
#9 /srv/www/vendor/php-di/invoker/src/Invoker.php(74): call_user_func_array()
#10 /srv/www/vendor/php-di/php-di/src/Definition/Resolver/FactoryResolver.php(80): Invoker\Invoker-&gt;call()
#11 /srv/www/vendor/php-di/php-di/src/Definition/Resolver/ResolverDispatcher.php(71): DI\Definition\Resolver\FactoryResolver-&gt;resolve()
#12 /srv/www/vendor/php-di/php-di/src/Container.php(387): DI\Definition\Resolver\ResolverDispatcher-&gt;resolve()
#13 /srv/www/vendor/php-di/php-di/src/Container.php(138): DI\Container-&gt;resolveDefinition()
#14 /srv/www/application/modules/navigation/configs/di-config.php(14): DI\Container-&gt;get()
#15 [internal function]: DI\Definition\Source\DefinitionFile-&gt;{closure}()
#16 /srv/www/vendor/php-di/invoker/src/Invoker.php(74): call_user_func_array()
#17 /srv/www/vendor/php-di/php-di/src/Definition/Resolver/FactoryResolver.php(80): Invoker\Invoker-&gt;call()
#18 /srv/www/vendor/php-di/php-di/src/Definition/Resolver/ResolverDispatcher.php(71): DI\Definition\Resolver\FactoryResolver-&gt;resolve()
#19 /srv/www/vendor/php-di/php-di/src/Container.php(387): DI\Definition\Resolver\ResolverDispatcher-&gt;resolve()
#20 /srv/www/vendor/php-di/php-di/src/Container.php(138): DI\Container-&gt;resolveDefinition()
#21 /srv/www/vendor/php-di/php-di/src/Definition/Reference.php(53): DI\Container-&gt;get()
#22 /srv/www/vendor/php-di/php-di/src/Definition/Resolver/ResolverDispatcher.php(66): DI\Definition\Reference-&gt;resolve()
#23 /srv/www/vendor/php-di/php-di/src/Definition/Resolver/ParameterResolver.php(78): DI\Definition\Resolver\ResolverDispatcher-&gt;resolve()
#24 /srv/www/vendor/php-di/php-di/src/Definition/Resolver/ObjectCreator.php(137): DI\Definition\Resolver\ParameterResolver-&gt;resolveParameters()
#25 /srv/www/vendor/php-di/php-di/src/Definition/Resolver/ObjectCreator.php(71): DI\Definition\Resolver\ObjectCreator-&gt;createInstance()
#26 /srv/www/vendor/php-di/php-di/src/Definition/Resolver/ResolverDispatcher.php(71): DI\Definition\Resolver\ObjectCreator-&gt;resolve()
#27 /srv/www/vendor/php-di/php-di/src/Container.php(387): DI\Definition\Resolver\ResolverDispatcher-&gt;resolve()
#28 /srv/www/vendor/php-di/php-di/src/Container.php(197): DI\Container-&gt;resolveDefinition()
#29 /srv/www/application/modules/core/code/Bootstrap/PluginFactory.php(18): DI\Container-&gt;make()
#30 /srv/www/application/modules/core/code/Bootstrap/ZendApplicationBootstrap.php(172): Pi\Core\Bootstrap\PluginFactory-&gt;createPlugin()
#31 /srv/www/application/modules/core/code/Bootstrap/ZendApplicationBootstrap.php(54): Pi\Core\Bootstrap\ZendApplicationBootstrap-&gt;initPlugins()
#32 /srv/www/application/modules/core/code/Bootstrap/ZendApplicationBootstrapProxy.php(25): Pi\Core\Bootstrap\ZendApplicationBootstrap-&gt;bootstrap()
#33 /srv/www/vendor/shardj/zf1-future/library/Zend/Application/Bootstrap/BootstrapAbstract.php(681): Pi\Core\Bootstrap\ZendApplicationBootstrapProxy-&gt;_initApp()
#34 /srv/www/vendor/shardj/zf1-future/library/Zend/Application/Bootstrap/BootstrapAbstract.php(634): Zend_Application_Bootstrap_BootstrapAbstract-&gt;_executeResource()
#35 /srv/www/vendor/shardj/zf1-future/library/Zend/Application/Bootstrap/BootstrapAbstract.php(598): Zend_Application_Bootstrap_BootstrapAbstract-&gt;_bootstrap()
#36 /srv/www/vendor/shardj/zf1-future/library/Zend/Application.php(373): Zend_Application_Bootstrap_BootstrapAbstract-&gt;bootstrap()
#37 /srv/www/app-loader.php(63): Zend_Application-&gt;bootstrap()
#38 /srv/www/public/index.php(2): require_once('...')
#39 {main}
  thrown in <b>/srv/www/vendor/laminas/laminas-diactoros/src/HeaderSecurity.php</b> on line <b>140</b><br />
Pi-George commented 3 years ago
array (
  'cookie' => 'CODECEPTION_CODECOVERAGE={"CodeCoverage":"pageSmokeTester","CodeCoverage_Suite":"smoke","CodeCoverage_Config":null}',
  'user-agent' => 'Symfony BrowserKit',
  'host' => 'localhost',
  'x-codeception-codecoverage' => 'pageSmokeTester',
  'x-codeception-codecoverage-suite' => 'smoke',
  'x-codeception-codecoverage-config' => NULL,
)

Seems it really doesn't like x-codeception-codecoverage-config having a NULL value. Is there an easy way to give it a value?

Pi-George commented 3 years ago

I can't find anything in the documentation on the coverage -> remote_config setting. But I can see that it default to null. Trying random values of my own doesn't seem to help at all as it just gives me slightly different errors, either from disallowed header formats or from codeception complaining about the values.

Naktibalda commented 3 years ago

It is odd that you get that header sent, Codeception only sets it if remote_config setting is not empty:

https://github.com/Codeception/Codeception/blob/4.1/src/Codeception/Coverage/Subscriber/LocalServer.php#L68-L69

Pi-George commented 3 years ago

Yeah, I've gone through that function with XDebug and it isn't even going into that if statement, it correctly skips lines 69-80. I think it's LN 188 in LocalServer.php which causes this, and then somewhere along the line the cookie values are somehow being changed into headers.

Naktibalda commented 3 years ago

You are right, that code is in c3.php https://github.com/Codeception/c3/blob/2.0/c3.php#L16-L30

Pi-George commented 3 years ago

Can we add an if statement to line 27, so it only adds the header if is_string($value) && is_numeric($value), or something along those lines

Naktibalda commented 3 years ago

if (!empty($value))

Pi-George commented 3 years ago

Sure that works too, thanks for all of your help by the way