Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of phpunit/php-code-coverage.
4
 *
5
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace SebastianBergmann\CodeCoverage;
11
 
12
use function array_diff;
13
use function array_diff_key;
14
use function array_flip;
15
use function array_intersect;
16
use function array_intersect_key;
17
use function count;
18
use function explode;
19
use function file_get_contents;
20
use function in_array;
21
use function is_file;
22
use function range;
23
use function trim;
24
use SebastianBergmann\CodeCoverage\Driver\Driver;
25
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
26
 
27
/**
28
 * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
29
 */
30
final class RawCodeCoverageData
31
{
32
    /**
33
     * @var array<string, array<int>>
34
     */
35
    private static $emptyLineCache = [];
36
 
37
    /**
38
     * @var array
39
     *
40
     * @see https://xdebug.org/docs/code_coverage for format
41
     */
42
    private $lineCoverage;
43
 
44
    /**
45
     * @var array
46
     *
47
     * @see https://xdebug.org/docs/code_coverage for format
48
     */
49
    private $functionCoverage;
50
 
51
    public static function fromXdebugWithoutPathCoverage(array $rawCoverage): self
52
    {
53
        return new self($rawCoverage, []);
54
    }
55
 
56
    public static function fromXdebugWithPathCoverage(array $rawCoverage): self
57
    {
58
        $lineCoverage     = [];
59
        $functionCoverage = [];
60
 
61
        foreach ($rawCoverage as $file => $fileCoverageData) {
62
            $lineCoverage[$file]     = $fileCoverageData['lines'];
63
            $functionCoverage[$file] = $fileCoverageData['functions'];
64
        }
65
 
66
        return new self($lineCoverage, $functionCoverage);
67
    }
68
 
69
    public static function fromXdebugWithMixedCoverage(array $rawCoverage): self
70
    {
71
        $lineCoverage     = [];
72
        $functionCoverage = [];
73
 
74
        foreach ($rawCoverage as $file => $fileCoverageData) {
75
            if (!isset($fileCoverageData['functions'])) {
76
                // Current file does not have functions, so line coverage
77
                // is stored in $fileCoverageData, not in $fileCoverageData['lines']
78
                $lineCoverage[$file] = $fileCoverageData;
79
 
80
                continue;
81
            }
82
 
83
            $lineCoverage[$file]     = $fileCoverageData['lines'];
84
            $functionCoverage[$file] = $fileCoverageData['functions'];
85
        }
86
 
87
        return new self($lineCoverage, $functionCoverage);
88
    }
89
 
90
    public static function fromUncoveredFile(string $filename, FileAnalyser $analyser): self
91
    {
92
        $lineCoverage = [];
93
 
94
        foreach ($analyser->executableLinesIn($filename) as $line => $branch) {
95
            $lineCoverage[$line] = Driver::LINE_NOT_EXECUTED;
96
        }
97
 
98
        return new self([$filename => $lineCoverage], []);
99
    }
100
 
101
    private function __construct(array $lineCoverage, array $functionCoverage)
102
    {
103
        $this->lineCoverage     = $lineCoverage;
104
        $this->functionCoverage = $functionCoverage;
105
 
106
        $this->skipEmptyLines();
107
    }
108
 
109
    public function clear(): void
110
    {
111
        $this->lineCoverage = $this->functionCoverage = [];
112
    }
113
 
114
    public function lineCoverage(): array
115
    {
116
        return $this->lineCoverage;
117
    }
118
 
119
    public function functionCoverage(): array
120
    {
121
        return $this->functionCoverage;
122
    }
123
 
124
    public function removeCoverageDataForFile(string $filename): void
125
    {
126
        unset($this->lineCoverage[$filename], $this->functionCoverage[$filename]);
127
    }
128
 
129
    /**
130
     * @param int[] $lines
131
     */
132
    public function keepLineCoverageDataOnlyForLines(string $filename, array $lines): void
133
    {
134
        if (!isset($this->lineCoverage[$filename])) {
135
            return;
136
        }
137
 
138
        $this->lineCoverage[$filename] = array_intersect_key(
139
            $this->lineCoverage[$filename],
140
            array_flip($lines)
141
        );
142
    }
143
 
144
    /**
145
     * @param int[] $linesToBranchMap
146
     */
147
    public function markExecutableLineByBranch(string $filename, array $linesToBranchMap): void
148
    {
149
        if (!isset($this->lineCoverage[$filename])) {
150
            return;
151
        }
152
 
153
        $linesByBranch = [];
154
 
155
        foreach ($linesToBranchMap as $line => $branch) {
156
            $linesByBranch[$branch][] = $line;
157
        }
158
 
159
        foreach ($this->lineCoverage[$filename] as $line => $lineStatus) {
160
            if (!isset($linesToBranchMap[$line])) {
161
                continue;
162
            }
163
 
164
            $branch = $linesToBranchMap[$line];
165
 
166
            if (!isset($linesByBranch[$branch])) {
167
                continue;
168
            }
169
 
170
            foreach ($linesByBranch[$branch] as $lineInBranch) {
171
                $this->lineCoverage[$filename][$lineInBranch] = $lineStatus;
172
            }
173
 
174
            if (Driver::LINE_EXECUTED === $lineStatus) {
175
                unset($linesByBranch[$branch]);
176
            }
177
        }
178
    }
179
 
180
    /**
181
     * @param int[] $lines
182
     */
183
    public function keepFunctionCoverageDataOnlyForLines(string $filename, array $lines): void
184
    {
185
        if (!isset($this->functionCoverage[$filename])) {
186
            return;
187
        }
188
 
189
        foreach ($this->functionCoverage[$filename] as $functionName => $functionData) {
190
            foreach ($functionData['branches'] as $branchId => $branch) {
191
                if (count(array_diff(range($branch['line_start'], $branch['line_end']), $lines)) > 0) {
192
                    unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]);
193
 
194
                    foreach ($functionData['paths'] as $pathId => $path) {
195
                        if (in_array($branchId, $path['path'], true)) {
196
                            unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]);
197
                        }
198
                    }
199
                }
200
            }
201
        }
202
    }
203
 
204
    /**
205
     * @param int[] $lines
206
     */
207
    public function removeCoverageDataForLines(string $filename, array $lines): void
208
    {
209
        if (empty($lines)) {
210
            return;
211
        }
212
 
213
        if (!isset($this->lineCoverage[$filename])) {
214
            return;
215
        }
216
 
217
        $this->lineCoverage[$filename] = array_diff_key(
218
            $this->lineCoverage[$filename],
219
            array_flip($lines)
220
        );
221
 
222
        if (isset($this->functionCoverage[$filename])) {
223
            foreach ($this->functionCoverage[$filename] as $functionName => $functionData) {
224
                foreach ($functionData['branches'] as $branchId => $branch) {
225
                    if (count(array_intersect($lines, range($branch['line_start'], $branch['line_end']))) > 0) {
226
                        unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]);
227
 
228
                        foreach ($functionData['paths'] as $pathId => $path) {
229
                            if (in_array($branchId, $path['path'], true)) {
230
                                unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]);
231
                            }
232
                        }
233
                    }
234
                }
235
            }
236
        }
237
    }
238
 
239
    /**
240
     * At the end of a file, the PHP interpreter always sees an implicit return. Where this occurs in a file that has
241
     * e.g. a class definition, that line cannot be invoked from a test and results in confusing coverage. This engine
242
     * implementation detail therefore needs to be masked which is done here by simply ensuring that all empty lines
243
     * are skipped over for coverage purposes.
244
     *
245
     * @see https://github.com/sebastianbergmann/php-code-coverage/issues/799
246
     */
247
    private function skipEmptyLines(): void
248
    {
249
        foreach ($this->lineCoverage as $filename => $coverage) {
250
            foreach ($this->getEmptyLinesForFile($filename) as $emptyLine) {
251
                unset($this->lineCoverage[$filename][$emptyLine]);
252
            }
253
        }
254
    }
255
 
256
    private function getEmptyLinesForFile(string $filename): array
257
    {
258
        if (!isset(self::$emptyLineCache[$filename])) {
259
            self::$emptyLineCache[$filename] = [];
260
 
261
            if (is_file($filename)) {
262
                $sourceLines = explode("\n", file_get_contents($filename));
263
 
264
                foreach ($sourceLines as $line => $source) {
265
                    if (trim($source) === '') {
266
                        self::$emptyLineCache[$filename][] = ($line + 1);
267
                    }
268
                }
269
            }
270
        }
271
 
272
        return self::$emptyLineCache[$filename];
273
    }
274
}