Completed
Push — master ( 8c06ff...a2f0f3 )
by Julian
02:34
created

Reader::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 9
cts 10
cp 0.9
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 1
crap 3.009
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ParaTest\Logging\JUnit;
6
7
use ParaTest\Logging\MetaProvider;
8
9
class Reader extends MetaProvider
10
{
11
    /**
12
     * @var \SimpleXMLElement
13
     */
14
    protected $xml;
15
16
    /**
17
     * @var bool
18
     */
19
    protected $isSingle = false;
20
21
    /**
22
     * @var array
23
     */
24
    protected $suites = [];
25
26
    /**
27
     * @var string
28
     */
29
    protected $logFile;
30
31
    /**
32
     * @var array
33
     */
34
    protected static $defaultSuite = [
35
        'name' => '',
36
        'file' => '',
37
        'tests' => 0,
38
        'assertions' => 0,
39
        'failures' => 0,
40
        'errors' => 0,
41
        'skipped' => 0,
42
        'time' => 0,
43
    ];
44
45 54
    public function __construct(string $logFile)
46
    {
47 54
        if (!file_exists($logFile)) {
48 1
            throw new \InvalidArgumentException("Log file $logFile does not exist");
49
        }
50
51 54
        $this->logFile = $logFile;
52 54
        if (filesize($logFile) === 0) {
53
            throw new \InvalidArgumentException("Log file $logFile is empty. This means a PHPUnit process has crashed.");
54
        }
55 54
        $logFileContents = file_get_contents($this->logFile);
56 54
        $this->xml = new \SimpleXMLElement($logFileContents);
57 54
        $this->init();
58 54
    }
59
60
    /**
61
     * Returns whether or not this reader contains only
62
     * a single suite.
63
     *
64
     * @return bool
65
     */
66 2
    public function isSingleSuite(): bool
67
    {
68 2
        return $this->isSingle;
69
    }
70
71
    /**
72
     * Return the Reader's collection
73
     * of test suites.
74
     *
75
     * @return array
76
     */
77 15
    public function getSuites(): array
78
    {
79 15
        return $this->suites;
80
    }
81
82
    /**
83
     * Return an array that contains
84
     * each suite's instant feedback. Since
85
     * logs do not contain skipped or incomplete
86
     * tests this array will contain any number of the following
87
     * characters: .,F,E
88
     * TODO: Update this, skipped was added in phpunit.
89
     *
90
     * @return array
91
     */
92 12
    public function getFeedback(): array
93
    {
94 12
        $feedback = [];
95 12
        $suites = $this->isSingle ? $this->suites : $this->suites[0]->suites;
96 12
        foreach ($suites as $suite) {
97 12
            foreach ($suite->cases as $case) {
98 12
                if ($case->failures) {
99 8
                    $feedback[] = 'F';
100 12
                } elseif ($case->errors) {
101 9
                    $feedback[] = 'E';
102 12
                } elseif ($case->skipped) {
103
                    $feedback[] = 'S';
104
                } else {
105 12
                    $feedback[] = '.';
106
                }
107
            }
108
        }
109
110 12
        return $feedback;
111
    }
112
113
    /**
114
     * Remove the JUnit xml file.
115
     */
116 3
    public function removeLog()
117
    {
118 3
        unlink($this->logFile);
119 3
    }
120
121
    /**
122
     * Initialize the suite collection
123
     * from the JUnit xml document.
124
     */
125 54
    protected function init()
126
    {
127 54
        $this->initSuite();
128 54
        $cases = $this->getCaseNodes();
129 54
        foreach ($cases as $file => $nodeArray) {
130 54
            $this->initSuiteFromCases($nodeArray);
131
        }
132 54
    }
133
134
    /**
135
     * Uses an array of testcase nodes to build a suite.
136
     *
137
     * @param array $nodeArray an array of SimpleXMLElement nodes representing testcase elements
138
     */
139 54
    protected function initSuiteFromCases(array $nodeArray)
140
    {
141 54
        $testCases = [];
142 54
        $properties = $this->caseNodesToSuiteProperties($nodeArray, $testCases);
143 54
        if (!$this->isSingle) {
144 44
            $this->addSuite($properties, $testCases);
145
        } else {
146 50
            $this->suites[0]->cases = $testCases;
147
        }
148 54
    }
149
150
    /**
151
     * Creates and adds a TestSuite based on the given
152
     * suite properties and collection of test cases.
153
     *
154
     * @param $properties
155
     * @param $testCases
156
     */
157 44
    protected function addSuite($properties, array $testCases)
158
    {
159 44
        $suite = TestSuite::suiteFromArray($properties);
160 44
        $suite->cases = $testCases;
161 44
        $this->suites[0]->suites[] = $suite;
162 44
    }
163
164
    /**
165
     * Fold an array of testcase nodes into a suite array.
166
     *
167
     * @param array $nodeArray an array of testcase nodes
168
     * @param array $testCases an array reference. Individual testcases will be placed here.
169
     *
170
     * @return mixed
171
     */
172 54
    protected function caseNodesToSuiteProperties(array $nodeArray, array &$testCases = [])
173
    {
174 54
        $cb = [TestCase::class, 'caseFromNode'];
175
176 54
        return array_reduce($nodeArray, function ($result, $c) use (&$testCases, $cb) {
177 54
            $testCases[] = call_user_func_array($cb, [$c]);
178 54
            $result['name'] = (string) $c['class'];
179 54
            $result['file'] = (string) $c['file'];
180 54
            $result['tests'] = $result['tests'] + 1;
181 54
            $result['assertions'] += (int) $c['assertions'];
182 54
            $result['failures'] += count($c->xpath('failure'));
183 54
            $result['errors'] += count($c->xpath('error'));
184 54
            $result['skipped'] += count($c->xpath('skipped'));
185 54
            $result['time'] += (float) ($c['time']);
186
187 54
            return $result;
188 54
        }, static::$defaultSuite);
189
    }
190
191
    /**
192
     * Return a collection of testcase nodes
193
     * from the xml document.
194
     *
195
     * @return array
196
     */
197 54
    protected function getCaseNodes(): array
198
    {
199 54
        $caseNodes = $this->xml->xpath('//testcase');
200 54
        $cases = [];
201 54
        foreach ($caseNodes as $node) {
202 54
            $case = $node;
0 ignored issues
show
Unused Code introduced by
$case is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
203 54
            if (!isset($cases[(string) $node['file']])) {
204 54
                $cases[(string) $node['file']] = [];
205
            }
206 54
            $cases[(string) $node['file']][] = $node;
207
        }
208
209 54
        return $cases;
210
    }
211
212
    /**
213
     * Determine if this reader is a single suite
214
     * and initialize the suite collection with the first
215
     * suite.
216
     */
217 54
    protected function initSuite()
218
    {
219 54
        $suiteNodes = $this->xml->xpath('/testsuites/testsuite/testsuite');
220 54
        $this->isSingle = count($suiteNodes) === 0;
221 54
        $node = current($this->xml->xpath('/testsuites/testsuite'));
222
223 54
        if ($node !== false) {
224 54
            $this->suites[] = TestSuite::suiteFromNode($node);
225
        } else {
226 21
            $this->suites[] = TestSuite::suiteFromArray(self::$defaultSuite);
227
        }
228 54
    }
229
}
230