Passed
Push — develop ( 30cf64...589229 )
by Guillaume
06:18 queued 04:10
created

TestRunner   F

Complexity

Total Complexity 212

Size/Duplication

Total Lines 1138
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 622
dl 0
loc 1138
rs 1.978
c 0
b 0
f 0
wmc 212

How to fix   Complexity   

Complex Class

Complex classes like TestRunner often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TestRunner, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of PHPUnit.
4
 *
5
 * (c) Sebastian Bergmann <[email protected]>
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 PHPUnit\TextUI;
11
12
use PHPUnit\Framework\Exception;
13
use PHPUnit\Framework\TestResult;
14
use PHPUnit\Framework\TestSuite;
15
use PHPUnit\Runner\AfterLastTestHook;
16
use PHPUnit\Runner\BaseTestRunner;
17
use PHPUnit\Runner\BeforeFirstTestHook;
18
use PHPUnit\Runner\DefaultTestResultCache;
19
use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator;
20
use PHPUnit\Runner\Filter\Factory;
21
use PHPUnit\Runner\Filter\IncludeGroupFilterIterator;
22
use PHPUnit\Runner\Filter\NameFilterIterator;
23
use PHPUnit\Runner\Hook;
24
use PHPUnit\Runner\NullTestResultCache;
25
use PHPUnit\Runner\ResultCacheExtension;
26
use PHPUnit\Runner\StandardTestSuiteLoader;
27
use PHPUnit\Runner\TestHook;
28
use PHPUnit\Runner\TestListenerAdapter;
29
use PHPUnit\Runner\TestSuiteLoader;
30
use PHPUnit\Runner\TestSuiteSorter;
31
use PHPUnit\Runner\Version;
32
use PHPUnit\TextUI\Configuration\Configuration;
33
use PHPUnit\TextUI\Configuration\ExtensionHandler;
34
use PHPUnit\TextUI\Configuration\PhpHandler;
35
use PHPUnit\TextUI\Configuration\Registry;
36
use PHPUnit\Util\Filesystem;
37
use PHPUnit\Util\Log\JUnit;
38
use PHPUnit\Util\Log\TeamCity;
39
use PHPUnit\Util\Printer;
40
use PHPUnit\Util\TestDox\CliTestDoxPrinter;
41
use PHPUnit\Util\TestDox\HtmlResultPrinter;
42
use PHPUnit\Util\TestDox\TextResultPrinter;
43
use PHPUnit\Util\TestDox\XmlResultPrinter;
44
use PHPUnit\Util\XdebugFilterScriptGenerator;
45
use SebastianBergmann\CodeCoverage\CodeCoverage;
46
use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException;
47
use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter;
48
use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport;
49
use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport;
50
use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport;
51
use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport;
52
use SebastianBergmann\CodeCoverage\Report\Text as TextReport;
53
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport;
54
use SebastianBergmann\Comparator\Comparator;
55
use SebastianBergmann\Environment\Runtime;
56
use SebastianBergmann\Invoker\Invoker;
57
use SebastianBergmann\Timer\Timer;
58
59
/**
60
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
61
 */
62
final class TestRunner extends BaseTestRunner
63
{
64
    public const SUCCESS_EXIT = 0;
65
66
    public const FAILURE_EXIT = 1;
67
68
    public const EXCEPTION_EXIT = 2;
69
70
    /**
71
     * @var bool
72
     */
73
    private static $versionStringPrinted = false;
74
75
    /**
76
     * @var CodeCoverageFilter
77
     */
78
    private $codeCoverageFilter;
79
80
    /**
81
     * @var TestSuiteLoader
82
     */
83
    private $loader;
84
85
    /**
86
     * @var ResultPrinter
87
     */
88
    private $printer;
89
90
    /**
91
     * @var Runtime
92
     */
93
    private $runtime;
94
95
    /**
96
     * @var bool
97
     */
98
    private $messagePrinted = false;
99
100
    /**
101
     * @var Hook[]
102
     */
103
    private $extensions = [];
104
105
    /**
106
     * @var Timer
107
     */
108
    private $timer;
109
110
    public function __construct(TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null)
111
    {
112
        if ($filter === null) {
113
            $filter = new CodeCoverageFilter;
114
        }
115
116
        $this->codeCoverageFilter = $filter;
117
        $this->loader             = $loader;
118
        $this->runtime            = new Runtime;
119
        $this->timer              = new Timer;
120
    }
121
122
    /**
123
     * @throws \PHPUnit\Runner\Exception
124
     * @throws Exception
125
     */
126
    public function run(TestSuite $suite, array $arguments = [], array $warnings = [], bool $exit = true): TestResult
127
    {
128
        if (isset($arguments['configuration'])) {
129
            $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration'];
130
        }
131
132
        $this->handleConfiguration($arguments);
133
134
        if (\is_int($arguments['columns']) && $arguments['columns'] < 16) {
135
            $arguments['columns']   = 16;
136
            $tooFewColumnsRequested = true;
137
        }
138
139
        if (isset($arguments['bootstrap'])) {
140
            $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap'];
141
        }
142
143
        if ($arguments['backupGlobals'] === true) {
144
            $suite->setBackupGlobals(true);
145
        }
146
147
        if ($arguments['backupStaticAttributes'] === true) {
148
            $suite->setBackupStaticAttributes(true);
149
        }
150
151
        if ($arguments['beStrictAboutChangesToGlobalState'] === true) {
152
            $suite->setBeStrictAboutChangesToGlobalState(true);
153
        }
154
155
        if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) {
156
            \mt_srand($arguments['randomOrderSeed']);
157
        }
158
159
        if ($arguments['cacheResult']) {
160
            if (!isset($arguments['cacheResultFile'])) {
161
                if (isset($arguments['configuration'])) {
162
                    \assert($arguments['configuration'] instanceof Configuration);
163
164
                    $cacheLocation = $arguments['configuration']->filename();
165
                } else {
166
                    $cacheLocation = $_SERVER['PHP_SELF'];
167
                }
168
169
                $arguments['cacheResultFile'] = null;
170
171
                $cacheResultFile = \realpath($cacheLocation);
172
173
                if ($cacheResultFile !== false) {
174
                    $arguments['cacheResultFile'] = \dirname($cacheResultFile);
175
                }
176
            }
177
178
            $cache = new DefaultTestResultCache($arguments['cacheResultFile']);
179
180
            $this->addExtension(new ResultCacheExtension($cache));
181
        }
182
183
        if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) {
184
            $cache = $cache ?? new NullTestResultCache;
185
186
            $cache->load();
187
188
            $sorter = new TestSuiteSorter($cache);
189
190
            $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']);
191
            $originalExecutionOrder = $sorter->getOriginalExecutionOrder();
192
193
            unset($sorter);
194
        }
195
196
        if (\is_int($arguments['repeat']) && $arguments['repeat'] > 0) {
197
            $_suite = new TestSuite;
198
199
            /* @noinspection PhpUnusedLocalVariableInspection */
200
            foreach (\range(1, $arguments['repeat']) as $step) {
201
                $_suite->addTest($suite);
202
            }
203
204
            $suite = $_suite;
205
206
            unset($_suite);
207
        }
208
209
        $result = $this->createTestResult();
210
211
        $listener       = new TestListenerAdapter;
212
        $listenerNeeded = false;
213
214
        foreach ($this->extensions as $extension) {
215
            if ($extension instanceof TestHook) {
216
                $listener->add($extension);
217
218
                $listenerNeeded = true;
219
            }
220
        }
221
222
        if ($listenerNeeded) {
223
            $result->addListener($listener);
224
        }
225
226
        unset($listener, $listenerNeeded);
227
228
        if (!$arguments['convertDeprecationsToExceptions']) {
229
            $result->convertDeprecationsToExceptions(false);
230
        }
231
232
        if (!$arguments['convertErrorsToExceptions']) {
233
            $result->convertErrorsToExceptions(false);
234
        }
235
236
        if (!$arguments['convertNoticesToExceptions']) {
237
            $result->convertNoticesToExceptions(false);
238
        }
239
240
        if (!$arguments['convertWarningsToExceptions']) {
241
            $result->convertWarningsToExceptions(false);
242
        }
243
244
        if ($arguments['stopOnError']) {
245
            $result->stopOnError(true);
246
        }
247
248
        if ($arguments['stopOnFailure']) {
249
            $result->stopOnFailure(true);
250
        }
251
252
        if ($arguments['stopOnWarning']) {
253
            $result->stopOnWarning(true);
254
        }
255
256
        if ($arguments['stopOnIncomplete']) {
257
            $result->stopOnIncomplete(true);
258
        }
259
260
        if ($arguments['stopOnRisky']) {
261
            $result->stopOnRisky(true);
262
        }
263
264
        if ($arguments['stopOnSkipped']) {
265
            $result->stopOnSkipped(true);
266
        }
267
268
        if ($arguments['stopOnDefect']) {
269
            $result->stopOnDefect(true);
270
        }
271
272
        if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) {
273
            $result->setRegisterMockObjectsFromTestArgumentsRecursively(true);
274
        }
275
276
        if ($this->printer === null) {
277
            if (isset($arguments['printer'])) {
278
                if ($arguments['printer'] instanceof ResultPrinter) {
279
                    $this->printer = $arguments['printer'];
280
                } elseif (\is_string($arguments['printer']) && \class_exists($arguments['printer'], false)) {
281
                    try {
282
                        $reflector = new \ReflectionClass($arguments['printer']);
283
284
                        if ($reflector->implementsInterface(ResultPrinter::class)) {
285
                            $this->printer = $this->createPrinter($arguments['printer'], $arguments);
286
                        }
287
288
                        // @codeCoverageIgnoreStart
289
                    } catch (\ReflectionException $e) {
290
                        throw new Exception(
291
                            $e->getMessage(),
292
                            (int) $e->getCode(),
293
                            $e
294
                        );
295
                    }
296
                    // @codeCoverageIgnoreEnd
297
                }
298
            } else {
299
                $this->printer = $this->createPrinter(DefaultResultPrinter::class, $arguments);
300
            }
301
        }
302
303
        if (isset($originalExecutionOrder) && $this->printer instanceof CliTestDoxPrinter) {
304
            \assert($this->printer instanceof CliTestDoxPrinter);
305
306
            $this->printer->setOriginalExecutionOrder($originalExecutionOrder);
307
            $this->printer->setShowProgressAnimation(!$arguments['noInteraction']);
308
        }
309
310
        $this->printer->write(
311
            Version::getVersionString() . "\n"
312
        );
313
314
        self::$versionStringPrinted = true;
315
316
        if ($arguments['verbose']) {
317
            $this->writeMessage('Runtime', $this->runtime->getNameWithVersionAndCodeCoverageDriver());
318
319
            if (isset($arguments['configuration'])) {
320
                \assert($arguments['configuration'] instanceof Configuration);
321
322
                $this->writeMessage(
323
                    'Configuration',
324
                    $arguments['configuration']->filename()
325
                );
326
            }
327
328
            foreach ($arguments['loadedExtensions'] as $extension) {
329
                $this->writeMessage(
330
                    'Extension',
331
                    $extension
332
                );
333
            }
334
335
            foreach ($arguments['notLoadedExtensions'] as $extension) {
336
                $this->writeMessage(
337
                    'Extension',
338
                    $extension
339
                );
340
            }
341
        }
342
343
        foreach ($warnings as $warning) {
344
            $this->writeMessage('Warning', $warning);
345
        }
346
347
        if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) {
348
            $this->writeMessage(
349
                'Random seed',
350
                (string) $arguments['randomOrderSeed']
351
            );
352
        }
353
354
        if (isset($tooFewColumnsRequested)) {
355
            $this->writeMessage('Error', 'Less than 16 columns requested, number of columns set to 16');
356
        }
357
358
        if ($this->runtime->discardsComments()) {
359
            $this->writeMessage('Warning', 'opcache.save_comments=0 set; annotations will not work');
360
        }
361
362
        if (isset($arguments['configuration'])) {
363
            \assert($arguments['configuration'] instanceof Configuration);
364
365
            if ($arguments['configuration']->hasValidationErrors()) {
366
                $this->write(
367
                    "\n  Warning - The configuration file did not pass validation!\n  The following problems have been detected:\n"
368
                );
369
370
                foreach ($arguments['configuration']->validationErrors() as $line => $errors) {
371
                    $this->write(\sprintf("\n  Line %d:\n", $line));
372
373
                    foreach ($errors as $msg) {
374
                        $this->write(\sprintf("  - %s\n", $msg));
375
                    }
376
                }
377
378
                $this->write("\n  Test results may not be as expected.\n\n");
379
            }
380
        }
381
382
        if (isset($arguments['conflictBetweenPrinterClassAndTestdox'])) {
383
            $this->writeMessage('Warning', 'Directives printerClass and testdox are mutually exclusive');
384
        }
385
386
        foreach ($arguments['listeners'] as $listener) {
387
            $result->addListener($listener);
388
        }
389
390
        $result->addListener($this->printer);
391
392
        $codeCoverageReports = 0;
393
394
        if (!isset($arguments['noLogging'])) {
395
            if (isset($arguments['testdoxHTMLFile'])) {
396
                $result->addListener(
397
                    new HtmlResultPrinter(
398
                        $arguments['testdoxHTMLFile'],
399
                        $arguments['testdoxGroups'],
400
                        $arguments['testdoxExcludeGroups']
401
                    )
402
                );
403
            }
404
405
            if (isset($arguments['testdoxTextFile'])) {
406
                $result->addListener(
407
                    new TextResultPrinter(
408
                        $arguments['testdoxTextFile'],
409
                        $arguments['testdoxGroups'],
410
                        $arguments['testdoxExcludeGroups']
411
                    )
412
                );
413
            }
414
415
            if (isset($arguments['testdoxXMLFile'])) {
416
                $result->addListener(
417
                    new XmlResultPrinter(
418
                        $arguments['testdoxXMLFile']
419
                    )
420
                );
421
            }
422
423
            if (isset($arguments['teamcityLogfile'])) {
424
                $result->addListener(
425
                    new TeamCity($arguments['teamcityLogfile'])
426
                );
427
            }
428
429
            if (isset($arguments['junitLogfile'])) {
430
                $result->addListener(
431
                    new JUnit(
432
                        $arguments['junitLogfile'],
433
                        $arguments['reportUselessTests']
434
                    )
435
                );
436
            }
437
438
            if (isset($arguments['coverageClover'])) {
439
                $codeCoverageReports++;
440
            }
441
442
            if (isset($arguments['coverageCrap4J'])) {
443
                $codeCoverageReports++;
444
            }
445
446
            if (isset($arguments['coverageHtml'])) {
447
                $codeCoverageReports++;
448
            }
449
450
            if (isset($arguments['coveragePHP'])) {
451
                $codeCoverageReports++;
452
            }
453
454
            if (isset($arguments['coverageText'])) {
455
                $codeCoverageReports++;
456
            }
457
458
            if (isset($arguments['coverageXml'])) {
459
                $codeCoverageReports++;
460
            }
461
        }
462
463
        if (isset($arguments['noCoverage'])) {
464
            $codeCoverageReports = 0;
465
        }
466
467
        if ($codeCoverageReports > 0 && !$this->runtime->canCollectCodeCoverage()) {
468
            $this->writeMessage('Error', 'No code coverage driver is available');
469
470
            $codeCoverageReports = 0;
471
        }
472
473
        if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) {
474
            $whitelistFromConfigurationFile = false;
475
            $whitelistFromOption            = false;
476
477
            if (isset($arguments['whitelist'])) {
478
                if (!\is_array($arguments['whitelist'])) {
479
                    $whitelistDirectories = [$arguments['whitelist']];
480
                } else {
481
                    $whitelistDirectories = $arguments['whitelist'];
482
                }
483
484
                foreach ($whitelistDirectories as $whitelistDirectory) {
485
                    $this->codeCoverageFilter->addDirectoryToWhitelist($whitelistDirectory);
486
                }
487
488
                $whitelistFromOption = true;
489
            }
490
491
            if (isset($arguments['configuration'])) {
492
                \assert($arguments['configuration'] instanceof Configuration);
493
494
                $filterConfiguration = $arguments['configuration']->filter();
495
496
                if ($filterConfiguration->hasNonEmptyWhitelist()) {
497
                    $whitelistFromConfigurationFile = true;
498
499
                    foreach ($filterConfiguration->directories() as $directory) {
500
                        $this->codeCoverageFilter->addDirectoryToWhitelist(
501
                            $directory->path(),
502
                            $directory->suffix(),
503
                            $directory->prefix()
504
                        );
505
                    }
506
507
                    foreach ($filterConfiguration->files() as $file) {
508
                        $this->codeCoverageFilter->addFileToWhitelist($file->path());
509
                    }
510
511
                    foreach ($filterConfiguration->excludeDirectories() as $directory) {
512
                        $this->codeCoverageFilter->removeDirectoryFromWhitelist(
513
                            $directory->path(),
514
                            $directory->suffix(),
515
                            $directory->prefix()
516
                        );
517
                    }
518
519
                    foreach ($filterConfiguration->excludeFiles() as $file) {
520
                        $this->codeCoverageFilter->removeFileFromWhitelist($file->path());
521
                    }
522
                }
523
            }
524
        }
525
526
        if ($codeCoverageReports > 0) {
527
            try {
528
                $codeCoverage = new CodeCoverage(
529
                    null,
530
                    $this->codeCoverageFilter
531
                );
532
533
                $codeCoverage->setUnintentionallyCoveredSubclassesWhitelist(
534
                    [Comparator::class]
535
                );
536
537
                $codeCoverage->setCheckForUnintentionallyCoveredCode(
538
                    $arguments['strictCoverage']
539
                );
540
541
                $codeCoverage->setCheckForMissingCoversAnnotation(
542
                    $arguments['strictCoverage']
543
                );
544
545
                if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) {
546
                    $codeCoverage->setIgnoreDeprecatedCode(
547
                        $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']
548
                    );
549
                }
550
551
                if (isset($arguments['disableCodeCoverageIgnore']) && $arguments['disableCodeCoverageIgnore'] === true) {
552
                    $codeCoverage->setDisableIgnoredLines(true);
553
                }
554
555
                if (isset($arguments['configuration'])) {
556
                    \assert($arguments['configuration'] instanceof Configuration);
557
558
                    $filterConfiguration = $arguments['configuration']->filter();
559
560
                    if ($filterConfiguration->hasNonEmptyWhitelist()) {
561
                        $codeCoverage->setAddUncoveredFilesFromWhitelist(
562
                            $filterConfiguration->addUncoveredFilesFromWhitelist()
563
                        );
564
565
                        $codeCoverage->setProcessUncoveredFilesFromWhitelist(
566
                            $filterConfiguration->processUncoveredFilesFromWhitelist()
567
                        );
568
                    }
569
                }
570
571
                if (!$this->codeCoverageFilter->hasWhitelist()) {
572
                    if (!$whitelistFromConfigurationFile && !$whitelistFromOption) {
573
                        $this->writeMessage('Error', 'No whitelist is configured, no code coverage will be generated.');
574
                    } else {
575
                        $this->writeMessage('Error', 'Incorrect whitelist config, no code coverage will be generated.');
576
                    }
577
578
                    $codeCoverageReports = 0;
579
580
                    unset($codeCoverage);
581
                }
582
            } catch (CodeCoverageException $e) {
583
                $this->writeMessage('Error', $e->getMessage());
584
585
                $codeCoverageReports = 0;
586
            }
587
        }
588
589
        if (isset($arguments['xdebugFilterFile'], $filterConfiguration)) {
590
            $this->write("\n");
591
592
            $script = (new XdebugFilterScriptGenerator)->generate($filterConfiguration);
593
594
            if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(\dirname($arguments['xdebugFilterFile']))) {
595
                $this->write(\sprintf('Cannot write Xdebug filter script to %s ' . \PHP_EOL, $arguments['xdebugFilterFile']));
596
597
                exit(self::EXCEPTION_EXIT);
598
            }
599
600
            \file_put_contents($arguments['xdebugFilterFile'], $script);
601
602
            $this->write(\sprintf('Wrote Xdebug filter script to %s ' . \PHP_EOL, $arguments['xdebugFilterFile']));
603
604
            exit(self::SUCCESS_EXIT);
605
        }
606
607
        $this->printer->write("\n");
608
609
        if (isset($codeCoverage)) {
610
            $result->setCodeCoverage($codeCoverage);
611
612
            if ($codeCoverageReports > 1 && isset($arguments['cacheTokens'])) {
613
                $codeCoverage->setCacheTokens($arguments['cacheTokens']);
614
            }
615
        }
616
617
        $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']);
618
        $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']);
619
        $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']);
620
        $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']);
621
622
        if ($arguments['enforceTimeLimit'] === true && !(new Invoker)->canInvokeWithTimeout()) {
623
            $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits');
624
        }
625
626
        $result->enforceTimeLimit($arguments['enforceTimeLimit']);
627
        $result->setDefaultTimeLimit($arguments['defaultTimeLimit']);
628
        $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']);
629
        $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']);
630
        $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']);
631
632
        if (isset($arguments['forceCoversAnnotation']) && $arguments['forceCoversAnnotation'] === true) {
633
            $result->forceCoversAnnotation();
634
        }
635
636
        $this->processSuiteFilters($suite, $arguments);
637
        $suite->setRunTestInSeparateProcess($arguments['processIsolation']);
638
639
        foreach ($this->extensions as $extension) {
640
            if ($extension instanceof BeforeFirstTestHook) {
641
                $extension->executeBeforeFirstTest();
642
            }
643
        }
644
645
        $testSuiteWarningsPrinted = false;
646
647
        foreach ($suite->warnings() as $warning) {
648
            $this->writeMessage('Warning', $warning);
649
650
            $testSuiteWarningsPrinted = true;
651
        }
652
653
        if ($testSuiteWarningsPrinted) {
654
            $this->write(\PHP_EOL);
655
        }
656
657
        $suite->run($result);
658
659
        foreach ($this->extensions as $extension) {
660
            if ($extension instanceof AfterLastTestHook) {
661
                $extension->executeAfterLastTest();
662
            }
663
        }
664
665
        $result->flushListeners();
666
        $this->printer->printResult($result);
667
668
        if (isset($codeCoverage)) {
669
            if (isset($arguments['coverageClover'])) {
670
                $this->codeCoverageGenerationStart('Clover XML');
671
672
                try {
673
                    $writer = new CloverReport;
674
                    $writer->process($codeCoverage, $arguments['coverageClover']);
675
676
                    $this->codeCoverageGenerationSucceeded();
677
678
                    unset($writer);
679
                } catch (CodeCoverageException $e) {
680
                    $this->codeCoverageGenerationFailed($e);
681
                }
682
            }
683
684
            if (isset($arguments['coverageCrap4J'])) {
685
                $this->codeCoverageGenerationStart('Crap4J XML');
686
687
                try {
688
                    $writer = new Crap4jReport($arguments['crap4jThreshold']);
689
                    $writer->process($codeCoverage, $arguments['coverageCrap4J']);
690
691
                    $this->codeCoverageGenerationSucceeded();
692
693
                    unset($writer);
694
                } catch (CodeCoverageException $e) {
695
                    $this->codeCoverageGenerationFailed($e);
696
                }
697
            }
698
699
            if (isset($arguments['coverageHtml'])) {
700
                $this->codeCoverageGenerationStart('HTML');
701
702
                try {
703
                    $writer = new HtmlReport(
704
                        $arguments['reportLowUpperBound'],
705
                        $arguments['reportHighLowerBound'],
706
                        \sprintf(
707
                            ' and <a href="https://phpunit.de/">PHPUnit %s</a>',
708
                            Version::id()
709
                        )
710
                    );
711
712
                    $writer->process($codeCoverage, $arguments['coverageHtml']);
713
714
                    $this->codeCoverageGenerationSucceeded();
715
716
                    unset($writer);
717
                } catch (CodeCoverageException $e) {
718
                    $this->codeCoverageGenerationFailed($e);
719
                }
720
            }
721
722
            if (isset($arguments['coveragePHP'])) {
723
                $this->codeCoverageGenerationStart('PHP');
724
725
                try {
726
                    $writer = new PhpReport;
727
                    $writer->process($codeCoverage, $arguments['coveragePHP']);
728
729
                    $this->codeCoverageGenerationSucceeded();
730
731
                    unset($writer);
732
                } catch (CodeCoverageException $e) {
733
                    $this->codeCoverageGenerationFailed($e);
734
                }
735
            }
736
737
            if (isset($arguments['coverageText'])) {
738
                if ($arguments['coverageText'] === 'php://stdout') {
739
                    $outputStream = $this->printer;
740
                    $colors       = $arguments['colors'] && $arguments['colors'] !== DefaultResultPrinter::COLOR_NEVER;
741
                } else {
742
                    $outputStream = new Printer($arguments['coverageText']);
743
                    $colors       = false;
744
                }
745
746
                $processor = new TextReport(
747
                    $arguments['reportLowUpperBound'],
748
                    $arguments['reportHighLowerBound'],
749
                    $arguments['coverageTextShowUncoveredFiles'],
750
                    $arguments['coverageTextShowOnlySummary']
751
                );
752
753
                $outputStream->write(
754
                    $processor->process($codeCoverage, $colors)
755
                );
756
            }
757
758
            if (isset($arguments['coverageXml'])) {
759
                $this->codeCoverageGenerationStart('PHPUnit XML');
760
761
                try {
762
                    $writer = new XmlReport(Version::id());
763
                    $writer->process($codeCoverage, $arguments['coverageXml']);
764
765
                    $this->codeCoverageGenerationSucceeded();
766
767
                    unset($writer);
768
                } catch (CodeCoverageException $e) {
769
                    $this->codeCoverageGenerationFailed($e);
770
                }
771
            }
772
        }
773
774
        if ($exit) {
775
            if ($result->wasSuccessfulIgnoringWarnings()) {
776
                if ($arguments['failOnRisky'] && !$result->allHarmless()) {
777
                    exit(self::FAILURE_EXIT);
778
                }
779
780
                if ($arguments['failOnWarning'] && $result->warningCount() > 0) {
781
                    exit(self::FAILURE_EXIT);
782
                }
783
784
                if ($arguments['failOnIncomplete'] && $result->notImplementedCount() > 0) {
785
                    exit(self::FAILURE_EXIT);
786
                }
787
788
                if ($arguments['failOnSkipped'] && $result->skippedCount() > 0) {
789
                    exit(self::FAILURE_EXIT);
790
                }
791
792
                exit(self::SUCCESS_EXIT);
793
            }
794
795
            if ($result->errorCount() > 0) {
796
                exit(self::EXCEPTION_EXIT);
797
            }
798
799
            if ($result->failureCount() > 0) {
800
                exit(self::FAILURE_EXIT);
801
            }
802
        }
803
804
        return $result;
805
    }
806
807
    /**
808
     * Returns the loader to be used.
809
     */
810
    public function getLoader(): TestSuiteLoader
811
    {
812
        if ($this->loader === null) {
813
            $this->loader = new StandardTestSuiteLoader;
814
        }
815
816
        return $this->loader;
817
    }
818
819
    public function addExtension(Hook $extension): void
820
    {
821
        $this->extensions[] = $extension;
822
    }
823
824
    /**
825
     * Override to define how to handle a failed loading of
826
     * a test suite.
827
     */
828
    protected function runFailed(string $message): void
829
    {
830
        $this->write($message . \PHP_EOL);
831
832
        exit(self::FAILURE_EXIT);
833
    }
834
835
    private function createTestResult(): TestResult
836
    {
837
        return new TestResult;
838
    }
839
840
    private function write(string $buffer): void
841
    {
842
        if (\PHP_SAPI !== 'cli' && \PHP_SAPI !== 'phpdbg') {
843
            $buffer = \htmlspecialchars($buffer);
844
        }
845
846
        if ($this->printer !== null) {
847
            $this->printer->write($buffer);
848
        } else {
849
            print $buffer;
850
        }
851
    }
852
853
    /**
854
     * @throws Exception
855
     */
856
    private function handleConfiguration(array &$arguments): void
857
    {
858
        if (isset($arguments['configuration']) &&
859
            !$arguments['configuration'] instanceof Configuration) {
860
            $arguments['configuration'] = Registry::getInstance()->get($arguments['configuration']);
861
        }
862
863
        $arguments['debug']     = $arguments['debug'] ?? false;
864
        $arguments['filter']    = $arguments['filter'] ?? false;
865
        $arguments['listeners'] = $arguments['listeners'] ?? [];
866
867
        if (isset($arguments['configuration'])) {
868
            (new PhpHandler)->handle($arguments['configuration']->php());
869
870
            $phpunitConfiguration = $arguments['configuration']->phpunit();
871
872
            $arguments['backupGlobals']                                   = $arguments['backupGlobals'] ?? $phpunitConfiguration->backupGlobals();
873
            $arguments['backupStaticAttributes']                          = $arguments['backupStaticAttributes'] ?? $phpunitConfiguration->backupStaticAttributes();
874
            $arguments['beStrictAboutChangesToGlobalState']               = $arguments['beStrictAboutChangesToGlobalState'] ?? $phpunitConfiguration->beStrictAboutChangesToGlobalState();
875
            $arguments['cacheResult']                                     = $arguments['cacheResult'] ?? $phpunitConfiguration->cacheResult();
876
            $arguments['cacheTokens']                                     = $arguments['cacheTokens'] ?? $phpunitConfiguration->cacheTokens();
877
            $arguments['colors']                                          = $arguments['colors'] ?? $phpunitConfiguration->colors();
878
            $arguments['convertDeprecationsToExceptions']                 = $arguments['convertDeprecationsToExceptions'] ?? $phpunitConfiguration->convertDeprecationsToExceptions();
879
            $arguments['convertErrorsToExceptions']                       = $arguments['convertErrorsToExceptions'] ?? $phpunitConfiguration->convertErrorsToExceptions();
880
            $arguments['convertNoticesToExceptions']                      = $arguments['convertNoticesToExceptions'] ?? $phpunitConfiguration->convertNoticesToExceptions();
881
            $arguments['convertWarningsToExceptions']                     = $arguments['convertWarningsToExceptions'] ?? $phpunitConfiguration->convertWarningsToExceptions();
882
            $arguments['processIsolation']                                = $arguments['processIsolation'] ?? $phpunitConfiguration->processIsolation();
883
            $arguments['stopOnDefect']                                    = $arguments['stopOnDefect'] ?? $phpunitConfiguration->stopOnDefect();
884
            $arguments['stopOnError']                                     = $arguments['stopOnError'] ?? $phpunitConfiguration->stopOnError();
885
            $arguments['stopOnFailure']                                   = $arguments['stopOnFailure'] ?? $phpunitConfiguration->stopOnFailure();
886
            $arguments['stopOnWarning']                                   = $arguments['stopOnWarning'] ?? $phpunitConfiguration->stopOnWarning();
887
            $arguments['stopOnIncomplete']                                = $arguments['stopOnIncomplete'] ?? $phpunitConfiguration->stopOnIncomplete();
888
            $arguments['stopOnRisky']                                     = $arguments['stopOnRisky'] ?? $phpunitConfiguration->stopOnRisky();
889
            $arguments['stopOnSkipped']                                   = $arguments['stopOnSkipped'] ?? $phpunitConfiguration->stopOnSkipped();
890
            $arguments['failOnIncomplete']                                = $arguments['failOnIncomplete'] ?? $phpunitConfiguration->failOnIncomplete();
891
            $arguments['failOnRisky']                                     = $arguments['failOnRisky'] ?? $phpunitConfiguration->failOnRisky();
892
            $arguments['failOnSkipped']                                   = $arguments['failOnSkipped'] ?? $phpunitConfiguration->failOnSkipped();
893
            $arguments['failOnWarning']                                   = $arguments['failOnWarning'] ?? $phpunitConfiguration->failOnWarning();
894
            $arguments['enforceTimeLimit']                                = $arguments['enforceTimeLimit'] ?? $phpunitConfiguration->enforceTimeLimit();
895
            $arguments['defaultTimeLimit']                                = $arguments['defaultTimeLimit'] ?? $phpunitConfiguration->defaultTimeLimit();
896
            $arguments['timeoutForSmallTests']                            = $arguments['timeoutForSmallTests'] ?? $phpunitConfiguration->timeoutForSmallTests();
897
            $arguments['timeoutForMediumTests']                           = $arguments['timeoutForMediumTests'] ?? $phpunitConfiguration->timeoutForMediumTests();
898
            $arguments['timeoutForLargeTests']                            = $arguments['timeoutForLargeTests'] ?? $phpunitConfiguration->timeoutForLargeTests();
899
            $arguments['reportUselessTests']                              = $arguments['reportUselessTests'] ?? $phpunitConfiguration->beStrictAboutTestsThatDoNotTestAnything();
900
            $arguments['strictCoverage']                                  = $arguments['strictCoverage'] ?? $phpunitConfiguration->beStrictAboutCoversAnnotation();
901
            $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']       = $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ?? $phpunitConfiguration->ignoreDeprecatedCodeUnitsFromCodeCoverage();
902
            $arguments['disallowTestOutput']                              = $arguments['disallowTestOutput'] ?? $phpunitConfiguration->beStrictAboutOutputDuringTests();
903
            $arguments['disallowTodoAnnotatedTests']                      = $arguments['disallowTodoAnnotatedTests'] ?? $phpunitConfiguration->beStrictAboutTodoAnnotatedTests();
904
            $arguments['beStrictAboutResourceUsageDuringSmallTests']      = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? $phpunitConfiguration->beStrictAboutResourceUsageDuringSmallTests();
905
            $arguments['verbose']                                         = $arguments['verbose'] ?? $phpunitConfiguration->verbose();
906
            $arguments['reverseDefectList']                               = $arguments['reverseDefectList'] ?? $phpunitConfiguration->reverseDefectList();
907
            $arguments['forceCoversAnnotation']                           = $arguments['forceCoversAnnotation'] ?? $phpunitConfiguration->forceCoversAnnotation();
908
            $arguments['disableCodeCoverageIgnore']                       = $arguments['disableCodeCoverageIgnore'] ?? $phpunitConfiguration->disableCodeCoverageIgnore();
909
            $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? $phpunitConfiguration->registerMockObjectsFromTestArgumentsRecursively();
910
            $arguments['noInteraction']                                   = $arguments['noInteraction'] ?? $phpunitConfiguration->noInteraction();
911
            $arguments['executionOrder']                                  = $arguments['executionOrder'] ?? $phpunitConfiguration->executionOrder();
912
            $arguments['resolveDependencies']                             = $arguments['resolveDependencies'] ?? $phpunitConfiguration->resolveDependencies();
913
914
            if (!isset($arguments['bootstrap']) && $phpunitConfiguration->hasBootstrap()) {
915
                $arguments['bootstrap'] = $phpunitConfiguration->bootstrap();
916
            }
917
918
            if (!isset($arguments['cacheResultFile']) && $phpunitConfiguration->hasCacheResultFile()) {
919
                $arguments['cacheResultFile'] = $phpunitConfiguration->cacheResultFile();
920
            }
921
922
            if (!isset($arguments['executionOrderDefects'])) {
923
                $arguments['executionOrderDefects'] = $phpunitConfiguration->defectsFirst() ? TestSuiteSorter::ORDER_DEFECTS_FIRST : TestSuiteSorter::ORDER_DEFAULT;
924
            }
925
926
            if ($phpunitConfiguration->conflictBetweenPrinterClassAndTestdox()) {
927
                $arguments['conflictBetweenPrinterClassAndTestdox'] = true;
928
            }
929
930
            $groupCliArgs = [];
931
932
            if (!empty($arguments['groups'])) {
933
                $groupCliArgs = $arguments['groups'];
934
            }
935
936
            $groupConfiguration = $arguments['configuration']->groups();
937
938
            if (!isset($arguments['groups']) && $groupConfiguration->hasInclude()) {
939
                $arguments['groups'] = $groupConfiguration->include()->asArrayOfStrings();
940
            }
941
942
            if (!isset($arguments['excludeGroups']) && $groupConfiguration->hasExclude()) {
943
                $arguments['excludeGroups'] = \array_diff($groupConfiguration->exclude()->asArrayOfStrings(), $groupCliArgs);
944
            }
945
946
            $extensionHandler = new ExtensionHandler;
947
948
            foreach ($arguments['configuration']->extensions() as $extension) {
949
                $this->addExtension($extensionHandler->createHookInstance($extension));
950
            }
951
952
            foreach ($arguments['configuration']->listeners() as $listener) {
953
                $arguments['listeners'][] = $extensionHandler->createTestListenerInstance($listener);
954
            }
955
956
            unset($extensionHandler);
957
958
            foreach ($arguments['unavailableExtensions'] as $extension) {
959
                $arguments['warnings'][] = \sprintf(
960
                    'Extension "%s" is not available',
961
                    $extension
962
                );
963
            }
964
965
            $loggingConfiguration = $arguments['configuration']->logging();
966
967
            if (!isset($arguments['coverageClover']) && $loggingConfiguration->hasCodeCoverageClover()) {
968
                $arguments['coverageClover'] = $loggingConfiguration->codeCoverageClover()->target()->path();
969
            }
970
971
            if (!isset($arguments['coverageCrap4J']) && $loggingConfiguration->hasCodeCoverageCrap4j()) {
972
                $arguments['coverageCrap4J'] = $loggingConfiguration->codeCoverageCrap4j()->target()->path();
973
974
                if (!isset($arguments['crap4jThreshold'])) {
975
                    $arguments['crap4jThreshold'] = $loggingConfiguration->codeCoverageCrap4j()->threshold();
976
                }
977
            }
978
979
            if (!isset($arguments['coverageHtml']) && $loggingConfiguration->hasCodeCoverageHtml()) {
980
                $arguments['coverageHtml'] = $loggingConfiguration->codeCoverageHtml()->target()->path();
981
982
                if (!isset($arguments['reportLowUpperBound'])) {
983
                    $arguments['reportLowUpperBound'] = $loggingConfiguration->codeCoverageHtml()->lowUpperBound();
984
                }
985
986
                if (!isset($arguments['reportHighLowerBound'])) {
987
                    $arguments['reportHighLowerBound'] = $loggingConfiguration->codeCoverageHtml()->highLowerBound();
988
                }
989
            }
990
991
            if (!isset($arguments['coveragePHP']) && $loggingConfiguration->hasCodeCoveragePhp()) {
992
                $arguments['coveragePHP'] = $loggingConfiguration->codeCoveragePhp()->target()->path();
993
            }
994
995
            if (!isset($arguments['coverageText']) && $loggingConfiguration->hasCodeCoverageText()) {
996
                $arguments['coverageText']                   = $loggingConfiguration->codeCoverageText()->target()->path();
997
                $arguments['coverageTextShowUncoveredFiles'] = $loggingConfiguration->codeCoverageText()->showUncoveredFiles();
998
                $arguments['coverageTextShowOnlySummary']    = $loggingConfiguration->codeCoverageText()->showOnlySummary();
999
            }
1000
1001
            if (!isset($arguments['coverageXml']) && $loggingConfiguration->hasCodeCoverageXml()) {
1002
                $arguments['coverageXml'] = $loggingConfiguration->codeCoverageXml()->target()->path();
1003
            }
1004
1005
            if ($loggingConfiguration->hasPlainText()) {
1006
                $arguments['listeners'][] = new DefaultResultPrinter(
1007
                    $loggingConfiguration->plainText()->target()->path(),
1008
                    true
1009
                );
1010
            }
1011
1012
            if (!isset($arguments['teamcityLogfile']) && $loggingConfiguration->hasTeamCity()) {
1013
                $arguments['teamcityLogfile'] = $loggingConfiguration->teamCity()->target()->path();
1014
            }
1015
1016
            if (!isset($arguments['junitLogfile']) && $loggingConfiguration->hasJunit()) {
1017
                $arguments['junitLogfile'] = $loggingConfiguration->junit()->target()->path();
1018
            }
1019
1020
            if (!isset($arguments['testdoxHTMLFile']) && $loggingConfiguration->hasTestDoxHtml()) {
1021
                $arguments['testdoxHTMLFile'] = $loggingConfiguration->testDoxHtml()->target()->path();
1022
            }
1023
1024
            if (!isset($arguments['testdoxTextFile']) && $loggingConfiguration->hasTestDoxText()) {
1025
                $arguments['testdoxTextFile'] = $loggingConfiguration->testDoxText()->target()->path();
1026
            }
1027
1028
            if (!isset($arguments['testdoxXMLFile']) && $loggingConfiguration->hasTestDoxXml()) {
1029
                $arguments['testdoxXMLFile'] = $loggingConfiguration->testDoxXml()->target()->path();
1030
            }
1031
1032
            $testdoxGroupConfiguration = $arguments['configuration']->testdoxGroups();
1033
1034
            if (!isset($arguments['testdoxGroups']) && $testdoxGroupConfiguration->hasInclude()) {
1035
                $arguments['testdoxGroups'] = $testdoxGroupConfiguration->include()->asArrayOfStrings();
1036
            }
1037
1038
            if (!isset($arguments['testdoxExcludeGroups']) && $testdoxGroupConfiguration->hasExclude()) {
1039
                $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration->exclude()->asArrayOfStrings();
1040
            }
1041
        }
1042
1043
        $extensionHandler = new ExtensionHandler;
1044
1045
        foreach ($arguments['extensions'] as $extension) {
1046
            $this->addExtension($extensionHandler->createHookInstance($extension));
1047
        }
1048
1049
        unset($extensionHandler);
1050
1051
        $arguments['addUncoveredFilesFromWhitelist']                  = $arguments['addUncoveredFilesFromWhitelist'] ?? true;
1052
        $arguments['backupGlobals']                                   = $arguments['backupGlobals'] ?? null;
1053
        $arguments['backupStaticAttributes']                          = $arguments['backupStaticAttributes'] ?? null;
1054
        $arguments['beStrictAboutChangesToGlobalState']               = $arguments['beStrictAboutChangesToGlobalState'] ?? null;
1055
        $arguments['beStrictAboutResourceUsageDuringSmallTests']      = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false;
1056
        $arguments['cacheResult']                                     = $arguments['cacheResult'] ?? true;
1057
        $arguments['cacheTokens']                                     = $arguments['cacheTokens'] ?? false;
1058
        $arguments['colors']                                          = $arguments['colors'] ?? DefaultResultPrinter::COLOR_DEFAULT;
1059
        $arguments['columns']                                         = $arguments['columns'] ?? 80;
1060
        $arguments['convertDeprecationsToExceptions']                 = $arguments['convertDeprecationsToExceptions'] ?? true;
1061
        $arguments['convertErrorsToExceptions']                       = $arguments['convertErrorsToExceptions'] ?? true;
1062
        $arguments['convertNoticesToExceptions']                      = $arguments['convertNoticesToExceptions'] ?? true;
1063
        $arguments['convertWarningsToExceptions']                     = $arguments['convertWarningsToExceptions'] ?? true;
1064
        $arguments['crap4jThreshold']                                 = $arguments['crap4jThreshold'] ?? 30;
1065
        $arguments['disallowTestOutput']                              = $arguments['disallowTestOutput'] ?? false;
1066
        $arguments['disallowTodoAnnotatedTests']                      = $arguments['disallowTodoAnnotatedTests'] ?? false;
1067
        $arguments['defaultTimeLimit']                                = $arguments['defaultTimeLimit'] ?? 0;
1068
        $arguments['enforceTimeLimit']                                = $arguments['enforceTimeLimit'] ?? false;
1069
        $arguments['excludeGroups']                                   = $arguments['excludeGroups'] ?? [];
1070
        $arguments['executionOrder']                                  = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT;
1071
        $arguments['executionOrderDefects']                           = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT;
1072
        $arguments['failOnIncomplete']                                = $arguments['failOnIncomplete'] ?? false;
1073
        $arguments['failOnRisky']                                     = $arguments['failOnRisky'] ?? false;
1074
        $arguments['failOnSkipped']                                   = $arguments['failOnSkipped'] ?? false;
1075
        $arguments['failOnWarning']                                   = $arguments['failOnWarning'] ?? false;
1076
        $arguments['groups']                                          = $arguments['groups'] ?? [];
1077
        $arguments['noInteraction']                                   = $arguments['noInteraction'] ?? false;
1078
        $arguments['processIsolation']                                = $arguments['processIsolation'] ?? false;
1079
        $arguments['processUncoveredFilesFromWhitelist']              = $arguments['processUncoveredFilesFromWhitelist'] ?? false;
1080
        $arguments['randomOrderSeed']                                 = $arguments['randomOrderSeed'] ?? \time();
1081
        $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false;
1082
        $arguments['repeat']                                          = $arguments['repeat'] ?? false;
1083
        $arguments['reportHighLowerBound']                            = $arguments['reportHighLowerBound'] ?? 90;
1084
        $arguments['reportLowUpperBound']                             = $arguments['reportLowUpperBound'] ?? 50;
1085
        $arguments['reportUselessTests']                              = $arguments['reportUselessTests'] ?? true;
1086
        $arguments['reverseList']                                     = $arguments['reverseList'] ?? false;
1087
        $arguments['resolveDependencies']                             = $arguments['resolveDependencies'] ?? true;
1088
        $arguments['stopOnError']                                     = $arguments['stopOnError'] ?? false;
1089
        $arguments['stopOnFailure']                                   = $arguments['stopOnFailure'] ?? false;
1090
        $arguments['stopOnIncomplete']                                = $arguments['stopOnIncomplete'] ?? false;
1091
        $arguments['stopOnRisky']                                     = $arguments['stopOnRisky'] ?? false;
1092
        $arguments['stopOnSkipped']                                   = $arguments['stopOnSkipped'] ?? false;
1093
        $arguments['stopOnWarning']                                   = $arguments['stopOnWarning'] ?? false;
1094
        $arguments['stopOnDefect']                                    = $arguments['stopOnDefect'] ?? false;
1095
        $arguments['strictCoverage']                                  = $arguments['strictCoverage'] ?? false;
1096
        $arguments['testdoxExcludeGroups']                            = $arguments['testdoxExcludeGroups'] ?? [];
1097
        $arguments['testdoxGroups']                                   = $arguments['testdoxGroups'] ?? [];
1098
        $arguments['timeoutForLargeTests']                            = $arguments['timeoutForLargeTests'] ?? 60;
1099
        $arguments['timeoutForMediumTests']                           = $arguments['timeoutForMediumTests'] ?? 10;
1100
        $arguments['timeoutForSmallTests']                            = $arguments['timeoutForSmallTests'] ?? 1;
1101
        $arguments['verbose']                                         = $arguments['verbose'] ?? false;
1102
    }
1103
1104
    private function processSuiteFilters(TestSuite $suite, array $arguments): void
1105
    {
1106
        if (!$arguments['filter'] &&
1107
            empty($arguments['groups']) &&
1108
            empty($arguments['excludeGroups'])) {
1109
            return;
1110
        }
1111
1112
        $filterFactory = new Factory;
1113
1114
        if (!empty($arguments['excludeGroups'])) {
1115
            $filterFactory->addFilter(
1116
                new \ReflectionClass(ExcludeGroupFilterIterator::class),
1117
                $arguments['excludeGroups']
1118
            );
1119
        }
1120
1121
        if (!empty($arguments['groups'])) {
1122
            $filterFactory->addFilter(
1123
                new \ReflectionClass(IncludeGroupFilterIterator::class),
1124
                $arguments['groups']
1125
            );
1126
        }
1127
1128
        if ($arguments['filter']) {
1129
            $filterFactory->addFilter(
1130
                new \ReflectionClass(NameFilterIterator::class),
1131
                $arguments['filter']
1132
            );
1133
        }
1134
1135
        $suite->injectFilter($filterFactory);
1136
    }
1137
1138
    private function writeMessage(string $type, string $message): void
1139
    {
1140
        if (!$this->messagePrinted) {
1141
            $this->write("\n");
1142
        }
1143
1144
        $this->write(
1145
            \sprintf(
1146
                "%-15s%s\n",
1147
                $type . ':',
1148
                $message
1149
            )
1150
        );
1151
1152
        $this->messagePrinted = true;
1153
    }
1154
1155
    private function createPrinter(string $class, array $arguments): ResultPrinter
1156
    {
1157
        $object = new $class(
1158
            (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null,
1159
            $arguments['verbose'],
1160
            $arguments['colors'],
1161
            $arguments['debug'],
1162
            $arguments['columns'],
1163
            $arguments['reverseList']
1164
        );
1165
1166
        \assert($object instanceof ResultPrinter);
1167
1168
        return $object;
1169
    }
1170
1171
    private function codeCoverageGenerationStart(string $format): void
1172
    {
1173
        $this->printer->write(
1174
            \sprintf(
1175
                "\nGenerating code coverage report in %s format ... ",
1176
                $format
1177
            )
1178
        );
1179
1180
        $this->timer->start();
1181
    }
1182
1183
    private function codeCoverageGenerationSucceeded(): void
1184
    {
1185
        $this->printer->write(
1186
            \sprintf(
1187
                "done [%s]\n",
1188
                $this->timer->stop()->asString()
1189
            )
1190
        );
1191
    }
1192
1193
    private function codeCoverageGenerationFailed(\Exception $e): void
1194
    {
1195
        $this->printer->write(
1196
            \sprintf(
1197
                "failed [%s]\n%s\n",
1198
                $this->timer->stop()->asString(),
1199
                $e->getMessage()
1200
            )
1201
        );
1202
    }
1203
}
1204