1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
4
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
5
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
6
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
7
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
8
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
9
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
10
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
11
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
12
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
13
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
14
|
|
|
* |
15
|
|
|
* This software consists of voluntary contributions made by many individuals |
16
|
|
|
* and is licensed under the LGPL. For more information please see |
17
|
|
|
* <http://phing.info>. |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
use Phing\Exception\BuildException; |
21
|
|
|
use Phing\Io\IOException; |
22
|
|
|
use Phing\Project; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Simple Testrunner for PHPUnit that runs all tests of a testsuite. |
26
|
|
|
* |
27
|
|
|
* @author Blair Cooper <[email protected]> |
28
|
|
|
* @package phing.tasks.ext.phpunit |
29
|
|
|
*/ |
30
|
|
|
class PHPUnitTestRunner9 implements \PHPUnit\Runner\TestHook, \PHPUnit\Framework\TestListener |
|
|
|
|
31
|
|
|
{ |
32
|
|
|
private $hasErrors = false; |
33
|
|
|
private $hasFailures = false; |
34
|
|
|
private $hasWarnings = false; |
35
|
|
|
private $hasIncomplete = false; |
36
|
|
|
private $hasSkipped = false; |
37
|
|
|
private $hasRisky = false; |
38
|
|
|
private $lastErrorMessage = ''; |
39
|
|
|
private $lastFailureMessage = ''; |
40
|
|
|
private $lastWarningMessage = ''; |
41
|
|
|
private $lastIncompleteMessage = ''; |
42
|
|
|
private $lastSkippedMessage = ''; |
43
|
|
|
private $lastRiskyMessage = ''; |
44
|
|
|
private $formatters = []; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var \SebastianBergmann\CodeCoverage\CodeCoverage |
48
|
|
|
*/ |
49
|
|
|
private $codecoverage; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var Project $project |
53
|
|
|
*/ |
54
|
|
|
private $project; |
55
|
|
|
|
56
|
|
|
private $groups = []; |
57
|
|
|
private $excludeGroups = []; |
58
|
|
|
|
59
|
|
|
private $processIsolation = false; |
60
|
|
|
|
61
|
|
|
private $useCustomErrorHandler = true; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @param Project $project |
65
|
|
|
* @param array $groups |
66
|
|
|
* @param array $excludeGroups |
67
|
|
|
* @param bool $processIsolation |
68
|
|
|
*/ |
69
|
4 |
|
public function __construct( |
70
|
|
|
Project $project, |
71
|
|
|
array $groups = [], |
72
|
|
|
array $excludeGroups = [], |
73
|
|
|
bool $processIsolation = false |
74
|
|
|
) { |
75
|
4 |
|
$this->project = $project; |
76
|
4 |
|
$this->groups = $groups; |
77
|
4 |
|
$this->excludeGroups = $excludeGroups; |
78
|
4 |
|
$this->processIsolation = $processIsolation; |
79
|
4 |
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @param $codecoverage |
83
|
|
|
*/ |
84
|
|
|
public function setCodecoverage(\SebastianBergmann\CodeCoverage\CodeCoverage $codecoverage): void |
85
|
|
|
{ |
86
|
|
|
$this->codecoverage = $codecoverage; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @param $useCustomErrorHandler |
91
|
|
|
*/ |
92
|
4 |
|
public function setUseCustomErrorHandler(bool $useCustomErrorHandler): void |
93
|
|
|
{ |
94
|
4 |
|
$this->useCustomErrorHandler = $useCustomErrorHandler; |
95
|
4 |
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @param $formatter |
99
|
|
|
*/ |
100
|
2 |
|
public function addFormatter(\PHPUnit\Framework\TestListener $formatter): void |
101
|
|
|
{ |
102
|
2 |
|
$this->formatters[] = $formatter; |
103
|
2 |
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @param $level |
107
|
|
|
* @param $message |
108
|
|
|
* @param $file |
109
|
|
|
* @param $line |
110
|
|
|
* @return bool |
111
|
|
|
*/ |
112
|
76 |
|
public function handleError(int $level, string $message, string $file, int $line): bool |
113
|
|
|
{ |
114
|
76 |
|
$invoke = new PHPUnit\Util\ErrorHandler(true, true, true, true); |
115
|
76 |
|
return $invoke($level, $message, $file, $line); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Run a test |
120
|
|
|
* |
121
|
|
|
* @param PHPUnit\Framework\TestSuite $suite |
122
|
|
|
* @throws \Phing\Exception\BuildException |
123
|
|
|
* @throws ReflectionException |
124
|
|
|
*/ |
125
|
4 |
|
public function run(PHPUnit\Framework\TestSuite $suite) |
126
|
|
|
{ |
127
|
4 |
|
$res = new PHPUnit\Framework\TestResult(); |
128
|
|
|
|
129
|
4 |
|
if ($this->codecoverage) { |
130
|
|
|
// Check if Phing coverage is being utlizied |
131
|
|
|
if ($this->project->getProperty('coverage.database')) { |
132
|
|
|
$whitelist = \Phing\Tasks\Ext\Coverage\CoverageMerger::getWhiteList($this->project); |
133
|
|
|
$filter = $this->codecoverage->filter(); |
134
|
|
|
|
135
|
|
|
if (method_exists($filter, 'includeFiles')) { |
136
|
|
|
$filter->includeFiles($whitelist); |
137
|
|
|
} else if (method_exists($filter, 'addFilesToWhiteList')) { |
138
|
|
|
$filter->addFilesToWhiteList($whitelist); |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
$res->setCodeCoverage($this->codecoverage); |
143
|
|
|
} |
144
|
|
|
|
145
|
4 |
|
$res->addListener($this); |
|
|
|
|
146
|
|
|
|
147
|
4 |
|
foreach ($this->formatters as $formatter) { |
148
|
2 |
|
$res->addListener($formatter); |
|
|
|
|
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/* Set PHPUnit error handler */ |
152
|
4 |
|
if ($this->useCustomErrorHandler) { |
153
|
4 |
|
set_error_handler([$this, 'handleError'], E_ALL | E_STRICT); |
154
|
|
|
} |
155
|
|
|
|
156
|
4 |
|
$this->injectFilters($suite); |
157
|
4 |
|
$suite->setRunTestInSeparateProcess($this->processIsolation); |
158
|
4 |
|
$suite->run($res); |
159
|
|
|
|
160
|
4 |
|
foreach ($this->formatters as $formatter) { |
161
|
2 |
|
$formatter->processResult($res); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/* Restore Phing error handler */ |
165
|
4 |
|
if ($this->useCustomErrorHandler) { |
166
|
4 |
|
restore_error_handler(); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
// Check if Phing coverage is being utlizied |
170
|
4 |
|
if ($this->codecoverage && $this->project->getProperty('coverage.database')) { |
171
|
|
|
try { |
172
|
|
|
\Phing\Tasks\Ext\Coverage\CoverageMerger::merge($this->project, $this->codecoverage->getData()); |
173
|
|
|
} catch (IOException $e) { |
174
|
|
|
throw new BuildException('Merging code coverage failed.', $e); |
175
|
|
|
} |
176
|
|
|
} |
177
|
4 |
|
$this->checkResult($res); |
178
|
4 |
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @param PHPUnit\Framework\TestSuite $suite |
182
|
|
|
* @throws ReflectionException |
183
|
|
|
*/ |
184
|
4 |
|
private function injectFilters(PHPUnit\Framework\TestSuite $suite): void |
185
|
|
|
{ |
186
|
4 |
|
$filterFactory = new PHPUnit\Runner\Filter\Factory(); |
187
|
|
|
|
188
|
4 |
|
if (empty($this->excludeGroups) && empty($this->groups)) { |
189
|
4 |
|
return; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
if (!empty($this->excludeGroups)) { |
193
|
|
|
$filterFactory->addFilter( |
194
|
|
|
new ReflectionClass(\PHPUnit\Runner\Filter\ExcludeGroupFilterIterator::class), |
195
|
|
|
$this->excludeGroups |
196
|
|
|
); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if (!empty($this->groups)) { |
200
|
|
|
$filterFactory->addFilter( |
201
|
|
|
new ReflectionClass(\PHPUnit\Runner\Filter\IncludeGroupFilterIterator::class), |
202
|
|
|
$this->groups |
203
|
|
|
); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
$suite->injectFilter($filterFactory); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* @param \PHPUnit\Framework\TestResult $res |
211
|
|
|
*/ |
212
|
4 |
|
private function checkResult(\PHPUnit\Framework\TestResult $res): void |
213
|
|
|
{ |
214
|
4 |
|
$this->hasSkipped = $res->skippedCount() > 0; |
215
|
4 |
|
$this->hasIncomplete = $res->notImplementedCount() > 0; |
216
|
4 |
|
$this->hasWarnings = $res->warningCount() > 0; |
217
|
4 |
|
$this->hasFailures = $res->failureCount() > 0; |
218
|
4 |
|
$this->hasErrors = $res->errorCount() > 0; |
219
|
4 |
|
$this->hasRisky = $res->riskyCount() > 0; |
220
|
4 |
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* @return boolean |
224
|
|
|
*/ |
225
|
4 |
|
public function hasErrors(): bool |
226
|
|
|
{ |
227
|
4 |
|
return $this->hasErrors; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* @return boolean |
232
|
|
|
*/ |
233
|
4 |
|
public function hasFailures(): bool |
234
|
|
|
{ |
235
|
4 |
|
return $this->hasFailures; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* @return boolean |
240
|
|
|
*/ |
241
|
4 |
|
public function hasWarnings(): bool |
242
|
|
|
{ |
243
|
4 |
|
return $this->hasWarnings; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* @return boolean |
248
|
|
|
*/ |
249
|
4 |
|
public function hasIncomplete(): bool |
250
|
|
|
{ |
251
|
4 |
|
return $this->hasIncomplete; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* @return boolean |
256
|
|
|
*/ |
257
|
4 |
|
public function hasSkipped(): bool |
258
|
|
|
{ |
259
|
4 |
|
return $this->hasSkipped; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* @return boolean |
264
|
|
|
*/ |
265
|
4 |
|
public function hasRisky(): bool |
266
|
|
|
{ |
267
|
4 |
|
return $this->hasRisky; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* @return string |
272
|
|
|
*/ |
273
|
|
|
public function getLastErrorMessage(): string |
274
|
|
|
{ |
275
|
|
|
return $this->lastErrorMessage; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* @return string |
280
|
|
|
*/ |
281
|
1 |
|
public function getLastFailureMessage(): string |
282
|
|
|
{ |
283
|
1 |
|
return $this->lastFailureMessage; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* @return string |
288
|
|
|
*/ |
289
|
|
|
public function getLastIncompleteMessage(): string |
290
|
|
|
{ |
291
|
|
|
return $this->lastIncompleteMessage; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* @return string |
296
|
|
|
*/ |
297
|
|
|
public function getLastSkippedMessage(): string |
298
|
|
|
{ |
299
|
|
|
return $this->lastSkippedMessage; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* @return string |
304
|
|
|
*/ |
305
|
|
|
public function getLastWarningMessage(): string |
306
|
|
|
{ |
307
|
|
|
return $this->lastWarningMessage; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* @return string |
312
|
|
|
*/ |
313
|
|
|
public function getLastRiskyMessage(): string |
314
|
|
|
{ |
315
|
|
|
return $this->lastRiskyMessage; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* An error occurred. |
320
|
|
|
* |
321
|
|
|
* @param PHPUnit\Framework\Test $test |
322
|
|
|
* @param Throwable $e |
323
|
|
|
* @param float $time |
324
|
|
|
*/ |
325
|
2 |
|
public function addError(PHPUnit\Framework\Test $test, Throwable $e, float $time): void |
326
|
|
|
{ |
327
|
2 |
|
$this->lastErrorMessage = $this->composeMessage('ERROR', $test, $e); |
328
|
2 |
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* @param string $message |
332
|
|
|
* @param PHPUnit\Framework\Test $test |
333
|
|
|
* @param Throwable $e |
334
|
|
|
* @return string |
335
|
|
|
*/ |
336
|
23 |
|
protected function composeMessage(string $message, PHPUnit\Framework\Test $test, Throwable $e): string |
337
|
|
|
{ |
338
|
23 |
|
$name = ($test instanceof \PHPUnit\Framework\TestCase ? $test->getName() : ''); |
339
|
23 |
|
$message = "Test {$message} ({$name} in class " . get_class($test) . ' ' . $e->getFile() |
340
|
23 |
|
. ' on line ' . $e->getLine() . '): ' . $e->getMessage(); |
341
|
|
|
|
342
|
23 |
|
if ($e instanceof PHPUnit\Framework\ExpectationFailedException && $e->getComparisonFailure()) { |
343
|
|
|
$message .= "\n" . $e->getComparisonFailure()->getDiff(); |
344
|
|
|
} |
345
|
|
|
|
346
|
23 |
|
return $message; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* A failure occurred. |
351
|
|
|
* |
352
|
|
|
* @param PHPUnit\Framework\Test $test |
353
|
|
|
* @param PHPUnit\Framework\AssertionFailedError $e |
354
|
|
|
* @param float $time |
355
|
|
|
*/ |
356
|
3 |
|
public function addFailure( |
357
|
|
|
PHPUnit\Framework\Test $test, |
358
|
|
|
PHPUnit\Framework\AssertionFailedError $e, |
359
|
|
|
float $time |
360
|
|
|
): void { |
361
|
3 |
|
$this->lastFailureMessage = $this->composeMessage('FAILURE', $test, $e); |
362
|
3 |
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* A failure occurred. |
366
|
|
|
* |
367
|
|
|
* @param PHPUnit\Framework\Test $test |
368
|
|
|
* @param PHPUnit\Framework\AssertionFailedError $e |
369
|
|
|
* @param float $time |
370
|
|
|
*/ |
371
|
20 |
|
public function addWarning(PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void |
372
|
|
|
{ |
373
|
20 |
|
$this->lastWarningMessage = $this->composeMessage("WARNING", $test, $e); |
374
|
20 |
|
} |
375
|
|
|
|
376
|
|
|
/** |
377
|
|
|
* Incomplete test. |
378
|
|
|
* |
379
|
|
|
* @param PHPUnit\Framework\Test $test |
380
|
|
|
* @param Exception $e |
381
|
|
|
* @param float $time |
382
|
|
|
*/ |
383
|
|
|
public function addIncompleteTest(PHPUnit\Framework\Test $test, Throwable $e, float $time): void |
384
|
|
|
{ |
385
|
|
|
$this->lastIncompleteMessage = $this->composeMessage("INCOMPLETE", $test, $e); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Skipped test. |
390
|
|
|
* |
391
|
|
|
* @param PHPUnit\Framework\Test $test |
392
|
|
|
* @param Exception $e |
393
|
|
|
* @param float $time |
394
|
|
|
* @since Method available since Release 3.0.0 |
395
|
|
|
*/ |
396
|
|
|
public function addSkippedTest(PHPUnit\Framework\Test $test, Throwable $e, float $time): void |
397
|
|
|
{ |
398
|
|
|
$this->lastSkippedMessage = $this->composeMessage('SKIPPED', $test, $e); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Risky test |
403
|
|
|
* |
404
|
|
|
* @param PHPUnit\Framework\Test $test |
405
|
|
|
* @param Throwable $e |
406
|
|
|
* @param float $time |
407
|
|
|
*/ |
408
|
|
|
public function addRiskyTest(PHPUnit\Framework\Test $test, Throwable $e, float $time): void |
409
|
|
|
{ |
410
|
|
|
$this->lastRiskyMessage = $this->composeMessage('RISKY', $test, $e); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* A test suite started. |
415
|
|
|
* |
416
|
|
|
* @param PHPUnit\Framework\TestSuite $suite |
417
|
|
|
*/ |
418
|
4 |
|
public function startTestSuite(PHPUnit\Framework\TestSuite $suite): void |
419
|
|
|
{ |
420
|
4 |
|
} |
421
|
|
|
|
422
|
|
|
/** |
423
|
|
|
* A test suite ended. |
424
|
|
|
* |
425
|
|
|
* @param PHPUnit\Framework\TestSuite $suite |
426
|
|
|
*/ |
427
|
4 |
|
public function endTestSuite(PHPUnit\Framework\TestSuite $suite): void |
428
|
|
|
{ |
429
|
4 |
|
} |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* A test started. |
433
|
|
|
* |
434
|
|
|
* @param PHPUnit\Framework\Test $test |
435
|
|
|
*/ |
436
|
4 |
|
public function startTest(PHPUnit\Framework\Test $test): void |
437
|
|
|
{ |
438
|
4 |
|
} |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* A test ended. |
442
|
|
|
* |
443
|
|
|
* @param PHPUnit\Framework\Test $test |
444
|
|
|
* @param float $time |
445
|
|
|
*/ |
446
|
4 |
|
public function endTest(PHPUnit\Framework\Test $test, float $time): void |
447
|
|
|
{ |
448
|
4 |
|
if (($test instanceof PHPUnit\Framework\TestCase) && !$test->hasExpectationOnOutput()) { |
449
|
4 |
|
echo $test->getActualOutput(); |
450
|
|
|
} |
451
|
4 |
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* Override to define how to handle a failed loading of |
455
|
|
|
* a test suite. |
456
|
|
|
* |
457
|
|
|
* @param string $message |
458
|
|
|
* @throws BuildException |
459
|
|
|
*/ |
460
|
|
|
protected function runFailed($message): void |
461
|
|
|
{ |
462
|
|
|
throw new BuildException($message); |
463
|
|
|
} |
464
|
|
|
} |
465
|
|
|
|
This interface has been deprecated. The supplier of the interface has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.