Passed
Pull Request — master (#17)
by
unknown
11:35
created

checkMinimumCoveragePercent()   B

Complexity

Conditions 9
Paths 5

Size

Total Lines 49
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 9
eloc 32
nc 5
nop 0
dl 0
loc 49
ccs 0
cts 0
cp 0
crap 90
rs 8.0555
c 2
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of the friends-of-phpspec/phpspec-code-coverage package.
5
 *
6
 * @author  ek9 <[email protected]>
7
 * @license MIT
8
 *
9
 * For the full copyright and license information, please see the LICENSE file
10
 * that was distributed with this source code.
11
 */
12
13
declare(strict_types=1);
14
15
namespace FriendsOfPhpSpec\PhpSpec\CodeCoverage\Listener;
16
17
use Exception;
18
use PhpSpec\Console\ConsoleIO;
19
use PhpSpec\Event\ExampleEvent;
20
use PhpSpec\Event\SuiteEvent;
21
use SebastianBergmann\CodeCoverage\CodeCoverage;
22
use SebastianBergmann\CodeCoverage\Report;
23
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
24
25
/**
26
 * @author Henrik Bjornskov
27
 */
28
class CodeCoverageListener implements EventSubscriberInterface
29
{
30
    private $coverage;
31
32
    private $io;
33
34
    private $options;
35
36
    private $reports;
37
38
    private $skipCoverage;
39
40
    public function __construct(ConsoleIO $io, CodeCoverage $coverage, array $reports, bool $skipCoverage = false)
41
    {
42
        $this->io = $io;
43
        $this->coverage = $coverage;
44
        $this->reports = $reports;
45
        $this->options = [
46
            'whitelist' => ['src', 'lib'],
47
            'blacklist' => ['test', 'vendor', 'spec'],
48
            'whitelist_files' => [],
49
            'blacklist_files' => [],
50
            'output' => ['html' => 'coverage'],
51
            'format' => ['html'],
52
        ];
53
54
        $this->skipCoverage = $skipCoverage;
55
    }
56 5
57
    public function afterExample(ExampleEvent $event): void
58 5
    {
59
        if ($this->skipCoverage) {
60
            return;
61
        }
62 5
63
        $this->coverage->stop();
64
    }
65
66
    public function afterSuite(SuiteEvent $event): void
67
    {
68
        if ($this->skipCoverage) {
69
            if ($this->io && $this->io->isVerbose()) {
70
                $this->io->writeln('Skipping code coverage generation');
71
            }
72
73
            return;
74
        }
75
76
        if ($this->io && $this->io->isVerbose()) {
77
            $this->io->writeln();
78
        }
79
80
        foreach ($this->reports as $format => $report) {
81
            if ($this->io && $this->io->isVerbose()) {
82
                $this->io->writeln(sprintf('Generating code coverage report in %s format ...', $format));
83
            }
84
85
            if ($report instanceof Report\Text) {
86
                $this->io->writeln(
87
                    $report->process($this->coverage, $this->io->isDecorated())
88
                );
89
            } else {
90
                $report->process($this->coverage, $this->options['output'][$format]);
91
            }
92
        }
93
94 5
        $this->checkMinimumCoveragePercent();
95
    }
96
97
    public function beforeExample(ExampleEvent $event): void
98
    {
99
        if ($this->skipCoverage) {
100
            return;
101
        }
102
103
        $example = $event->getExample();
104
105
        $name = null;
106
107
        if (null !== $spec = $example->getSpecification()) {
108
            $name = $spec->getClassReflection()->getName();
109
        }
110
111
        $name = strtr('%spec%::%example%', [
112
            '%spec%' => $name,
113
            '%example%' => $example->getFunctionReflection()->getName(),
114 5
        ]);
115
116
        $this->coverage->start($name);
117
    }
118
119
    /**
120
     * Note: We use array_map() instead of array_walk() because the latter expects
121
     * the callback to take the value as the first and the index as the seconds parameter.
122
     *
123
     * @param SuiteEvent $event
124
     */
125
    public function beforeSuite(SuiteEvent $event): void
126
    {
127
        if ($this->skipCoverage) {
128
            return;
129
        }
130
131
        $filter = $this->coverage->filter();
132
133
        foreach ($this->options['whitelist'] as $option) {
134
            $filter->addDirectoryToWhitelist($option);
135
        }
136
137
        foreach ($this->options['blacklist'] as $option) {
138
            $filter->removeDirectoryFromWhitelist($option);
139
        }
140
141
        foreach ($this->options['whitelist_files'] as $option) {
142
            $filter->addFilesToWhitelist($option);
143
        }
144
145
        foreach ($this->options['blacklist_files'] as $option) {
146
            $filter->removeFileFromWhitelist($option);
147
        }
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153
    public static function getSubscribedEvents(): array
154
    {
155
        return [
156
            'beforeExample' => ['beforeExample', -10],
157
            'afterExample' => ['afterExample', -10],
158
            'beforeSuite' => ['beforeSuite', -10],
159
            'afterSuite' => ['afterSuite', -10],
160
        ];
161
    }
162
163
    public function setOptions(array $options): void
164
    {
165
        $this->options = $options + $this->options;
166
    }
167
168
    /**
169
     * @throws Exception
170
     */
171
    private function checkMinimumCoveragePercent(): void
172
    {
173
        $report = $this->coverage->getReport();
174
175
        $coverageMessage = <<<'COVMSG'
176
%s coverage is too low as specified by minimum configuration: %.2f%% expected, %.2f covered.
177
COVMSG;
178
179
        $lineMinimum = $this->options['minimum']['lines'] ?? false;
180
181
        if (false !== $lineMinimum && (string) $lineMinimum > $report->getLineExecutedPercent()) {
182
            throw new Exception(sprintf(
183
                $coverageMessage,
184
                'Line',
185
                $lineMinimum,
186
                $report->getLineExecutedPercent()
187
            ));
188
        }
189
190
        $classMinimum = $this->options['minimum']['classes'] ?? false;
191
192
        if (false !== $classMinimum && (string) $classMinimum > $report->getTestedClassesPercent()) {
193
            throw new Exception(sprintf(
194
                $coverageMessage,
195
                'Class',
196
                $classMinimum,
197
                $report->getTestedClassesPercent()
198
            ));
199
        }
200
201
        $methodMinimum = $this->options['minimum']['methods'] ?? false;
202
203
        if (false !== $methodMinimum && (string) $methodMinimum > $report->getTestedMethodsPercent()) {
204
            throw new Exception(sprintf(
205
                $coverageMessage,
206
                'Methods',
207
                $methodMinimum,
208
                $report->getTestedMethodsPercent()
209
            ));
210
        }
211
212
        $functionsMinimum = $this->options['minimum']['functions'] ?? false;
213
214
        if (false !== $functionsMinimum && (string) $functionsMinimum > $report->getTestedFunctionsPercent()) {
215
            throw new Exception(sprintf(
216
                $coverageMessage,
217
                'Functions',
218
                $functionsMinimum,
219
                $report->getTestedFunctionsPercent()
220
            ));
221
        }
222
    }
223
}
224