| 1 | <?php |
||
| 2 | /** |
||
| 3 | * Behat Code Coverage |
||
| 4 | */ |
||
| 5 | declare(strict_types=1); |
||
| 6 | |||
| 7 | namespace DVDoug\Behat\CodeCoverage; |
||
| 8 | |||
| 9 | use Behat\Testwork\Cli\Controller; |
||
| 10 | use Behat\Testwork\Cli\ServiceContainer\CliExtension; |
||
| 11 | use Behat\Testwork\EventDispatcher\ServiceContainer\EventDispatcherExtension; |
||
| 12 | use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface; |
||
| 13 | use Behat\Testwork\ServiceContainer\ExtensionManager; |
||
| 14 | use DVDoug\Behat\CodeCoverage\Subscriber\EventSubscriber; |
||
| 15 | use SebastianBergmann\CodeCoverage\CodeCoverage; |
||
| 16 | use SebastianBergmann\CodeCoverage\Driver\Selector; |
||
| 17 | use SebastianBergmann\CodeCoverage\Driver\XdebugNotAvailableException; |
||
| 18 | use SebastianBergmann\CodeCoverage\Driver\XdebugNotEnabledException; |
||
| 19 | use SebastianBergmann\CodeCoverage\Filter; |
||
| 20 | use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException; |
||
| 21 | use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException; |
||
| 22 | use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; |
||
| 23 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; |
||
| 24 | use Symfony\Component\Config\FileLocator; |
||
| 25 | use Symfony\Component\Console\Input\InputInterface; |
||
| 26 | use Symfony\Component\Console\Output\ConsoleOutputInterface; |
||
| 27 | use Symfony\Component\Console\Output\OutputInterface; |
||
| 28 | use Symfony\Component\DependencyInjection\ContainerBuilder; |
||
| 29 | use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; |
||
| 30 | use Symfony\Component\DependencyInjection\Reference; |
||
| 31 | |||
| 32 | use function sprintf; |
||
| 33 | use function sys_get_temp_dir; |
||
| 34 | use function realpath; |
||
| 35 | |||
| 36 | class Extension implements ExtensionInterface |
||
| 37 | { |
||
| 38 | 37 | public function initialize(ExtensionManager $extensionManager): void |
|
| 39 | { |
||
| 40 | 37 | } |
|
| 41 | |||
| 42 | 37 | public function load(ContainerBuilder $container, array $config): void |
|
| 43 | { |
||
| 44 | 37 | $container->registerForAutoconfiguration(Controller::class)->addTag(CliExtension::CONTROLLER_TAG); |
|
| 45 | 37 | $container->registerForAutoconfiguration(EventSubscriber::class)->addTag(EventDispatcherExtension::SUBSCRIBER_TAG); |
|
| 46 | |||
| 47 | 37 | $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../config')); |
|
| 48 | 37 | $loader->load('services.php'); |
|
| 49 | |||
| 50 | 37 | $container->setParameter('behat.code_coverage.config.filter', $config['filter']); |
|
| 51 | 37 | $container->setParameter('behat.code_coverage.config.branchAndPathCoverage', $config['branchAndPathCoverage']); |
|
| 52 | 37 | $container->setParameter('behat.code_coverage.config.reports', $config['reports'] ?? []); |
|
| 53 | 37 | $container->setParameter('behat.code_coverage.config.cache', $config['cache']); |
|
| 54 | } |
||
| 55 | |||
| 56 | 74 | public function configure(ArrayNodeDefinition $builder): void |
|
| 57 | { |
||
| 58 | 74 | $builder |
|
| 59 | 74 | ->children() |
|
| 60 | 74 | ->scalarNode('cache') |
|
| 61 | 74 | ->defaultValue(sys_get_temp_dir() . '/behat-code-coverage-cache') |
|
| 62 | 74 | ->end() |
|
| 63 | 74 | ->booleanNode('branchAndPathCoverage') |
|
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 64 | 74 | ->defaultNull() // use null to mean auto |
|
| 65 | 74 | ->end() |
|
| 66 | 74 | ->arrayNode('filter') |
|
| 67 | 74 | ->addDefaultsIfNotSet() |
|
| 68 | 74 | ->children() |
|
| 69 | 74 | ->scalarNode('includeUncoveredFiles') |
|
| 70 | 74 | ->defaultTrue() |
|
| 71 | 74 | ->end() |
|
| 72 | 74 | ->scalarNode('processUncoveredFiles') |
|
| 73 | 74 | ->defaultFalse() |
|
| 74 | 74 | ->setDeprecated('dvdoug/behat-code-coverage', '5.3', 'the processUncoveredFiles setting is deprecated, it has been removed from php-code-coverage v10') |
|
| 75 | 74 | ->end() |
|
| 76 | 74 | ->arrayNode('include') |
|
| 77 | 74 | ->addDefaultsIfNotSet() |
|
| 78 | 74 | ->children() |
|
| 79 | 74 | ->arrayNode('directories') |
|
| 80 | 74 | ->useAttributeAsKey('name') |
|
| 81 | 74 | ->normalizeKeys(false) |
|
| 82 | 74 | ->prototype('array') |
|
| 83 | 74 | ->children() |
|
| 84 | 74 | ->scalarNode('prefix')->defaultValue('')->end() |
|
| 85 | 74 | ->scalarNode('suffix')->defaultValue('.php')->end() |
|
| 86 | 74 | ->end() |
|
| 87 | 74 | ->end() |
|
| 88 | 74 | ->end() |
|
| 89 | 74 | ->arrayNode('files') |
|
| 90 | 74 | ->prototype('scalar')->end() |
|
| 91 | 74 | ->end() |
|
| 92 | 74 | ->end() |
|
| 93 | 74 | ->end() |
|
| 94 | 74 | ->arrayNode('exclude') |
|
| 95 | 74 | ->addDefaultsIfNotSet() |
|
| 96 | 74 | ->children() |
|
| 97 | 74 | ->arrayNode('directories') |
|
| 98 | 74 | ->useAttributeAsKey('name') |
|
| 99 | 74 | ->normalizeKeys(false) |
|
| 100 | 74 | ->prototype('array') |
|
| 101 | 74 | ->children() |
|
| 102 | 74 | ->scalarNode('prefix')->defaultValue('')->end() |
|
| 103 | 74 | ->scalarNode('suffix')->defaultValue('.php')->end() |
|
| 104 | 74 | ->end() |
|
| 105 | 74 | ->end() |
|
| 106 | 74 | ->end() |
|
| 107 | 74 | ->arrayNode('files') |
|
| 108 | 74 | ->prototype('scalar')->end() |
|
| 109 | 74 | ->end() |
|
| 110 | 74 | ->end() |
|
| 111 | 74 | ->end() |
|
| 112 | 74 | ->end() |
|
| 113 | 74 | ->end() |
|
| 114 | 74 | ->arrayNode('reports') |
|
| 115 | 74 | ->children() |
|
| 116 | 74 | ->arrayNode('cobertura') |
|
| 117 | 74 | ->children() |
|
| 118 | 74 | ->scalarNode('name')->defaultNull()->end() |
|
| 119 | 74 | ->scalarNode('target')->isRequired()->cannotBeEmpty()->end() |
|
| 120 | 74 | ->end() |
|
| 121 | 74 | ->end() |
|
| 122 | 74 | ->arrayNode('clover') |
|
| 123 | 74 | ->children() |
|
| 124 | 74 | ->scalarNode('name')->defaultNull()->end() |
|
| 125 | 74 | ->scalarNode('target')->isRequired()->cannotBeEmpty()->end() |
|
| 126 | 74 | ->end() |
|
| 127 | 74 | ->end() |
|
| 128 | 74 | ->arrayNode('crap4j') |
|
| 129 | 74 | ->children() |
|
| 130 | 74 | ->scalarNode('name')->defaultNull()->end() |
|
| 131 | 74 | ->scalarNode('target')->isRequired()->cannotBeEmpty()->end() |
|
| 132 | 74 | ->end() |
|
| 133 | 74 | ->end() |
|
| 134 | 74 | ->arrayNode('html') |
|
| 135 | 74 | ->children() |
|
| 136 | 74 | ->scalarNode('target')->isRequired()->cannotBeEmpty()->end() |
|
| 137 | 74 | ->scalarNode('lowUpperBound')->defaultValue(50)->end() |
|
| 138 | 74 | ->scalarNode('highLowerBound')->defaultValue(90)->end() |
|
| 139 | 74 | ->arrayNode('colors') |
|
| 140 | 74 | ->addDefaultsIfNotSet() |
|
| 141 | 74 | ->children() |
|
| 142 | 74 | ->scalarNode('successLow')->defaultValue('#dff0d8')->end() |
|
| 143 | 74 | ->scalarNode('successMedium')->defaultValue('#c3e3b5')->end() |
|
| 144 | 74 | ->scalarNode('successHigh')->defaultValue('#99cb84')->end() |
|
| 145 | 74 | ->scalarNode('warning')->defaultValue('#fcf8e3')->end() |
|
| 146 | 74 | ->scalarNode('danger')->defaultValue('#f2dede')->end() |
|
| 147 | 74 | ->end() |
|
| 148 | 74 | ->end() |
|
| 149 | 74 | ->scalarNode('customCSSFile')->defaultNull()->end() |
|
| 150 | 74 | ->end() |
|
| 151 | 74 | ->end() |
|
| 152 | 74 | ->arrayNode('php') |
|
| 153 | 74 | ->children() |
|
| 154 | 74 | ->scalarNode('target')->isRequired()->cannotBeEmpty()->end() |
|
| 155 | 74 | ->end() |
|
| 156 | 74 | ->end() |
|
| 157 | 74 | ->arrayNode('text') |
|
| 158 | 74 | ->children() |
|
| 159 | 74 | ->booleanNode('showColors')->defaultValue(false)->end() |
|
| 160 | 74 | ->scalarNode('lowUpperBound')->defaultValue(50)->end() |
|
| 161 | 74 | ->scalarNode('highLowerBound')->defaultValue(90)->end() |
|
| 162 | 74 | ->booleanNode('showOnlySummary')->defaultValue(false)->end() |
|
| 163 | 74 | ->booleanNode('showUncoveredFiles')->defaultValue(false)->end() |
|
| 164 | 74 | ->end() |
|
| 165 | 74 | ->end() |
|
| 166 | 74 | ->arrayNode('xml') |
|
| 167 | 74 | ->children() |
|
| 168 | 74 | ->scalarNode('target')->isRequired()->cannotBeEmpty()->end() |
|
| 169 | 74 | ->end() |
|
| 170 | 74 | ->end() |
|
| 171 | 74 | ->end() |
|
| 172 | 74 | ->end() |
|
| 173 | 74 | ->end() |
|
| 174 | 74 | ->end(); |
|
| 175 | } |
||
| 176 | |||
| 177 | 74 | public function getConfigKey() |
|
| 178 | { |
||
| 179 | 74 | return 'code_coverage'; |
|
| 180 | } |
||
| 181 | |||
| 182 | 148 | public function process(ContainerBuilder $container): void |
|
| 183 | { |
||
| 184 | /** @var InputInterface $input */ |
||
| 185 | 148 | $input = $container->get(CliExtension::INPUT_ID); |
|
| 186 | |||
| 187 | /** @var OutputInterface $output */ |
||
| 188 | 148 | $output = $container->get(CliExtension::OUTPUT_ID); |
|
| 189 | |||
| 190 | 148 | if ($output instanceof ConsoleOutputInterface) { |
|
| 191 | $output = $output->getErrorOutput(); |
||
| 192 | } |
||
| 193 | |||
| 194 | 148 | $filterConfig = $container->getParameter('behat.code_coverage.config.filter'); |
|
| 195 | 148 | $branchPathConfig = $container->getParameter('behat.code_coverage.config.branchAndPathCoverage'); |
|
| 196 | 148 | $cacheDir = $container->getParameter('behat.code_coverage.config.cache'); |
|
| 197 | |||
| 198 | 148 | $canCollectCodeCoverage = !$input->hasParameterOption('--no-coverage'); |
|
| 199 | 148 | if ($canCollectCodeCoverage) { |
|
| 200 | try { |
||
| 201 | 111 | $this->initCodeCoverage(new Filter(), $filterConfig, $branchPathConfig, $cacheDir, $output); |
|
| 202 | |||
| 203 | 74 | $codeCoverageDefinition = $container->getDefinition(CodeCoverage::class); |
|
| 204 | 74 | $filterDefinition = $container->getDefinition(Filter::class); |
|
| 205 | 74 | $codeCoverageDefinition->setFactory([new Reference(self::class), 'initCodeCoverage']); |
|
| 206 | 74 | $codeCoverageDefinition->setArguments([$filterDefinition, $filterConfig, $branchPathConfig, $cacheDir, $output]); |
|
| 207 | 37 | } catch (NoCodeCoverageDriverAvailableException|XdebugNotEnabledException|XdebugNotAvailableException $e) { |
|
| 208 | 37 | $output->writeln("<comment>{$e->getMessage()}</comment>"); |
|
| 209 | 37 | $canCollectCodeCoverage = false; |
|
| 210 | } |
||
| 211 | } |
||
| 212 | |||
| 213 | 148 | if (!$canCollectCodeCoverage) { |
|
| 214 | 74 | $container->getDefinition(EventSubscriber::class)->setArgument('$coverage', null); |
|
| 215 | } |
||
| 216 | } |
||
| 217 | |||
| 218 | 74 | public function initCodeCoverage(Filter $filter, array $filterConfig, ?bool $branchPathConfig, string $cacheDir, OutputInterface $output): CodeCoverage |
|
| 219 | { |
||
| 220 | // set up filter |
||
| 221 | 74 | $files = []; |
|
| 222 | |||
| 223 | 74 | foreach ($filterConfig['include']['directories'] as $directoryToInclude => $details) { |
|
| 224 | 74 | foreach ((new FileIteratorFacade())->getFilesAsArray($directoryToInclude, $details['suffix'], $details['prefix']) as $fileToInclude) { |
|
| 225 | $files[realpath($fileToInclude)] = realpath($fileToInclude); |
||
| 226 | } |
||
| 227 | } |
||
| 228 | |||
| 229 | 74 | foreach ($filterConfig['include']['files'] as $fileToInclude) { |
|
| 230 | 74 | $files[$fileToInclude] = $fileToInclude; |
|
| 231 | } |
||
| 232 | |||
| 233 | 74 | foreach ($filterConfig['exclude']['directories'] as $directoryToExclude => $details) { |
|
| 234 | 74 | foreach ((new FileIteratorFacade())->getFilesAsArray($directoryToExclude, $details['suffix'], $details['prefix']) as $fileToExclude) { |
|
| 235 | unset($files[$fileToExclude]); |
||
| 236 | } |
||
| 237 | } |
||
| 238 | |||
| 239 | 74 | foreach ($filterConfig['exclude']['files'] as $fileToExclude) { |
|
| 240 | 74 | unset($files[realpath($fileToExclude)]); |
|
| 241 | } |
||
| 242 | |||
| 243 | 74 | foreach ($files as $file) { |
|
| 244 | 74 | $filter->includeFile($file); |
|
| 245 | } |
||
| 246 | |||
| 247 | // see if we can get a driver |
||
| 248 | 74 | $selector = new Selector(); |
|
| 249 | 74 | $driver = $selector->forLineCoverage($filter); |
|
| 250 | 74 | if ($branchPathConfig !== false) { |
|
| 251 | try { |
||
| 252 | 37 | $driver = $selector->forLineAndPathCoverage($filter); |
|
| 253 | 19 | } catch (NoCodeCoverageDriverWithPathCoverageSupportAvailableException|XdebugNotAvailableException|XdebugNotEnabledException $e) { |
|
| 254 | // fallback driver is already set |
||
| 255 | 19 | if ($branchPathConfig === true) { // only warn if explicitly enabled |
|
|
0 ignored issues
–
show
|
|||
| 256 | 19 | $output->writeln(sprintf('<info>%s does not support collecting branch and path data</info>', $driver->nameAndVersion())); |
|
| 257 | } |
||
| 258 | } |
||
| 259 | } |
||
| 260 | |||
| 261 | // and init coverage |
||
| 262 | 74 | $codeCoverage = new CodeCoverage($driver, $filter); |
|
| 263 | 74 | $codeCoverage->cacheStaticAnalysis($cacheDir); |
|
| 264 | |||
| 265 | 74 | if ($filterConfig['includeUncoveredFiles']) { |
|
| 266 | 37 | $codeCoverage->includeUncoveredFiles(); |
|
| 267 | } else { |
||
| 268 | 37 | $codeCoverage->excludeUncoveredFiles(); |
|
| 269 | } |
||
| 270 | |||
| 271 | 74 | return $codeCoverage; |
|
| 272 | } |
||
| 273 | } |
||
| 274 |