Passed
Push — master ( dd99a9...c8547b )
by Michiel
10:09
created

PHPUnitTask   F

Complexity

Total Complexity 86

Size/Duplication

Total Lines 577
Duplicated Lines 0 %

Test Coverage

Coverage 44.12%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 215
dl 0
loc 577
ccs 105
cts 238
cp 0.4412
rs 2
c 2
b 0
f 0
wmc 86

30 Methods

Rating   Name   Duplication   Size   Complexity  
A setConfiguration() 0 3 1
A getHaltonskipped() 0 3 1
A getHaltonincomplete() 0 3 1
A setSkippedproperty() 0 3 1
A getHaltonfailure() 0 3 1
A setBootstrap() 0 3 1
A setErrorproperty() 0 3 1
A addFormatter() 0 4 1
A setHaltonskipped() 0 3 1
A setUseCustomErrorHandler() 0 3 1
A setPharLocation() 0 3 1
A setProcessIsolation() 0 3 1
A setCodecoverage() 0 3 1
A setHaltonerror() 0 3 1
A init() 0 2 1
A setIncompleteproperty() 0 3 1
A setPrintsummary() 0 3 1
A setExcludeGroups() 0 8 2
A setGroups() 0 8 2
A setHaltonincomplete() 0 3 1
A setFailureproperty() 0 3 1
A setHaltonfailure() 0 3 1
A addListener() 0 3 1
A loadPHPUnit() 0 20 4
D main() 0 54 14
A appendBatchTestToTestSuite() 0 11 4
A createBatchTest() 0 7 1
C handlePHPUnitConfiguration() 0 71 13
F execute() 0 116 24
A getDefaultOutput() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like PHPUnitTask 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 PHPUnitTask, and based on these observations, apply Extract Interface, too.

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
/**
21
 * Runs PHPUnit tests.
22
 *
23
 * @author  Michiel Rook <[email protected]>
24
 * @package phing.tasks.ext.phpunit
25
 * @see     BatchTest
26
 * @since   2.1.0
27
 */
28
class PHPUnitTask extends Task
29
{
30
    private $batchtests = [];
31
    /**
32
     * @var FormatterElement[] $formatters
33
     */
34
    private $formatters = [];
35
    private $bootstrap = "";
36
    private $haltonerror = false;
37
    private $haltonfailure = false;
38
    private $haltonincomplete = false;
39
    private $haltonskipped = false;
40
    private $errorproperty;
41
    private $failureproperty;
42
    private $incompleteproperty;
43
    private $skippedproperty;
44
    private $printsummary = false;
45
    private $testfailed = false;
46
    private $testfailuremessage = "";
47
    private $codecoverage = null;
48
    private $groups = [];
49
    private $excludeGroups = [];
50
    private $processIsolation = false;
51
    private $usecustomerrorhandler = true;
52
    private $listeners = [];
53
54
    /**
55
     * @var string
56
     */
57
    private $pharLocation = "";
58
59
    /**
60
     * @var PhingFile
61
     */
62
    private $configuration = null;
63
64
    /**
65
     * Initialize Task.
66
     * This method includes any necessary PHPUnit libraries and triggers
67
     * appropriate error if they cannot be found.  This is not done in header
68
     * because we may want this class to be loaded w/o triggering an error.
69
     */
70 4
    public function init()
71
    {
72 4
    }
73
74 4
    private function loadPHPUnit()
75
    {
76
        /**
77
         * Determine PHPUnit version number, try
78
         * PEAR old-style, then composer, then PHAR
79
         */
80 4
        @include_once 'PHPUnit/Runner/Version.php';
81 4
        if (!class_exists('PHPUnit_Runner_Version')) {
82 4
            @include_once 'phpunit/Runner/Version.php';
83
        }
84 4
        if (!empty($this->pharLocation)) {
85
            $GLOBALS['_SERVER']['SCRIPT_NAME'] = '-';
86
            ob_start();
87
            @include $this->pharLocation;
88
            ob_end_clean();
89
        }
90
91 4
        @include_once 'PHPUnit/Autoload.php';
92 4
        if (!class_exists('PHPUnit\Runner\Version')) {
93
            throw new BuildException("PHPUnitTask requires PHPUnit to be installed", $this->getLocation());
94
        }
95 4
    }
96
97
    /**
98
     * Sets the name of a bootstrap file that is run before
99
     * executing the tests
100
     *
101
     * @param string $bootstrap the name of the bootstrap file
102
     */
103 1
    public function setBootstrap($bootstrap)
104
    {
105 1
        $this->bootstrap = $bootstrap;
106 1
    }
107
108
    /**
109
     * @param $value
110
     */
111
    public function setErrorproperty($value)
112
    {
113
        $this->errorproperty = $value;
114
    }
115
116
    /**
117
     * @param $value
118
     */
119
    public function setFailureproperty($value)
120
    {
121
        $this->failureproperty = $value;
122
    }
123
124
    /**
125
     * @param $value
126
     */
127
    public function setIncompleteproperty($value)
128
    {
129
        $this->incompleteproperty = $value;
130
    }
131
132
    /**
133
     * @param $value
134
     */
135
    public function setSkippedproperty($value)
136
    {
137
        $this->skippedproperty = $value;
138
    }
139
140
    /**
141
     * @param $value
142
     */
143 3
    public function setHaltonerror($value)
144
    {
145 3
        $this->haltonerror = $value;
146 3
    }
147
148
    /**
149
     * @param $value
150
     */
151 3
    public function setHaltonfailure($value)
152
    {
153 3
        $this->haltonfailure = $value;
154 3
    }
155
156
    /**
157
     * @return bool
158
     */
159
    public function getHaltonfailure()
160
    {
161
        return $this->haltonfailure;
162
    }
163
164
    /**
165
     * @param $value
166
     */
167
    public function setHaltonincomplete($value)
168
    {
169
        $this->haltonincomplete = $value;
170
    }
171
172
    /**
173
     * @return bool
174
     */
175 1
    public function getHaltonincomplete()
176
    {
177 1
        return $this->haltonincomplete;
178
    }
179
180
    /**
181
     * @param $value
182
     */
183
    public function setHaltonskipped($value)
184
    {
185
        $this->haltonskipped = $value;
186
    }
187
188
    /**
189
     * @return bool
190
     */
191 1
    public function getHaltonskipped()
192
    {
193 1
        return $this->haltonskipped;
194
    }
195
196
    /**
197
     * @param $printsummary
198
     */
199 1
    public function setPrintsummary($printsummary)
200
    {
201 1
        $this->printsummary = $printsummary;
202 1
    }
203
204
    /**
205
     * @param $codecoverage
206
     */
207 1
    public function setCodecoverage($codecoverage)
208
    {
209 1
        $this->codecoverage = $codecoverage;
210 1
    }
211
212
    /**
213
     * @param $processIsolation
214
     */
215
    public function setProcessIsolation($processIsolation)
216
    {
217
        $this->processIsolation = $processIsolation;
218
    }
219
220
    /**
221
     * @param $usecustomerrorhandler
222
     */
223
    public function setUseCustomErrorHandler($usecustomerrorhandler)
224
    {
225
        $this->usecustomerrorhandler = $usecustomerrorhandler;
226
    }
227
228
    /**
229
     * @param $groups
230
     */
231
    public function setGroups($groups)
232
    {
233
        $token = ' ,;';
234
        $this->groups = [];
235
        $tok = strtok($groups, $token);
236
        while ($tok !== false) {
237
            $this->groups[] = $tok;
238
            $tok = strtok($token);
239
        }
240
    }
241
242
    /**
243
     * @param $excludeGroups
244
     */
245
    public function setExcludeGroups($excludeGroups)
246
    {
247
        $token = ' ,;';
248
        $this->excludeGroups = [];
249
        $tok = strtok($excludeGroups, $token);
250
        while ($tok !== false) {
251
            $this->excludeGroups[] = $tok;
252
            $tok = strtok($token);
253
        }
254
    }
255
256
    /**
257
     * Add a new formatter to all tests of this task.
258
     *
259
     * @param FormatterElement $fe formatter element
260
     */
261 2
    public function addFormatter(FormatterElement $fe)
262
    {
263 2
        $fe->setParent($this);
264 2
        $this->formatters[] = $fe;
265 2
    }
266
267
    /**
268
     * Add a new listener to all tests of this taks
269
     *
270
     * @param $listener
271
     */
272
    private function addListener($listener)
273
    {
274
        $this->listeners[] = $listener;
275
    }
276
277
    /**
278
     * @param PhingFile $configuration
279
     */
280
    public function setConfiguration(PhingFile $configuration)
281
    {
282
        $this->configuration = $configuration;
283
    }
284
285
    /**
286
     * @param string $pharLocation
287
     */
288
    public function setPharLocation($pharLocation)
289
    {
290
        $this->pharLocation = $pharLocation;
291
    }
292
293
    /**
294
     * Load and processes the PHPUnit configuration
295
     *
296
     * @param  $configuration
297
     * @return mixed
298
     * @throws ReflectionException
299
     * @throws BuildException
300
     */
301
    protected function handlePHPUnitConfiguration(PhingFile $configuration)
302
    {
303
        if (!$configuration->exists()) {
304
            throw new BuildException("Unable to find PHPUnit configuration file '" . (string) $configuration . "'");
305
        }
306
307
        if (class_exists('\PHPUnit\TextUI\Configuration\Registry')) {
308
            $config = \PHPUnit\TextUI\Configuration\Registry::getInstance()->get($configuration->getAbsolutePath());
0 ignored issues
show
Bug introduced by
The type PHPUnit\TextUI\Configuration\Registry was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
309
        } elseif (class_exists('\PHPUnit\TextUI\XmlConfiguration\Loader')) {
310
            $config = (new \PHPUnit\TextUI\XmlConfiguration\Loader())->load($configuration->getAbsolutePath());
311
        } else {
312
            throw new BuildException("Can't parse PHPUnit configuration file '" . (string) $configuration . "'");
313
        }
314
315
        if (empty($config)) {
316
            return [];
317
        }
318
319
        $phpunit = $config->phpunit();
320
321
        if (empty($phpunit)) {
322
            return [];
323
        }
324
325
        if ($phpunit->hasBootstrap()) {
326
            $this->setBootstrap($phpunit->bootstrap());
327
        }
328
        $this->setHaltonfailure($phpunit->stopOnFailure());
329
        $this->setHaltonerror($phpunit->stopOnError());
330
        $this->setHaltonskipped($phpunit->stopOnSkipped());
331
        $this->setHaltonincomplete($phpunit->stopOnIncomplete());
332
        $this->setProcessIsolation($phpunit->processIsolation());
333
334
        foreach ($config->listeners() as $listener) {
335
            if (
336
                !class_exists($listener->className(), false)
337
                && $listener->hasSourceFile()
338
            ) {
339
                include_once $listener->sourceFile();
340
            }
341
342
            if (class_exists($listener->className())) {
343
                if ($listener->hasArguments()) {
344
                    $listener = (new $listener->className())();
345
                } else {
346
                    $listenerClass = new ReflectionClass(
347
                        $listener->className()
348
                    );
349
                    $listener = $listenerClass->newInstanceArgs(
350
                        $listener->arguments()
351
                    );
352
                }
353
354
                if ($listener instanceof \PHPUnit\Framework\TestListener) {
355
                    $this->addListener($listener);
356
                }
357
            }
358
        }
359
360
/*        if (method_exists($config, 'getSeleniumBrowserConfiguration')) {
361
            $browsers = $config->getSeleniumBrowserConfiguration();
362
363
            if (
364
                !empty($browsers)
365
                && class_exists('PHPUnit_Extensions_SeleniumTestCase')
366
            ) {
367
                PHPUnit_Extensions_SeleniumTestCase::$browsers = $browsers;
368
            }
369
        } */
370
371
        return $phpunit;
372
    }
373
374
    /**
375
     * The main entry point
376
     *
377
     * @throws BuildException
378
     */
379 4
    public function main()
380
    {
381 4
        if ($this->codecoverage && !extension_loaded('xdebug')) {
382
            throw new BuildException("PHPUnitTask depends on Xdebug being installed to gather code coverage information.");
383
        }
384
385 4
        $this->loadPHPUnit();
386 4
        $suite = new \PHPUnit\Framework\TestSuite('AllTests');
387 4
        $autoloadSave = spl_autoload_functions();
388
389 4
        if ($this->bootstrap) {
390 1
            include $this->bootstrap;
391
        }
392
393 4
        if ($this->configuration) {
394
            $phpunit = $this->handlePHPUnitConfiguration($this->configuration);
395
396
            if ($phpunit->backupGlobals() === false) {
397
                $suite->setBackupGlobals(false);
398
            }
399
400
            if ($phpunit->backupStaticAttributes() === true) {
401
                $suite->setBackupStaticAttributes(true);
402
            }
403
        }
404
405 4
        if ($this->printsummary) {
406 1
            $fe = new FormatterElement();
407 1
            $fe->setParent($this);
408 1
            $fe->setType("summary");
409 1
            $fe->setUseFile(false);
410 1
            $this->formatters[] = $fe;
411
        }
412
413 4
        foreach ($this->batchtests as $batchTest) {
414 4
            $this->appendBatchTestToTestSuite($batchTest, $suite);
415
        }
416
417 4
        $this->execute($suite);
418
419 4
        if ($this->testfailed) {
420 1
            throw new BuildException($this->testfailuremessage);
421
        }
422
423 3
        $autoloadNew = spl_autoload_functions();
424 3
        if (is_array($autoloadNew)) {
0 ignored issues
show
introduced by
The condition is_array($autoloadNew) is always true.
Loading history...
425 3
            foreach ($autoloadNew as $autoload) {
426 3
                spl_autoload_unregister($autoload);
427
            }
428
        }
429
430 3
        if (is_array($autoloadSave)) {
431 3
            foreach ($autoloadSave as $autoload) {
432 3
                spl_autoload_register($autoload);
433
            }
434
        }
435 3
    }
436
437
    /**
438
     * @param $suite
439
     * @throws BuildException
440
     * @throws ReflectionException
441
     */
442 4
    protected function execute($suite)
443
    {
444
        if (
445 4
            class_exists('\PHPUnit\Runner\Version', false) &&
446 4
            version_compare(\PHPUnit\Runner\Version::id(), '8.0.0', '<')
447
        ) {
448
            $runner = new PHPUnitTestRunner7(
449
                $this->project,
450
                $this->groups,
451
                $this->excludeGroups,
452
                $this->processIsolation
453
            );
454
        } else {
455 4
            $runner = new PHPUnitTestRunner8(
456 4
                $this->project,
457 4
                $this->groups,
458 4
                $this->excludeGroups,
459 4
                $this->processIsolation
460
            );
461
        }
462
463 4
        if ($this->codecoverage) {
464
            /**
465
             * Add some defaults to the PHPUnit filter
466
             */
467
            $pwd = __DIR__;
468
            $path = realpath($pwd . '/../../../');
469
470
            if (class_exists('\SebastianBergmann\CodeCoverage\Filter')) {
471
                $filter = new \SebastianBergmann\CodeCoverage\Filter();
472
                if (method_exists($filter, 'addDirectoryToBlacklist')) {
473
                    $filter->addDirectoryToBlacklist($path);
474
                }
475
                if (class_exists('\SebastianBergmann\CodeCoverage\CodeCoverage')) {
476
                    $driver = (new \SebastianBergmann\CodeCoverage\Driver\Selector())->forLineCoverage($filter);
477
                    $codeCoverage = new \SebastianBergmann\CodeCoverage\CodeCoverage($driver, $filter);
478
                    $runner->setCodecoverage($codeCoverage);
479
                }
480
            }
481
        }
482
483 4
        $runner->setUseCustomErrorHandler($this->usecustomerrorhandler);
484
485 4
        foreach ($this->listeners as $listener) {
486
            $runner->addListener($listener);
487
        }
488
489 4
        foreach ($this->formatters as $fe) {
490 2
            $formatter = $fe->getFormatter();
491
492 2
            if ($fe->getUseFile()) {
493
                try {
494
                    $destFile = new PhingFile($fe->getToDir(), $fe->getOutfile());
495
                } catch (Exception $e) {
496
                    throw new BuildException('Unable to create destination.', $e);
497
                }
498
499
                $writer = new FileWriter($destFile->getAbsolutePath());
500
501
                $formatter->setOutput($writer);
502
            } else {
503 2
                $formatter->setOutput($this->getDefaultOutput());
504
            }
505
506 2
            $runner->addFormatter($formatter);
507
508 2
            $formatter->startTestRun();
509
        }
510
511 4
        $runner->run($suite);
512
513 4
        foreach ($this->formatters as $fe) {
514 2
            $formatter = $fe->getFormatter();
515 2
            $formatter->endTestRun();
516
        }
517
518 4
        if ($runner->hasErrors()) {
519 2
            if ($this->errorproperty) {
520
                $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 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

520
                $this->project->setNewProperty($this->errorproperty, /** @scrutinizer ignore-type */ true);
Loading history...
521
            }
522 2
            if ($this->haltonerror) {
523
                $this->testfailed = true;
524
                $this->testfailuremessage = $runner->getLastErrorMessage();
525
            }
526
        }
527
528 4
        if ($runner->hasFailures()) {
529 3
            if ($this->failureproperty) {
530
                $this->project->setNewProperty($this->failureproperty, true);
531
            }
532
533 3
            if ($this->haltonfailure) {
534 1
                $this->testfailed = true;
535 1
                $this->testfailuremessage = $runner->getLastFailureMessage();
536
            }
537
        }
538
539 4
        if ($runner->hasIncomplete()) {
540
            if ($this->incompleteproperty) {
541
                $this->project->setNewProperty($this->incompleteproperty, true);
542
            }
543
544
            if ($this->haltonincomplete) {
545
                $this->testfailed = true;
546
                $this->testfailuremessage = $runner->getLastIncompleteMessage();
547
            }
548
        }
549
550 4
        if ($runner->hasSkipped()) {
551
            if ($this->skippedproperty) {
552
                $this->project->setNewProperty($this->skippedproperty, true);
553
            }
554
555
            if ($this->haltonskipped) {
556
                $this->testfailed = true;
557
                $this->testfailuremessage = $runner->getLastSkippedMessage();
558
            }
559
        }
560 4
    }
561
562
    /**
563
     * Add the tests in this batchtest to a test suite
564
     *
565
     * @param BatchTest $batchTest
566
     * @param PHPUnit\Framework\TestSuite $suite
567
     * @throws BuildException
568
     * @throws ReflectionException
569
     */
570 4
    protected function appendBatchTestToTestSuite(BatchTest $batchTest, $suite)
571
    {
572 4
        foreach ($batchTest->elements() as $element) {
573 4
            $testClass = new $element();
574 4
            if (!($testClass instanceof PHPUnit\Framework\TestSuite)) {
575 4
                $testClass = new ReflectionClass($element);
576
            }
577
            try {
578 4
                $suite->addTestSuite($testClass);
579
            } catch (\PHPUnit\Framework\Exception $e) {
580
                throw new BuildException('Unable to add TestSuite ' . get_class($testClass), $e);
581
            }
582
        }
583 4
    }
584
585
    /**
586
     * @return LogWriter
587
     */
588 2
    protected function getDefaultOutput()
589
    {
590 2
        return new LogWriter($this);
591
    }
592
593
    /**
594
     * Adds a set of tests based on pattern matching.
595
     *
596
     * @return BatchTest a new instance of a batch test.
597
     */
598 4
    public function createBatchTest()
599
    {
600 4
        $batchtest = new BatchTest($this->getProject());
601
602 4
        $this->batchtests[] = $batchtest;
603
604 4
        return $batchtest;
605
    }
606
}
607