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

CodeCoverageListener   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 186
Duplicated Lines 0 %

Test Coverage

Coverage 8.77%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 92
dl 0
loc 186
ccs 5
cts 57
cp 0.0877
rs 9.76
c 1
b 0
f 0
wmc 33

8 Methods

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