• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / tester / 22278741586

22 Feb 2026 02:09PM UTC coverage: 81.835%. First build
22278741586

Pull #467

github

web-flow
Merge b74a1f536 into 29a5403e0
Pull Request #467: Feat/structural metrics v2

79 of 107 new or added lines in 18 files covered. (73.83%)

1757 of 2147 relevant lines covered (81.84%)

0.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

14.06
/src/CodeCoverage/Collector.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Tester.
5
 * Copyright (c) 2009 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Tester\CodeCoverage;
11
use pcov;
12
use function defined, in_array;
13
use const LOCK_EX, LOCK_UN, XDEBUG_CC_DEAD_CODE, XDEBUG_CC_UNUSED;
14

15

16
/**
17
 * Code coverage collector.
18
 */
19
class Collector
20
{
21
        public const
22
                EnginePcov = 'PCOV',
23
                EnginePhpdbg = 'PHPDBG',
24
                EngineXdebug = 'Xdebug';
25

26
        /** @var resource */
27
        private static $file;
28
        private static string $engine;
29

30

31
        /** @return array<array{string, string}>  [engine name, version] */
32
        public static function detectEngines(): array
33
        {
34
                return array_filter([
×
NEW
35
                        extension_loaded('pcov') ? [self::EnginePcov, (string) phpversion('pcov')] : null,
×
NEW
36
                        defined('PHPDBG_VERSION') ? [self::EnginePhpdbg, (string) PHPDBG_VERSION] : null,
×
NEW
37
                        extension_loaded('xdebug') ? [self::EngineXdebug, (string) phpversion('xdebug')] : null,
×
38
                ]);
39
        }
40

41

42
        public static function isStarted(): bool
43
        {
44
                return self::$file !== null;
1✔
45
        }
46

47

48
        /**
49
         * Starts gathering the information for code coverage.
50
         * @throws \LogicException
51
         */
52
        public static function start(string $file, string $engine): void
53
        {
54
                if (self::isStarted()) {
×
55
                        throw new \LogicException('Code coverage collector has been already started.');
×
56

57
                } elseif (!in_array(
×
58
                        $engine,
×
59
                        array_map(fn(array $engineInfo) => $engineInfo[0], self::detectEngines()),
×
60
                        strict: true,
×
61
                )) {
62
                        throw new \LogicException("Code coverage engine '$engine' is not supported.");
×
63
                }
64

65
                self::$file = fopen($file, 'c+') ?: throw new \RuntimeException("Cannot open file '$file' for code coverage.");
×
66
                self::$engine = $engine;
×
67
                self::{'start' . $engine}();
×
68

69
                register_shutdown_function(function (): void {
1✔
70
                        register_shutdown_function(self::save(...));
1✔
71
                });
1✔
72
        }
1✔
73

74

75
        /**
76
         * Flushes all gathered information. Effective only with PHPDBG collector.
77
         */
78
        public static function flush(): void
79
        {
80
                if (self::isStarted() && self::$engine === self::EnginePhpdbg) {
×
81
                        self::save();
×
82
                }
83
        }
84

85

86
        /**
87
         * Saves information about code coverage. Can be called repeatedly to free memory.
88
         * @throws \LogicException
89
         */
90
        public static function save(): void
91
        {
92
                if (!self::isStarted()) {
1✔
93
                        throw new \LogicException('Code coverage collector has not been started.');
×
94
                }
95

96
                [$positive, $negative] = self::{'collect' . self::$engine}();
1✔
97

98
                flock(self::$file, LOCK_EX);
×
99
                fseek(self::$file, 0);
×
100
                $rawContent = stream_get_contents(self::$file);
×
101
                $original = $rawContent ? unserialize($rawContent) : [];
×
102
                $coverage = array_replace_recursive($negative, $original, $positive);
×
103

104
                fseek(self::$file, 0);
×
105
                ftruncate(self::$file, 0);
×
106
                fwrite(self::$file, serialize($coverage));
×
107
                flock(self::$file, LOCK_UN);
×
108
        }
109

110

111
        private static function startPCOV(): void
112
        {
113
                pcov\start();
×
114
        }
115

116

117
        /**
118
         * Collects information about code coverage.
119
         * @return array{array<string, array<int, int>>, array<string, array<int, int>>}  [positive, negative]
120
         */
121
        private static function collectPCOV(): array
122
        {
123
                $positive = $negative = [];
×
124

125
                pcov\stop();
×
126

127
                foreach (pcov\collect() as $file => $lines) {
×
128
                        if (!file_exists($file)) {
×
129
                                continue;
×
130
                        }
131

132
                        foreach ($lines as $num => $val) {
×
133
                                if ($val > 0) {
×
134
                                        $positive[$file][$num] = $val;
×
135
                                } else {
136
                                        $negative[$file][$num] = $val;
×
137
                                }
138
                        }
139
                }
140

141
                return [$positive, $negative];
×
142
        }
143

144

145
        private static function startXdebug(): void
146
        {
147
                xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
×
148
        }
149

150

151
        /**
152
         * Collects information about code coverage.
153
         * @return array{array<string, array<int, int>>, array<string, array<int, int>>}  [positive, negative]
154
         */
155
        private static function collectXdebug(): array
156
        {
157
                $positive = $negative = [];
×
158

159
                foreach (xdebug_get_code_coverage() as $file => $lines) {
×
160
                        if (!file_exists($file)) {
×
161
                                continue;
×
162
                        }
163

164
                        foreach ($lines as $num => $val) {
×
165
                                if ($val > 0) {
×
166
                                        $positive[$file][$num] = $val;
×
167
                                } else {
168
                                        $negative[$file][$num] = $val;
×
169
                                }
170
                        }
171
                }
172

173
                return [$positive, $negative];
×
174
        }
175

176

177
        private static function startPhpDbg(): void
178
        {
179
                phpdbg_start_oplog();
×
180
        }
1✔
181

182

183
        /**
184
         * Collects information about code coverage.
185
         * @return array{array<string, array<int, int>>, array<string, array<int, int>>}  [positive, negative]
186
         */
187
        private static function collectPhpDbg(): array
188
        {
189
                /** @var array<string, array<int, int>> $positive */
190
                $positive = phpdbg_end_oplog();
1✔
191
                /** @var array<string, array<int, int>> $negative */
192
                $negative = phpdbg_get_executable();
×
193

194
                foreach ($positive as $file => &$lines) {
×
195
                        $lines = array_fill_keys(array_keys($lines), 1);
×
196
                }
197

198
                foreach ($negative as $file => &$lines) {
×
199
                        $lines = array_fill_keys(array_keys($lines), -1);
×
200
                }
201

202
                phpdbg_start_oplog();
×
203
                return [$positive, $negative];
×
204
        }
205
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc