Writer   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 21
lcom 1
cbo 3
dl 0
loc 223
ccs 67
cts 67
cp 1
rs 10
c 1
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getName() 0 4 1
A getXml() 0 13 3
A write() 0 4 1
A appendSuite() 0 13 3
A appendCase() 0 18 4
A appendDefects() 0 8 2
A getSuiteRoot() 0 16 3
A getSuiteRootAttributes() 0 13 1
A isEmptyLineAttribute() 0 4 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ParaTest\Logging\JUnit;
6
7
use ParaTest\Logging\LogInterpreter;
8
9
class Writer
10
{
11
    /**
12
     * The name attribute of the testsuite being
13
     * written.
14
     *
15
     * @var string
16
     */
17
    protected $name;
18
19
    /**
20
     * @var \ParaTest\Logging\LogInterpreter
21
     */
22
    protected $interpreter;
23
24
    /**
25
     * @var \DOMDocument
26
     */
27
    protected $document;
28
29
    /**
30
     * A pattern for matching testsuite attributes.
31
     *
32
     * @var string
33
     */
34
    protected static $suiteAttrs = '/name|(?:test|assertion|failure|error)s|time|file/';
35
36
    /**
37
     * A pattern for matching testcase attrs.
38
     *
39
     * @var string
40
     */
41
    protected static $caseAttrs = '/name|class|file|line|assertions|time/';
42
43
    /**
44
     * A default suite to ease flattening of
45
     * suite structures.
46
     *
47
     * @var array
48
     */
49
    protected static $defaultSuite = [
50
        'tests' => 0,
51
        'assertions' => 0,
52
        'failures' => 0,
53
        'skipped' => 0,
54
        'errors' => 0,
55
        'time' => 0,
56
    ];
57
58 7
    public function __construct(LogInterpreter $interpreter, string $name = '')
59
    {
60 7
        $this->name = $name;
61 7
        $this->interpreter = $interpreter;
62 7
        $this->document = new \DOMDocument('1.0', 'UTF-8');
63 7
        $this->document->formatOutput = true;
64 7
    }
65
66
    /**
67
     * Get the name of the root suite being written.
68
     *
69
     * @return string
70
     */
71 1
    public function getName(): string
72
    {
73 1
        return $this->name;
74
    }
75
76
    /**
77
     * Returns the xml structure the writer
78
     * will use.
79
     *
80
     * @return string
81
     */
82 6
    public function getXml(): string
83
    {
84 6
        $suites = $this->interpreter->flattenCases();
85 6
        $root = $this->getSuiteRoot($suites);
86 6
        foreach ($suites as $suite) {
87 6
            $snode = $this->appendSuite($root, $suite);
88 6
            foreach ($suite->cases as $case) {
89 6
                $cnode = $this->appendCase($snode, $case);
0 ignored issues
show
Unused Code introduced by
$cnode 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...
90
            }
91
        }
92
93 6
        return $this->document->saveXML();
94
    }
95
96
    /**
97
     * Write the xml structure to a file path.
98
     *
99
     * @param $path
100
     */
101 2
    public function write(string $path)
102
    {
103 2
        file_put_contents($path, $this->getXml());
104 2
    }
105
106
    /**
107
     * Append a testsuite node to the given
108
     * root element.
109
     *
110
     * @param $root
111
     * @param TestSuite $suite
112
     *
113
     * @return \DOMElement
114
     */
115 6
    protected function appendSuite(\DOMElement $root, TestSuite $suite): \DOMElement
116
    {
117 6
        $suiteNode = $this->document->createElement('testsuite');
118 6
        $vars = get_object_vars($suite);
119 6
        foreach ($vars as $name => $value) {
120 6
            if (preg_match(static::$suiteAttrs, $name)) {
121 6
                $suiteNode->setAttribute($name, (string) $value);
122
            }
123
        }
124 6
        $root->appendChild($suiteNode);
125
126 6
        return $suiteNode;
127
    }
128
129
    /**
130
     * Append a testcase node to the given testsuite
131
     * node.
132
     *
133
     * @param $suiteNode
134
     * @param TestCase $case
135
     *
136
     * @return \DOMElement
137
     */
138 6
    protected function appendCase(\DOMElement $suiteNode, TestCase $case): \DOMElement
139
    {
140 6
        $caseNode = $this->document->createElement('testcase');
141 6
        $vars = get_object_vars($case);
142 6
        foreach ($vars as $name => $value) {
143 6
            if (preg_match(static::$caseAttrs, $name)) {
144 6
                if ($this->isEmptyLineAttribute($name, $value)) {
145 1
                    continue;
146
                }
147 6
                $caseNode->setAttribute($name, (string) $value);
148
            }
149
        }
150 6
        $suiteNode->appendChild($caseNode);
151 6
        $this->appendDefects($caseNode, $case->failures, 'failure');
152 6
        $this->appendDefects($caseNode, $case->errors, 'error');
153
154 6
        return $caseNode;
155
    }
156
157
    /**
158
     * Append error or failure nodes to the given testcase node.
159
     *
160
     * @param $caseNode
161
     * @param $defects
162
     * @param $type
163
     */
164 6
    protected function appendDefects(\DOMElement $caseNode, array $defects, string $type)
165
    {
166 6
        foreach ($defects as $defect) {
167 4
            $defectNode = $this->document->createElement($type, htmlspecialchars($defect['text'], ENT_XML1) . "\n");
168 4
            $defectNode->setAttribute('type', $defect['type']);
169 4
            $caseNode->appendChild($defectNode);
170
        }
171 6
    }
172
173
    /**
174
     * Get the root level testsuite node.
175
     *
176
     * @param $suites
177
     *
178
     * @return \DOMElement
179
     */
180 6
    protected function getSuiteRoot(array $suites): \DOMElement
181
    {
182 6
        $testsuites = $this->document->createElement('testsuites');
183 6
        $this->document->appendChild($testsuites);
184 6
        if (count($suites) === 1) {
185 3
            return $testsuites;
186
        }
187 3
        $rootSuite = $this->document->createElement('testsuite');
188 3
        $attrs = $this->getSuiteRootAttributes($suites);
189 3
        foreach ($attrs as $attr => $value) {
190 3
            $rootSuite->setAttribute($attr, (string) $value);
191
        }
192 3
        $testsuites->appendChild($rootSuite);
193
194 3
        return $rootSuite;
195
    }
196
197
    /**
198
     * Get the attributes used on the root testsuite
199
     * node.
200
     *
201
     * @param $suites
202
     *
203
     * @return mixed
204
     */
205
    protected function getSuiteRootAttributes(array $suites)
206
    {
207 3
        return array_reduce($suites, function (array $result, TestSuite $suite): array {
208 3
            $result['tests'] += $suite->tests;
209 3
            $result['assertions'] += $suite->assertions;
210 3
            $result['failures'] += $suite->failures;
211 3
            $result['skipped'] += $suite->skipped;
212 3
            $result['errors'] += $suite->errors;
213 3
            $result['time'] += $suite->time;
214
215 3
            return $result;
216 3
        }, array_merge(['name' => $this->name], self::$defaultSuite));
217
    }
218
219
    /**
220
     * Prevent writing empty "line" XML attributes which could break parsers.
221
     *
222
     * @param string $name
223
     * @param mixed  $value
224
     *
225
     * @return bool
226
     */
227 6
    private function isEmptyLineAttribute(string $name, $value): bool
228
    {
229 6
        return $name === 'line' && empty($value);
230
    }
231
}
232