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') |
|
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
introduced
by
![]() |
|||
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 |