Passed
Push — master ( eabf33...342b63 )
by Michiel
05:53
created

JUnit   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 398
Duplicated Lines 0 %

Test Coverage

Coverage 71.97%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 147
dl 0
loc 398
ccs 113
cts 157
cp 0.7197
rs 9.6
c 1
b 0
f 0
wmc 35

16 Methods

Rating   Name   Duplication   Size   Complexity  
A startTest() 0 24 3
A __construct() 0 11 1
A doAddSkipped() 0 10 2
A endTestSuite() 0 42 2
A addSkippedTest() 0 3 1
A addError() 0 4 1
A startTestSuite() 0 28 4
A flush() 0 7 2
A setWriteDocument() 0 4 2
A addIncompleteTest() 0 3 1
A addRiskyTest() 0 19 3
A addFailure() 0 4 1
A getXML() 0 3 1
B endTest() 0 43 6
A doAddFault() 0 27 4
A addWarning() 0 4 1
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 PHPUnit\Framework\AssertionFailedError;
21
use PHPUnit\Framework\ExceptionWrapper;
22
use PHPUnit\Framework\SelfDescribing;
23
use PHPUnit\Framework\Test;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Test. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
24
use PHPUnit\Framework\TestFailure;
25
use PHPUnit\Framework\TestListener;
26
use PHPUnit\Framework\TestSuite;
27
use PHPUnit\Framework\Warning;
28
use PHPUnit\Util\Filter;
29
use PHPUnit\Util\Printer;
30
use PHPUnit\Util\Xml;
31
32
/**
33
 * A TestListener that generates a logfile of the test execution in XML markup.
34
 *
35
 * The XML markup used is the same as the one that is used by the JUnit Ant task.
36
 */
37
class JUnit extends Printer implements TestListener
0 ignored issues
show
Deprecated Code introduced by
The interface PHPUnit\Framework\TestListener has been deprecated: Use the `TestHook` interfaces instead ( Ignorable by Annotation )

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

37
class JUnit extends Printer implements /** @scrutinizer ignore-deprecated */ TestListener

This interface has been deprecated. The supplier of the interface has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.

Loading history...
38
{
39
    /**
40
     * @var DOMDocument
41
     */
42
    protected $document;
43
44
    /**
45
     * @var DOMElement
46
     */
47
    protected $root;
48
49
    /**
50
     * @var bool
51
     */
52
    protected $reportUselessTests = false;
53
54
    /**
55
     * @var bool
56
     */
57
    protected $writeDocument = true;
58
59
    /**
60
     * @var DOMElement[]
61
     */
62
    protected $testSuites = [];
63
64
    /**
65
     * @var int[]
66
     */
67
    protected $testSuiteTests = [0];
68
69
    /**
70
     * @var int[]
71
     */
72
    protected $testSuiteAssertions = [0];
73
74
    /**
75
     * @var int[]
76
     */
77
    protected $testSuiteErrors = [0];
78
79
    /**
80
     * @var int[]
81
     */
82
    protected $testSuiteFailures = [0];
83
84
    /**
85
     * @var int[]
86
     */
87
    protected $testSuiteSkipped = [0];
88
89
    /**
90
     * @var int[]
91
     */
92
    protected $testSuiteTimes = [0];
93
94
    /**
95
     * @var int
96
     */
97
    protected $testSuiteLevel = 0;
98
99
    /**
100
     * @var DOMElement
101
     */
102
    protected $currentTestCase;
103
104
    /**
105
     * Constructor.
106
     *
107
     * @param null|mixed $out
108
     *
109
     * @throws \PHPUnit\Framework\Exception
110
     */
111 1
    public function __construct($out = null, bool $reportUselessTests = false)
112
    {
113 1
        $this->document               = new DOMDocument('1.0', 'UTF-8');
114 1
        $this->document->formatOutput = true;
115
116 1
        $this->root = $this->document->createElement('testsuites');
117 1
        $this->document->appendChild($this->root);
118
119 1
        parent::__construct($out);
120
121 1
        $this->reportUselessTests = $reportUselessTests;
122 1
    }
123
124
    /**
125
     * Flush buffer and close output.
126
     */
127
    public function flush(): void
128
    {
129
        if ($this->writeDocument === true) {
130
            $this->write($this->getXML());
131
        }
132
133
        parent::flush();
134
    }
135
136
    /**
137
     * An error occurred.
138
     *
139
     * @throws \InvalidArgumentException
140
     */
141
    public function addError(Test $test, \Throwable $t, float $time): void
142
    {
143
        $this->doAddFault($test, $t, $time, 'error');
144
        $this->testSuiteErrors[$this->testSuiteLevel]++;
145
    }
146
147
    /**
148
     * A warning occurred.
149
     *
150
     * @throws \InvalidArgumentException
151
     */
152 20
    public function addWarning(Test $test, Warning $e, float $time): void
153
    {
154 20
        $this->doAddFault($test, $e, $time, 'warning');
155 20
        $this->testSuiteFailures[$this->testSuiteLevel]++;
156 20
    }
157
158
    /**
159
     * A failure occurred.
160
     *
161
     * @throws \InvalidArgumentException
162
     */
163
    public function addFailure(Test $test, AssertionFailedError $e, float $time): void
164
    {
165
        $this->doAddFault($test, $e, $time, 'failure');
166
        $this->testSuiteFailures[$this->testSuiteLevel]++;
167
    }
168
169
    /**
170
     * Incomplete test.
171
     */
172
    public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
173
    {
174
        $this->doAddSkipped($test);
175
    }
176
177
    /**
178
     * Risky test.
179
     */
180
    public function addRiskyTest(Test $test, \Throwable $t, float $time): void
181
    {
182
        if (!$this->reportUselessTests || $this->currentTestCase === null) {
183
            return;
184
        }
185
186
        $error = $this->document->createElement(
187
            'error',
188
            Xml::prepareString(
189
                "Risky Test\n" .
190
                Filter::getFilteredStacktrace($t)
191
            )
192
        );
193
194
        $error->setAttribute('type', \get_class($t));
195
196
        $this->currentTestCase->appendChild($error);
197
198
        $this->testSuiteErrors[$this->testSuiteLevel]++;
199
    }
200
201
    /**
202
     * Skipped test.
203
     */
204
    public function addSkippedTest(Test $test, \Throwable $t, float $time): void
205
    {
206
        $this->doAddSkipped($test);
207
    }
208
209
    /**
210
     * A testsuite started.
211
     */
212 1
    public function startTestSuite(TestSuite $suite): void
213
    {
214 1
        $testSuite = $this->document->createElement('testsuite');
215 1
        $testSuite->setAttribute('name', $suite->getName());
216
217 1
        if (\class_exists($suite->getName(), false)) {
218
            try {
219 1
                $class = new ReflectionClass($suite->getName());
220
221 1
                $testSuite->setAttribute('file', $class->getFileName());
222
            } catch (ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
223
            }
224
        }
225
226 1
        if ($this->testSuiteLevel > 0) {
227 1
            $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
228
        } else {
229 1
            $this->root->appendChild($testSuite);
230
        }
231
232 1
        $this->testSuiteLevel++;
233 1
        $this->testSuites[$this->testSuiteLevel]          = $testSuite;
234 1
        $this->testSuiteTests[$this->testSuiteLevel]      = 0;
235 1
        $this->testSuiteAssertions[$this->testSuiteLevel] = 0;
236 1
        $this->testSuiteErrors[$this->testSuiteLevel]     = 0;
237 1
        $this->testSuiteFailures[$this->testSuiteLevel]   = 0;
238 1
        $this->testSuiteSkipped[$this->testSuiteLevel]    = 0;
239 1
        $this->testSuiteTimes[$this->testSuiteLevel]      = 0;
240 1
    }
241
242
    /**
243
     * A testsuite ended.
244
     */
245 1
    public function endTestSuite(TestSuite $suite): void
246
    {
247 1
        $this->testSuites[$this->testSuiteLevel]->setAttribute(
248 1
            'tests',
249 1
            $this->testSuiteTests[$this->testSuiteLevel]
250
        );
251
252 1
        $this->testSuites[$this->testSuiteLevel]->setAttribute(
253 1
            'assertions',
254 1
            $this->testSuiteAssertions[$this->testSuiteLevel]
255
        );
256
257 1
        $this->testSuites[$this->testSuiteLevel]->setAttribute(
258 1
            'errors',
259 1
            $this->testSuiteErrors[$this->testSuiteLevel]
260
        );
261
262 1
        $this->testSuites[$this->testSuiteLevel]->setAttribute(
263 1
            'failures',
264 1
            $this->testSuiteFailures[$this->testSuiteLevel]
265
        );
266
267 1
        $this->testSuites[$this->testSuiteLevel]->setAttribute(
268 1
            'skipped',
269 1
            $this->testSuiteSkipped[$this->testSuiteLevel]
270
        );
271
272 1
        $this->testSuites[$this->testSuiteLevel]->setAttribute(
273 1
            'time',
274 1
            \sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])
275
        );
276
277 1
        if ($this->testSuiteLevel > 1) {
278 1
            $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
279 1
            $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
280 1
            $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
281 1
            $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
282 1
            $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
283 1
            $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
284
        }
285
286 1
        $this->testSuiteLevel--;
287 1
    }
288
289
    /**
290
     * A test started.
291
     *
292
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
293
     * @throws ReflectionException
294
     */
295 1
    public function startTest(Test $test): void
296
    {
297 1
        $usesDataprovider = false;
298
299 1
        if (\method_exists($test, 'usesDataProvider')) {
300 1
            $usesDataprovider = $test->usesDataProvider();
301
        }
302
303 1
        $testCase = $this->document->createElement('testcase');
304 1
        $testCase->setAttribute('name', $test->getName());
305
306 1
        $class      = new ReflectionClass($test);
307 1
        $methodName = $test->getName(!$usesDataprovider);
308
309 1
        if ($class->hasMethod($methodName)) {
310 1
            $method = $class->getMethod($methodName);
311
312 1
            $testCase->setAttribute('class', $class->getName());
313 1
            $testCase->setAttribute('classname', \str_replace('\\', '.', $class->getName()));
314 1
            $testCase->setAttribute('file', $class->getFileName());
315 1
            $testCase->setAttribute('line', $method->getStartLine());
316
        }
317
318 1
        $this->currentTestCase = $testCase;
319 1
    }
320
321
    /**
322
     * A test ended.
323
     */
324 1
    public function endTest(Test $test, float $time): void
325
    {
326 1
        $numAssertions = 0;
327
328 1
        if (\method_exists($test, 'getNumAssertions')) {
329 1
            $numAssertions = $test->getNumAssertions();
330
        }
331
332 1
        $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
333
334 1
        $this->currentTestCase->setAttribute(
335 1
            'assertions',
336
            $numAssertions
337
        );
338
339 1
        $this->currentTestCase->setAttribute(
340 1
            'time',
341 1
            \sprintf('%F', $time)
342
        );
343
344 1
        $this->testSuites[$this->testSuiteLevel]->appendChild(
345 1
            $this->currentTestCase
346
        );
347
348 1
        $this->testSuiteTests[$this->testSuiteLevel]++;
349 1
        $this->testSuiteTimes[$this->testSuiteLevel] += $time;
350
351 1
        $testOutput = '';
352
353 1
        if (\method_exists($test, 'hasOutput') && \method_exists($test, 'getActualOutput')) {
354 1
            $testOutput = $test->hasOutput() ? $test->getActualOutput() : '';
355
        }
356
357 1
        if (!empty($testOutput)) {
358
            $systemOut = $this->document->createElement(
359
                'system-out',
360
                Xml::prepareString($testOutput)
361
            );
362
363
            $this->currentTestCase->appendChild($systemOut);
364
        }
365
366 1
        $this->currentTestCase = null;
367 1
    }
368
369
    /**
370
     * Returns the XML as a string.
371
     */
372 1
    public function getXML(): string
373
    {
374 1
        return $this->document->saveXML();
375
    }
376
377
    /**
378
     * Enables or disables the writing of the document
379
     * in flush().
380
     *
381
     * This is a "hack" needed for the integration of
382
     * PHPUnit with Phing.
383
     */
384 1
    public function setWriteDocument(bool $flag): void
385
    {
386 1
        if (\is_bool($flag)) {
0 ignored issues
show
introduced by
The condition is_bool($flag) is always true.
Loading history...
387 1
            $this->writeDocument = $flag;
388
        }
389 1
    }
390
391
    /**
392
     * Method which generalizes addError() and addFailure()
393
     *
394
     * @throws \InvalidArgumentException
395
     */
396 20
    private function doAddFault(Test $test, \Throwable $t, float $time, $type): void
0 ignored issues
show
Unused Code introduced by
The parameter $time is not used and could be removed. ( Ignorable by Annotation )

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

396
    private function doAddFault(Test $test, \Throwable $t, /** @scrutinizer ignore-unused */ float $time, $type): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
397
    {
398 20
        if ($this->currentTestCase === null) {
399
            return;
400
        }
401
402 20
        if ($test instanceof SelfDescribing) {
403 20
            $buffer = $test->toString() . "\n";
404
        } else {
405
            $buffer = '';
406
        }
407
408 20
        $buffer .= TestFailure::exceptionToString($t) . "\n" .
409 20
            Filter::getFilteredStacktrace($t);
410
411 20
        $fault = $this->document->createElement(
412 20
            $type,
413 20
            Xml::prepareString($buffer)
414
        );
415
416 20
        if ($t instanceof ExceptionWrapper) {
417
            $fault->setAttribute('type', $t->getClassName());
418
        } else {
419 20
            $fault->setAttribute('type', \get_class($t));
420
        }
421
422 20
        $this->currentTestCase->appendChild($fault);
423 20
    }
424
425
    private function doAddSkipped(Test $test): void
0 ignored issues
show
Unused Code introduced by
The parameter $test is not used and could be removed. ( Ignorable by Annotation )

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

425
    private function doAddSkipped(/** @scrutinizer ignore-unused */ Test $test): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
426
    {
427
        if ($this->currentTestCase === null) {
428
            return;
429
        }
430
431
        $skipped = $this->document->createElement('skipped');
432
        $this->currentTestCase->appendChild($skipped);
433
434
        $this->testSuiteSkipped[$this->testSuiteLevel]++;
435
    }
436
}
437