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

nette / tester / 22278866689

22 Feb 2026 02:19PM UTC coverage: 81.835%. First build
22278866689

Pull #467

github

web-flow
Merge 8859839ce 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

47.37
/src/Framework/Environment.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;
11

12
use function array_key_exists, count, in_array;
13
use const LOCK_EX, PHP_OUTPUT_HANDLER_FLUSHABLE, PHP_SAPI, STDOUT, T_FINAL, TOKEN_PARSE;
14

15

16
/**
17
 * Testing environment.
18
 */
19
class Environment
20
{
21
        /** Should Test use console colors? */
22
        public const VariableColors = 'NETTE_TESTER_COLORS';
23

24
        /** Test is run by Runner */
25
        public const VariableRunner = 'NETTE_TESTER_RUNNER';
26

27
        /** Code coverage engine */
28
        public const VariableCoverageEngine = 'NETTE_TESTER_COVERAGE_ENGINE';
29

30
        /** Code coverage file */
31
        public const VariableCoverage = 'NETTE_TESTER_COVERAGE';
32

33
        /** Thread number when run tests in multi threads */
34
        public const VariableThread = 'NETTE_TESTER_THREAD';
35

36
        /** @deprecated use Environment::VariableColors */
37
        public const COLORS = self::VariableColors;
38

39
        /** @deprecated use Environment::VariableRunner */
40
        public const RUNNER = self::VariableRunner;
41

42
        /** @deprecated use Environment::VariableCoverageEngine */
43
        public const COVERAGE_ENGINE = self::VariableCoverageEngine;
44

45
        /** @deprecated use Environment::VariableCoverage */
46
        public const COVERAGE = self::VariableCoverage;
47

48
        /** @deprecated use Environment::VariableThread */
49
        public const THREAD = self::VariableThread;
50

51
        public static bool $checkAssertions = false;
52
        public static bool $useColors = false;
53
        private static int $exitCode = 0;
54

55

56
        /**
57
         * Configures testing environment.
58
         */
59
        public static function setup(): void
60
        {
61
                self::setupErrors();
×
62
                self::setupColors();
×
63

64
                class_exists(Runner\Job::class);
×
65
                class_exists(Dumper::class);
×
66
                class_exists(Assert::class);
×
67

68
                $annotations = self::getTestAnnotations();
×
69
                self::$checkAssertions = !isset($annotations['outputmatch']) && !isset($annotations['outputmatchfile']);
×
70

NEW
71
                $coverageFile = getenv(self::VariableCoverage);
×
NEW
72
                $coverageEngine = getenv(self::VariableCoverageEngine);
×
NEW
73
                if ($coverageFile && $coverageEngine) {
×
NEW
74
                        CodeCoverage\Collector::start($coverageFile, $coverageEngine);
×
75
                }
76

77
                if (getenv('TERMINAL_EMULATOR') === 'JetBrains-JediTerm') {
1✔
78
                        Dumper::$maxPathSegments = -1;
×
79
                        Dumper::$pathSeparator = '/';
×
80
                }
81
        }
1✔
82

83

84
        /**
85
         * Configures colored output.
86
         */
87
        public static function setupColors(): void
88
        {
89
                self::$useColors = getenv(self::VariableColors) !== false
×
90
                        ? (bool) getenv(self::VariableColors)
×
91
                        : (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg')
92
                                && getenv('NO_COLOR') === false // https://no-color.org
×
93
                                && (getenv('FORCE_COLOR')
×
94
                                        || (function_exists('sapi_windows_vt100_support')
×
95
                                                ? sapi_windows_vt100_support(STDOUT)
×
96
                                                : @stream_isatty(STDOUT)) // @ may trigger error 'cannot cast a filtered stream on this system'
×
97
                                );
98

99
                ob_start(
×
100
                        fn(string $s): string => self::$useColors ? $s : Ansi::stripAnsi($s),
1✔
101
                        1,
×
102
                        PHP_OUTPUT_HANDLER_FLUSHABLE,
×
103
                );
104
        }
105

106

107
        /**
108
         * Configures PHP error handling.
109
         */
110
        public static function setupErrors(): void
111
        {
112
                error_reporting(E_ALL);
×
113
                ini_set('display_errors', '1');
×
114
                ini_set('html_errors', '0');
×
115
                ini_set('log_errors', '0');
×
116

NEW
117
                set_exception_handler(self::handleException(...));
×
118

119
                set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
1✔
120
                        if (
121
                                in_array($severity, [E_RECOVERABLE_ERROR, E_USER_ERROR], strict: true)
1✔
122
                                || ($severity & error_reporting()) === $severity
1✔
123
                        ) {
124
                                self::handleException(new \ErrorException($message, 0, $severity, $file, $line));
125
                        }
126

127
                        return false;
1✔
128
                });
×
129

130
                register_shutdown_function(function (): void {
×
131
                        Assert::$onFailure = self::handleException(...);
1✔
132

133
                        $error = error_get_last();
1✔
134
                        register_shutdown_function(function () use ($error): void {
1✔
135
                                if (in_array($error['type'] ?? null, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], strict: true)) {
1✔
136
                                        if (($error['type'] & error_reporting()) !== $error['type']) { // show fatal errors hidden by @shutup
137
                                                self::print("\n" . Ansi::colorize("Fatal error: $error[message] in $error[file] on line $error[line]", 'white/red'));
138
                                        }
139
                                } elseif (self::$checkAssertions && !Assert::$counter) {
1✔
140
                                        self::print("\n" . Ansi::colorize('Error: This test forgets to execute an assertion.', 'white/red'));
141
                                        self::exit(Runner\Job::CodeFail);
142
                                } elseif (!getenv(self::VariableRunner) && self::$exitCode !== Runner\Job::CodeSkip) {
1✔
143
                                        self::print("\n" . (self::$exitCode ? Ansi::colorize('FAILURE', 'white/red') : Ansi::colorize('OK', 'white/green')));
144
                                }
145
                        });
1✔
146
                });
1✔
147
        }
148

149

150
        /**
151
         * Creates global functions test(), testException(), setUp() and tearDown().
152
         */
153
        public static function setupFunctions(): void
154
        {
155
                require __DIR__ . '/functions.php';
1✔
156
        }
1✔
157

158

159
        /**
160
         * @internal
161
         */
162
        public static function handleException(\Throwable $e): void
163
        {
164
                self::$checkAssertions = false;
×
165
                self::print(Dumper::dumpException($e));
×
166
                self::exit($e instanceof AssertException ? Runner\Job::CodeFail : Runner\Job::CodeError);
×
167
        }
168

169

170
        /**
171
         * Skips this test.
172
         */
173
        public static function skip(string $message = ''): void
174
        {
175
                self::$checkAssertions = false;
×
176
                self::print("\nSkipped:\n$message");
×
177
                self::exit(Runner\Job::CodeSkip);
×
178
        }
179

180

181
        /**
182
         * Locks the parallel tests.
183
         * @param  string  $path  lock store directory
184
         */
185
        public static function lock(string $name = '', string $path = ''): void
186
        {
187
                static $locks;
×
188
                $file = "$path/lock-" . md5($name);
×
189
                if (!isset($locks[$file])) {
×
190
                        $locks[$file] = fopen($file, 'w') ?: throw new \RuntimeException("Unable to create lock file '$file'.");
×
191
                        flock($locks[$file], LOCK_EX);
×
192
                }
193
        }
194

195

196
        /**
197
         * Returns current test annotations.
198
         * @return array<string|string[]>
199
         */
200
        public static function getTestAnnotations(): array
201
        {
202
                $trace = debug_backtrace();
1✔
203
                return ($file = $trace[count($trace) - 1]['file'] ?? null)
1✔
204
                        ? Helpers::parseDocComment(Helpers::readFile($file)) + ['file' => $file]
1✔
205
                        : [];
1✔
206
        }
207

208

209
        /**
210
         * Removes keyword final from source codes.
211
         */
212
        public static function bypassFinals(): void
213
        {
214
                FileMutator::addMutator(function (string $code): string {
1✔
215
                        if (str_contains($code, 'final')) {
1✔
216
                                $tokens = \PhpToken::tokenize($code, TOKEN_PARSE);
1✔
217
                                $code = '';
1✔
218
                                foreach ($tokens as $token) {
1✔
219
                                        $code .= $token->is(T_FINAL) ? '' : $token->text;
1✔
220
                                }
221
                        }
222

223
                        return $code;
1✔
224
                });
1✔
225
        }
1✔
226

227

228
        /**
229
         * Loads data according to the file annotation or specified by Tester\Runner\TestHandler::initiateDataProvider()
230
         * @return array<string, mixed>
231
         */
232
        public static function loadData(): array
233
        {
234
                /** @var list<string> $argv */
235
                $argv = $_SERVER['argv'] ?? [];
1✔
236
                if ($argv && ($tmp = preg_filter('#--dataprovider=(.*)#Ai', '$1', $argv))) {
1✔
237
                        [$key, $file] = explode('|', reset($tmp), 2);
1✔
238
                        $data = DataProvider::load($file);
1✔
239
                        if (!array_key_exists($key, $data)) {
1✔
240
                                throw new \Exception("Missing dataset '$key' from data provider '$file'.");
1✔
241
                        }
242

243
                        return $data[$key];
1✔
244
                }
245

246
                $annotations = self::getTestAnnotations();
1✔
247
                if (!isset($annotations['dataprovider'])) {
1✔
248
                        throw new \Exception('Missing annotation @dataProvider.');
1✔
249
                }
250

251
                $provider = (array) $annotations['dataprovider'];
×
252
                [$file, $query] = DataProvider::parseAnnotation($provider[0], $annotations['file']);
×
253

254
                $data = DataProvider::load($file, $query);
×
255
                if (!$data) {
×
256
                        throw new \Exception("No datasets from data provider '$file'" . ($query ? " for query '$query'" : '') . '.');
×
257
                }
258

259
                return reset($data);
×
260
        }
261

262

263
        public static function exit(int $code = 0): void
264
        {
265
                self::$exitCode = $code;
×
266
                exit($code);
×
267
        }
268

269

270
        /** @internal */
271
        public static function print(string $s): void
1✔
272
        {
273
                $s = $s === '' || str_ends_with($s, "\n") ? $s : $s . "\n";
1✔
274
                if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
1✔
275
                        fwrite(STDOUT, self::$useColors ? $s : Ansi::stripAnsi($s));
1✔
276
                } else {
277
                        echo $s;
×
278
                }
279
        }
1✔
280
}
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