Passed
Push — master ( cbf6d3...d62b89 )
by Michiel
08:56
created

PHPUnitTask::handlePHPUnitConfiguration()   B

Complexity

Conditions 11
Paths 25

Size

Total Lines 58
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 35
dl 0
loc 58
ccs 0
cts 34
cp 0
rs 7.3166
c 2
b 0
f 0
cc 11
nc 25
nop 1
crap 132

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\FileWriter;
22
use Phing\Io\LogWriter;
23
use Phing\Io\File;
24
use Phing\Task;
25
use PHPUnit\TextUI\XmlConfiguration\Loader;
26
27
/**
28
 * Runs PHPUnit tests.
29
 *
30
 * @author  Michiel Rook <[email protected]>
31
 * @package phing.tasks.ext.phpunit
32
 * @see     BatchTest
33
 * @since   2.1.0
34
 */
35
class PHPUnitTask extends Task
36
{
37
    private $batchtests = [];
38
    /**
39
     * @var FormatterElement[] $formatters
40
     */
41
    private $formatters = [];
42
    private $bootstrap = "";
43
    private $haltonerror = false;
44
    private $haltonfailure = false;
45
    private $haltonincomplete = false;
46
    private $haltonskipped = false;
47
    private $haltondefect = false;
48
    private $haltonwarning = false;
49
    private $haltonrisky = false;
50
    private $errorproperty;
51
    private $failureproperty;
52
    private $incompleteproperty;
53
    private $skippedproperty;
54
    private $warningproperty;
55
    private $riskyproperty;
56
    private $printsummary = false;
57
    private $testfailed = false;
58
    private $testfailuremessage = "";
59
    private $codecoverage = null;
60
    private $groups = [];
61
    private $excludeGroups = [];
62
    private $processIsolation = false;
63
    private $usecustomerrorhandler = true;
64
    private $listeners = [];
65
66
    /**
67
     * @var string
68
     */
69
    private $pharLocation = "";
70
71
    /**
72
     * @var File
73
     */
74
    private $configuration = null;
75
76
    /**
77
     * @var \PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage
78
     */
79
    private $codeCoverageConfig = null;
80
81
    /**
82
     * Initialize Task.
83
     * This method includes any necessary PHPUnit libraries and triggers
84
     * appropriate error if they cannot be found.  This is not done in header
85
     * because we may want this class to be loaded w/o triggering an error.
86
     */
87 4
    public function init()
88
    {
89 4
    }
90
91 4
    private function loadPHPUnit()
92
    {
93 4
        if (!empty($this->pharLocation)) {
94
            $GLOBALS['_SERVER']['SCRIPT_NAME'] = '-';
95
            ob_start();
96
            @include $this->pharLocation;
97
            ob_end_clean();
98
        }
99
100 4
        if (!class_exists('PHPUnit\Runner\Version')) {
101
            throw new BuildException("PHPUnitTask requires PHPUnit to be installed", $this->getLocation());
102
        }
103
104
        if (
105 4
            class_exists('\PHPUnit\Runner\Version', false) &&
106 4
            version_compare(\PHPUnit\Runner\Version::id(), '9.0.0', '<')
107
        ) {
108
            throw new BuildException("Phing only supports PHPUnit 9+");
109
        }
110 4
    }
111
112
    /**
113
     * Sets the name of a bootstrap file that is run before
114
     * executing the tests
115
     *
116
     * @param string $bootstrap the name of the bootstrap file
117
     */
118 1
    public function setBootstrap($bootstrap)
119
    {
120 1
        $this->bootstrap = $bootstrap;
121 1
    }
122
123
    /**
124
     * @param $value
125
     */
126
    public function setErrorproperty($value)
127
    {
128
        $this->errorproperty = $value;
129
    }
130
131
    /**
132
     * @param $value
133
     */
134
    public function setFailureproperty($value)
135
    {
136
        $this->failureproperty = $value;
137
    }
138
139
    /**
140
     * @param $value
141
     */
142
    public function setIncompleteproperty($value)
143
    {
144
        $this->incompleteproperty = $value;
145
    }
146
147
    /**
148
     * @param $value
149
     */
150
    public function setSkippedproperty($value)
151
    {
152
        $this->skippedproperty = $value;
153
    }
154
155
    /**
156
     * @param $value
157
     */
158
    public function setRiskyproperty($value)
159
    {
160
        $this->riskyproperty = $value;
161
    }
162
163
    /**
164
     * @param $value
165
     */
166
    public function setWarningproperty($value)
167
    {
168
        $this->riskyproperty = $value;
169
    }
170
171
    /**
172
     * @return bool
173
     */
174
    public function getHaltondefect(): bool
175
    {
176
        return $this->haltondefect;
177
    }
178
179
    /**
180
     * @param bool $haltondefect
181
     */
182
    public function setHaltondefect(bool $haltondefect): void
183
    {
184
        $this->haltondefect = $haltondefect;
185
    }
186
187
    /**
188
     * @return bool
189
     */
190
    public function getHaltonwarning(): bool
191
    {
192
        return $this->haltonwarning;
193
    }
194
195
    /**
196
     * @param bool $haltonwarning
197
     */
198
    public function setHaltonwarning(bool $haltonwarning): void
199
    {
200
        $this->haltonwarning = $haltonwarning;
201
    }
202
203
    /**
204
     * @return bool
205
     */
206
    public function getHaltonrisky(): bool
207
    {
208
        return $this->haltonrisky;
209
    }
210
211
    /**
212
     * @param bool $haltonrisky
213
     */
214
    public function setHaltonrisky(bool $haltonrisky): void
215
    {
216
        $this->haltonrisky = $haltonrisky;
217
    }
218
219
    /**
220
     * @param $value
221
     */
222 3
    public function setHaltonerror($value)
223
    {
224 3
        $this->haltonerror = $value;
225 3
    }
226
227
    /**
228
     * @param $value
229
     */
230 3
    public function setHaltonfailure($value)
231
    {
232 3
        $this->haltonfailure = $value;
233 3
    }
234
235
    /**
236
     * @return bool
237
     */
238
    public function getHaltonfailure()
239
    {
240
        return $this->haltonfailure;
241
    }
242
243
    /**
244
     * @param $value
245
     */
246
    public function setHaltonincomplete($value)
247
    {
248
        $this->haltonincomplete = $value;
249
    }
250
251
    /**
252
     * @return bool
253
     */
254 1
    public function getHaltonincomplete()
255
    {
256 1
        return $this->haltonincomplete;
257
    }
258
259
    /**
260
     * @param $value
261
     */
262
    public function setHaltonskipped($value)
263
    {
264
        $this->haltonskipped = $value;
265
    }
266
267
    /**
268
     * @return bool
269
     */
270 1
    public function getHaltonskipped()
271
    {
272 1
        return $this->haltonskipped;
273
    }
274
275
    /**
276
     * @param $printsummary
277
     */
278 1
    public function setPrintsummary($printsummary)
279
    {
280 1
        $this->printsummary = $printsummary;
281 1
    }
282
283
    /**
284
     * @param $codecoverage
285
     */
286 1
    public function setCodecoverage($codecoverage)
287
    {
288 1
        $this->codecoverage = $codecoverage;
289 1
    }
290
291
    /**
292
     * @param $processIsolation
293
     */
294
    public function setProcessIsolation($processIsolation)
295
    {
296
        $this->processIsolation = $processIsolation;
297
    }
298
299
    /**
300
     * @param $usecustomerrorhandler
301
     */
302
    public function setUseCustomErrorHandler($usecustomerrorhandler)
303
    {
304
        $this->usecustomerrorhandler = $usecustomerrorhandler;
305
    }
306
307
    /**
308
     * @param $groups
309
     */
310
    public function setGroups($groups)
311
    {
312
        $token = ' ,;';
313
        $this->groups = [];
314
        $tok = strtok($groups, $token);
315
        while ($tok !== false) {
316
            $this->groups[] = $tok;
317
            $tok = strtok($token);
318
        }
319
    }
320
321
    /**
322
     * @param $excludeGroups
323
     */
324
    public function setExcludeGroups($excludeGroups)
325
    {
326
        $token = ' ,;';
327
        $this->excludeGroups = [];
328
        $tok = strtok($excludeGroups, $token);
329
        while ($tok !== false) {
330
            $this->excludeGroups[] = $tok;
331
            $tok = strtok($token);
332
        }
333
    }
334
335
    /**
336
     * Add a new formatter to all tests of this task.
337
     *
338
     * @param FormatterElement $fe formatter element
339
     */
340 2
    public function addFormatter(FormatterElement $fe)
341
    {
342 2
        $fe->setParent($this);
343 2
        $this->formatters[] = $fe;
344 2
    }
345
346
    /**
347
     * Add a new listener to all tests of this taks
348
     *
349
     * @param $listener
350
     */
351
    private function addListener($listener)
352
    {
353
        $this->listeners[] = $listener;
354
    }
355
356
    /**
357
     * @param File $configuration
358
     */
359
    public function setConfiguration(File $configuration)
360
    {
361
        $this->configuration = $configuration;
362
    }
363
364
    /**
365
     * @param string $pharLocation
366
     */
367
    public function setPharLocation($pharLocation)
368
    {
369
        $this->pharLocation = $pharLocation;
370
    }
371
372
    /**
373
     * Load and processes the PHPUnit configuration
374
     *
375
     * @param  $configuration
376
     * @return mixed
377
     * @throws ReflectionException
378
     * @throws BuildException
379
     */
380
    protected function handlePHPUnitConfiguration(File $configuration)
381
    {
382
        if (!$configuration->exists()) {
383
            throw new BuildException("Unable to find PHPUnit configuration file '" . (string) $configuration . "'");
384
        }
385
386
        $config = (new Loader())->load($configuration->getAbsolutePath());
387
388
        if (empty($config)) {
389
            return [];
390
        }
391
392
        $phpunit = $config->phpunit();
393
394
        if (empty($phpunit)) {
395
            return [];
396
        }
397
398
        if ($phpunit->hasBootstrap()) {
399
            $this->setBootstrap($phpunit->bootstrap());
400
        }
401
        $this->setHaltonfailure($phpunit->stopOnFailure());
402
        $this->setHaltonerror($phpunit->stopOnError());
403
        $this->setHaltonskipped($phpunit->stopOnSkipped());
404
        $this->setHaltonincomplete($phpunit->stopOnIncomplete());
405
        $this->setHaltondefect($phpunit->stopOnDefect());
406
        $this->setHaltonwarning($phpunit->stopOnWarning());
407
        $this->setHaltonrisky($phpunit->stopOnRisky());
408
        $this->setProcessIsolation($phpunit->processIsolation());
409
410
        foreach ($config->listeners() as $listener) {
411
            if (
412
                !class_exists($listener->className(), false)
413
                && $listener->hasSourceFile()
414
            ) {
415
                include_once $listener->sourceFile();
416
            }
417
418
            if (class_exists($listener->className())) {
419
                if ($listener->hasArguments()) {
420
                    $listener = (new $listener->className())();
421
                } else {
422
                    $listenerClass = new ReflectionClass(
423
                        $listener->className()
424
                    );
425
                    $listener = $listenerClass->newInstanceArgs(
426
                        $listener->arguments()
427
                    );
428
                }
429
430
                if ($listener instanceof \PHPUnit\Framework\TestListener) {
431
                    $this->addListener($listener);
432
                }
433
            }
434
        }
435
436
        $this->codeCoverageConfig = $config->codeCoverage();
437
        return $phpunit;
438
    }
439
440
    /**
441
     * The main entry point
442
     *
443
     * @throws BuildException
444
     */
445 4
    public function main()
446
    {
447 4
        if ($this->codecoverage && !extension_loaded('xdebug')) {
448
            throw new BuildException("PHPUnitTask depends on Xdebug being installed to gather code coverage information.");
449
        }
450
451 4
        $this->loadPHPUnit();
452 4
        $suite = new \PHPUnit\Framework\TestSuite('AllTests');
453 4
        $autoloadSave = spl_autoload_functions();
454
455 4
        if ($this->bootstrap) {
456 1
            include $this->bootstrap;
457
        }
458
459 4
        if ($this->configuration) {
460
            $phpunit = $this->handlePHPUnitConfiguration($this->configuration);
461
462
            if ($phpunit->backupGlobals() === false) {
463
                $suite->setBackupGlobals(false);
464
            }
465
466
            if ($phpunit->backupStaticAttributes() === true) {
467
                $suite->setBackupStaticAttributes(true);
468
            }
469
        }
470
471 4
        if ($this->printsummary) {
472 1
            $fe = new FormatterElement();
473 1
            $fe->setParent($this);
474 1
            $fe->setType("summary");
475 1
            $fe->setUseFile(false);
476 1
            $this->formatters[] = $fe;
477
        }
478
479 4
        foreach ($this->batchtests as $batchTest) {
480 4
            $this->appendBatchTestToTestSuite($batchTest, $suite);
481
        }
482
483 4
        $this->execute($suite);
484
485 4
        if ($this->testfailed) {
486 1
            throw new BuildException("Test(s) failed: " . $this->testfailuremessage);
487
        }
488
489 3
        $autoloadNew = spl_autoload_functions();
490 3
        if (is_array($autoloadNew)) {
0 ignored issues
show
introduced by
The condition is_array($autoloadNew) is always true.
Loading history...
491 3
            foreach ($autoloadNew as $autoload) {
492 3
                spl_autoload_unregister($autoload);
493
            }
494
        }
495
496 3
        if (is_array($autoloadSave)) {
497 3
            foreach ($autoloadSave as $autoload) {
498 3
                spl_autoload_register($autoload);
499
            }
500
        }
501 3
    }
502
503
    /**
504
     * @param $suite
505
     * @throws BuildException
506
     * @throws ReflectionException
507
     */
508 4
    protected function execute($suite)
509
    {
510 4
        $runner = new PHPUnitTestRunner9(
511 4
            $this->project,
512 4
            $this->groups,
513 4
            $this->excludeGroups,
514 4
            $this->processIsolation
515
        );
516
517 4
        if ($this->codecoverage) {
518
            /**
519
             * Add some defaults to the PHPUnit filter
520
             */
521
            $pwd = __DIR__;
522
            $path = realpath($pwd . '/../../../');
523
524
            if (class_exists('\SebastianBergmann\CodeCoverage\Filter')) {
525
                $filter = new \SebastianBergmann\CodeCoverage\Filter();
526
                if (method_exists($filter, 'addDirectoryToBlacklist')) {
527
                    $filter->addDirectoryToBlacklist($path);
528
                }
529
                if (class_exists('\SebastianBergmann\CodeCoverage\CodeCoverage')) {
530
                    if (null !== $this->codeCoverageConfig) {
531
                        // Update filters
532
                        foreach ($this->codeCoverageConfig->files()->asArray() as $file) {
533
                            $filter->includeFile($file->path());
534
                        }
535
                        foreach ($this->codeCoverageConfig->directories()->asArray() as $dir) {
536
                            $filter->includeDirectory($dir->path(), $dir->suffix(), $dir->prefix());
537
                        }
538
                        foreach ($this->codeCoverageConfig->excludeFiles()->asArray() as $file) {
539
                            $filter->excludeFile($file->path());
540
                        }
541
                        foreach ($this->codeCoverageConfig->excludeDirectories()->asArray() as $dir) {
542
                            $filter->excludeDirectory($dir->path(), $dir->suffix(), $dir->prefix());
543
                        }
544
                    }
545
546
                    if (null !== $this->codeCoverageConfig && $this->codeCoverageConfig->pathCoverage()) {
547
                        $driver = (new \SebastianBergmann\CodeCoverage\Driver\Selector())->forLineAndPathCoverage($filter);
0 ignored issues
show
Unused Code introduced by
The assignment to $driver is dead and can be removed.
Loading history...
548
                    } else {
549
                        $driver = (new \SebastianBergmann\CodeCoverage\Driver\Selector())->forLineCoverage($filter);
550
                    }
551
552
                    $driver = (new \SebastianBergmann\CodeCoverage\Driver\Selector())->forLineCoverage($filter);
553
                    $codeCoverage = new \SebastianBergmann\CodeCoverage\CodeCoverage($driver, $filter);
554
555
                    if (null !== $this->codeCoverageConfig) {
556
                        // Set code coverage configuration
557
                        if ($this->codeCoverageConfig->hasCacheDirectory()) {
558
                            $codeCoverage->cacheStaticAnalysis($this->codeCoverageConfig->cacheDirectory()->path());
559
                        }
560
                        if ($this->codeCoverageConfig->ignoreDeprecatedCodeUnits()) {
561
                            $codeCoverage->ignoreDeprecatedCode();
562
                        } else {
563
                            $codeCoverage->doNotIgnoreDeprecatedCode();
564
                        }
565
                        if ($this->codeCoverageConfig->includeUncoveredFiles()) {
566
                            $codeCoverage->includeUncoveredFiles();
567
                        } else {
568
                            $codeCoverage->doNotProcessUncoveredFiles();
569
                        }
570
                    }
571
572
                    $runner->setCodecoverage($codeCoverage);
573
                }
574
            }
575
        }
576
577 4
        $runner->setUseCustomErrorHandler($this->usecustomerrorhandler);
578
579 4
        foreach ($this->listeners as $listener) {
580
            $runner->addListener($listener);
0 ignored issues
show
Bug introduced by
The method addListener() does not exist on PHPUnitTestRunner9. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

580
            $runner->/** @scrutinizer ignore-call */ 
581
                     addListener($listener);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
581
        }
582
583 4
        foreach ($this->formatters as $fe) {
584 2
            $formatter = $fe->getFormatter();
585
586 2
            if ($fe->getUseFile()) {
587
                try {
588
                    $destFile = new File($fe->getToDir(), $fe->getOutfile());
589
                } catch (Exception $e) {
590
                    throw new BuildException('Unable to create destination.', $e);
591
                }
592
593
                $writer = new FileWriter($destFile->getAbsolutePath());
594
595
                $formatter->setOutput($writer);
596
            } else {
597 2
                $formatter->setOutput($this->getDefaultOutput());
598
            }
599
600 2
            $runner->addFormatter($formatter);
601
602 2
            $formatter->startTestRun();
603
        }
604
605 4
        $runner->run($suite);
606
607 4
        foreach ($this->formatters as $fe) {
608 2
            $formatter = $fe->getFormatter();
609 2
            $formatter->endTestRun();
610
        }
611
612 4
        if ($runner->hasErrors()) {
613 2
            if ($this->errorproperty) {
614
                $this->project->setNewProperty($this->errorproperty, true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $value of Phing\Project::setNewProperty(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

614
                $this->project->setNewProperty($this->errorproperty, /** @scrutinizer ignore-type */ true);
Loading history...
615
            }
616 2
            if ($this->haltonerror || $this->haltondefect) {
617
                $this->testfailed = true;
618
                $this->testfailuremessage = $runner->getLastErrorMessage();
619
            }
620
        }
621
622 4
        if ($runner->hasFailures()) {
623 3
            if ($this->failureproperty) {
624
                $this->project->setNewProperty($this->failureproperty, true);
625
            }
626
627 3
            if ($this->haltonfailure || $this->haltondefect) {
628 1
                $this->testfailed = true;
629 1
                $this->testfailuremessage = $runner->getLastFailureMessage();
630
            }
631
        }
632
633 4
        if ($runner->hasIncomplete()) {
634
            if ($this->incompleteproperty) {
635
                $this->project->setNewProperty($this->incompleteproperty, true);
636
            }
637
638
            if ($this->haltonincomplete) {
639
                $this->testfailed = true;
640
                $this->testfailuremessage = $runner->getLastIncompleteMessage();
641
            }
642
        }
643
644 4
        if ($runner->hasSkipped()) {
645
            if ($this->skippedproperty) {
646
                $this->project->setNewProperty($this->skippedproperty, true);
647
            }
648
649
            if ($this->haltonskipped) {
650
                $this->testfailed = true;
651
                $this->testfailuremessage = $runner->getLastSkippedMessage();
652
            }
653
        }
654
655 4
        if ($runner->hasWarnings()) {
656
            if ($this->warningproperty) {
657
                $this->project->setNewProperty($this->warningproperty, true);
658
            }
659
660
            if ($this->haltonwarning || $this->haltondefect) {
661
                $this->testfailed = true;
662
                $this->testfailuremessage = $runner->getLastWarningMessage();
663
            }
664
        }
665
666 4
        if ($runner->hasRisky()) {
667
            if ($this->riskyproperty) {
668
                $this->project->setNewProperty($this->riskyproperty, true);
669
            }
670
671
            if ($this->haltonrisky) {
672
                $this->testfailed = true;
673
                $this->testfailuremessage = $runner->getLastRiskyMessage();
674
            }
675
        }
676 4
    }
677
678
    /**
679
     * Add the tests in this batchtest to a test suite
680
     *
681
     * @param BatchTest $batchTest
682
     * @param PHPUnit\Framework\TestSuite $suite
683
     * @throws BuildException
684
     * @throws ReflectionException
685
     */
686 4
    protected function appendBatchTestToTestSuite(BatchTest $batchTest, $suite)
687
    {
688 4
        foreach ($batchTest->elements() as $element) {
689 4
            $testClass = new $element();
690 4
            if (!($testClass instanceof PHPUnit\Framework\TestSuite)) {
691 4
                $testClass = new ReflectionClass($element);
692
            }
693
            try {
694 4
                $suite->addTestSuite($testClass);
695
            } catch (\PHPUnit\Framework\Exception $e) {
696
                throw new BuildException('Unable to add TestSuite ' . get_class($testClass), $e);
697
            }
698
        }
699 4
    }
700
701
    /**
702
     * @return LogWriter
703
     */
704 2
    protected function getDefaultOutput()
705
    {
706 2
        return new LogWriter($this);
707
    }
708
709
    /**
710
     * Adds a set of tests based on pattern matching.
711
     *
712
     * @return BatchTest a new instance of a batch test.
713
     */
714 4
    public function createBatchTest()
715
    {
716 4
        $batchtest = new BatchTest($this->getProject());
717
718 4
        $this->batchtests[] = $batchtest;
719
720 4
        return $batchtest;
721
    }
722
}
723