• 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

81.73
/src/Runner/Runner.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\Runner;
11

12
use Tester\Environment;
13
use Tester\Helpers;
14
use function count, in_array;
15
use const DIRECTORY_SEPARATOR, GLOB_ONLYDIR, PATHINFO_FILENAME;
16

17

18
/**
19
 * Test runner.
20
 */
21
class Runner
22
{
23
        /** @var list<string>  paths to test files/directories */
24
        public array $paths = [];
25

26
        /** @var list<string> */
27
        public array $ignoreDirs = ['vendor'];
28
        public int $threadCount = 1;
29
        public TestHandler $testHandler;
30

31
        /** @var list<OutputHandler> */
32
        public array $outputHandlers = [];
33
        public bool $stopOnFail = false;
34
        private PhpInterpreter $interpreter;
35

36
        /** @var array<string, string>  environment variables for test processes */
37
        private array $envVars = [];
38

39
        /** @var Job[] */
40
        private array $jobs;
41
        private bool $interrupted = false;
42
        private ?string $tempDir = null;
43
        private bool $result;
44

45
        /** @var array<string, int>  test signature => result (Test::Prepared|Passed|Failed|Skipped) */
46
        private array $lastResults = [];
47
        private int $jobCount = 0;
48
        private int $finishedCount = 0;
49

50

51
        public function __construct(PhpInterpreter $interpreter)
1✔
52
        {
53
                $this->interpreter = $interpreter;
1✔
54
                $this->testHandler = new TestHandler($this);
1✔
55
        }
1✔
56

57

58
        public function setEnvironmentVariable(string $name, string $value): void
1✔
59
        {
60
                $this->envVars[$name] = $value;
1✔
61
        }
1✔
62

63

64
        /** @return array<string, string> */
65
        public function getEnvironmentVariables(): array
66
        {
67
                return $this->envVars;
1✔
68
        }
69

70

71
        public function addPhpIniOption(string $name, ?string $value = null): void
1✔
72
        {
73
                $this->interpreter = $this->interpreter->withPhpIniOption($name, $value);
1✔
74
        }
1✔
75

76

77
        public function setTempDirectory(?string $path): void
1✔
78
        {
79
                $this->tempDir = $path;
1✔
80
                $this->testHandler->setTempDirectory($path);
1✔
81
        }
1✔
82

83

84
        /**
85
         * Runs all tests.
86
         */
87
        public function run(): bool
88
        {
89
                $this->result = true;
1✔
90
                $this->interrupted = false;
1✔
91

92
                foreach ($this->outputHandlers as $handler) {
1✔
93
                        $handler->begin();
1✔
94
                }
95

96
                $this->jobs = $running = [];
1✔
97
                foreach ($this->paths as $path) {
1✔
98
                        $this->findTests($path);
1✔
99
                }
100
                $this->finishedCount = 0;
1✔
101
                $this->jobCount = count($this->jobs);
1✔
102

103
                if ($this->tempDir) {
1✔
104
                        usort(
1✔
105
                                $this->jobs,
1✔
106
                                fn(Job $a, Job $b): int => $this->getLastResult($a->getTest()) - $this->getLastResult($b->getTest()),
1✔
107
                        );
108
                }
109

110
                $threads = range(1, $this->threadCount);
1✔
111
                $async = $this->threadCount > 1 && count($this->jobs) > 1;
1✔
112

113
                try {
114
                        while (($this->jobs || $running) && !$this->interrupted) {
1✔
115
                                while ($threads && $this->jobs) {
1✔
116
                                        $running[] = $job = array_shift($this->jobs);
1✔
117
                                        $job->setEnvironmentVariable(Environment::VariableThread, (string) array_shift($threads));
1✔
118
                                        foreach ($this->outputHandlers as $handler) {
1✔
119
                                                if (method_exists($handler, 'jobStarted')) {
1✔
NEW
120
                                                        $handler->jobStarted($job);
×
121
                                                }
122
                                        }
123
                                        $job->run(async: $async);
1✔
124
                                }
125

126
                                if ($async) {
1✔
NEW
127
                                        foreach ($this->outputHandlers as $handler) {
×
NEW
128
                                                if (method_exists($handler, 'tick')) {
×
NEW
129
                                                        $handler->tick($running);
×
130
                                                }
131
                                        }
132

133
                                        Job::waitForActivity($running);
×
134
                                }
135

136
                                foreach ($running as $key => $job) {
1✔
137
                                        if ($this->interrupted) {
1✔
138
                                                break 2;
×
139
                                        }
140

141
                                        if (!$job->isRunning()) {
1✔
142
                                                $threads[] = $job->getEnvironmentVariable(Environment::VariableThread);
1✔
143
                                                $this->finishedCount++;
1✔
144
                                                $this->testHandler->assess($job);
1✔
145
                                                unset($running[$key]);
1✔
146
                                        }
147
                                }
148
                        }
149
                } finally {
1✔
150
                        foreach ($this->outputHandlers as $handler) {
1✔
151
                                $handler->end();
1✔
152
                        }
153
                }
154

155
                return $this->result;
1✔
156
        }
157

158

159
        private function findTests(string $path): void
1✔
160
        {
161
                if (strpbrk($path, '*?') === false && !file_exists($path)) {
1✔
162
                        throw new \InvalidArgumentException("File or directory '$path' not found.");
×
163
                }
164

165
                if (is_dir($path)) {
1✔
166
                        foreach (glob(str_replace('[', '[[]', $path) . '/*', GLOB_ONLYDIR) ?: [] as $dir) {
1✔
167
                                if (in_array(basename($dir), $this->ignoreDirs, strict: true)) {
×
168
                                        continue;
×
169
                                }
170

171
                                $this->findTests($dir);
×
172
                        }
173

174
                        $this->findTests($path . '/*.phpt');
1✔
175
                        $this->findTests($path . '/*Test.php');
1✔
176

177
                } else {
178
                        foreach (glob(str_replace('[', '[[]', $path)) ?: [] as $file) {
1✔
179
                                if (is_file($file)) {
1✔
180
                                        $this->testHandler->initiate(realpath($file) ?: $file);
1✔
181
                                }
182
                        }
183
                }
184
        }
1✔
185

186

187
        /**
188
         * Appends new job to queue.
189
         */
190
        public function addJob(Job $job): void
1✔
191
        {
192
                $this->jobs[] = $job;
1✔
193
        }
1✔
194

195

196
        public function prepareTest(Test $test): void
1✔
197
        {
198
                foreach ($this->outputHandlers as $handler) {
1✔
199
                        $handler->prepare($test);
1✔
200
                }
201
        }
1✔
202

203

204
        /**
205
         * Writes to output handlers.
206
         */
207
        public function finishTest(Test $test): void
1✔
208
        {
209
                $this->result = $this->result && ($test->getResult() !== Test::Failed);
1✔
210

211
                foreach ($this->outputHandlers as $handler) {
1✔
212
                        $handler->finish($test);
1✔
213
                }
214

215
                if ($this->tempDir) {
1✔
216
                        $lastResult = &$this->lastResults[$test->getSignature()];
1✔
217
                        if ($lastResult !== $test->getResult()) {
1✔
218
                                file_put_contents($this->getLastResultFilename($test), $lastResult = $test->getResult());
1✔
219
                        }
220
                }
221

222
                if ($this->stopOnFail && $test->getResult() === Test::Failed) {
1✔
223
                        $this->interrupted = true;
1✔
224
                }
225
        }
1✔
226

227

228
        public function getInterpreter(): PhpInterpreter
229
        {
230
                return $this->interpreter;
1✔
231
        }
232

233

234
        public function getJobCount(): int
235
        {
NEW
236
                return $this->jobCount;
×
237
        }
238

239

240
        public function getFinishedCount(): int
241
        {
NEW
242
                return $this->finishedCount;
×
243
        }
244

245

246
        private function getLastResult(Test $test): int
247
        {
248
                $signature = $test->getSignature();
×
249
                if (isset($this->lastResults[$signature])) {
×
250
                        return $this->lastResults[$signature];
×
251
                }
252

253
                $file = $this->getLastResultFilename($test);
×
254
                if (is_file($file)) {
×
255
                        return $this->lastResults[$signature] = (int) Helpers::readFile($file);
×
256
                }
257

258
                return $this->lastResults[$signature] = Test::Prepared;
×
259
        }
260

261

262
        private function getLastResultFilename(Test $test): string
1✔
263
        {
264
                return $this->tempDir
1✔
265
                        . DIRECTORY_SEPARATOR
1✔
266
                        . pathinfo($test->getFile(), PATHINFO_FILENAME)
1✔
267
                        . '.'
1✔
268
                        . substr(md5($test->getSignature()), 0, 5)
1✔
269
                        . '.result';
1✔
270
        }
271
}
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