Passed
Push — master ( 33ba2d...ddbcf3 )
by Maks
02:41
created

addFailOnAttributesIfNotSet()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This code is licensed under the BSD 3-Clause License.
4
 *
5
 * Copyright (c) 2017, Maks Rafalko
6
 * All rights reserved.
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions are met:
10
 *
11
 * * Redistributions of source code must retain the above copyright notice, this
12
 *   list of conditions and the following disclaimer.
13
 *
14
 * * Redistributions in binary form must reproduce the above copyright notice,
15
 *   this list of conditions and the following disclaimer in the documentation
16
 *   and/or other materials provided with the distribution.
17
 *
18
 * * Neither the name of the copyright holder nor the names of its
19
 *   contributors may be used to endorse or promote products derived from
20
 *   this software without specific prior written permission.
21
 *
22
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
 */
33
34
declare(strict_types=1);
35
36
namespace Infection\TestFramework\PhpUnit\Config\Builder;
37
38
use DOMDocument;
39
use DOMElement;
40
use DOMNode;
41
use Infection\TestFramework\Config\InitialConfigBuilder as ConfigBuilder;
42
use Infection\TestFramework\PhpUnit\Adapter\PhpUnitAdapter;
43
use Infection\TestFramework\PhpUnit\Config\XmlConfigurationManipulator;
44
use Infection\TestFramework\SafeDOMXPath;
45
use function Safe\file_put_contents;
46
use function Safe\sprintf;
47
use function version_compare;
48
use Webmozart\Assert\Assert;
49
50
/**
51
 * @internal
52
 */
53
class InitialConfigBuilder implements ConfigBuilder
54
{
55
    private $tmpDir;
56
    private $originalXmlConfigContent;
57
    private $configManipulator;
58
    private $jUnitFilePath;
59
    private $srcDirs;
60
    private $skipCoverage;
61
62
    /**
63
     * @param string[] $srcDirs
64
     */
65
    public function __construct(
66
        string $tmpDir,
67
        string $originalXmlConfigContent,
68
        XmlConfigurationManipulator $configManipulator,
69
        string $jUnitFilePath,
70
        array $srcDirs,
71
        bool $skipCoverage
72
    ) {
73
        Assert::notEmpty(
74
            $originalXmlConfigContent,
75
            'The original XML config content cannot be an empty string'
76
        );
77
78
        $this->tmpDir = $tmpDir;
79
        $this->originalXmlConfigContent = $originalXmlConfigContent;
80
        $this->configManipulator = $configManipulator;
81
        $this->jUnitFilePath = $jUnitFilePath;
82
        $this->srcDirs = $srcDirs;
83
        $this->skipCoverage = $skipCoverage;
84
    }
85
86
    public function build(string $version): string
87
    {
88
        $path = $this->buildPath();
89
90
        $dom = new DOMDocument();
91
        $dom->preserveWhiteSpace = false;
92
        $dom->formatOutput = true;
93
        $success = @$dom->loadXML($this->originalXmlConfigContent);
94
95
        Assert::true($success);
96
97
        $xPath = new SafeDOMXPath($dom);
98
99
        $this->configManipulator->validate($path, $xPath);
100
101
        $this->addCoverageFilterWhitelistIfDoesNotExist($xPath);
102
        $this->addRandomTestsOrderAttributesIfNotSet($version, $xPath);
103
        $this->addFailOnAttributesIfNotSet($version, $xPath);
104
        $this->configManipulator->replaceWithAbsolutePaths($xPath);
105
        $this->configManipulator->setStopOnFailure($xPath);
106
        $this->configManipulator->deactivateColours($xPath);
107
        $this->configManipulator->deactivateResultCaching($xPath);
108
        $this->configManipulator->deactivateStderrRedirection($xPath);
109
        $this->configManipulator->removeExistingLoggers($xPath);
110
        $this->configManipulator->removeExistingPrinters($xPath);
111
112
        if (!$this->skipCoverage) {
113
            $this->addCodeCoverageLogger($xPath);
114
            $this->addJUnitLogger($xPath);
115
        }
116
117
        file_put_contents($path, $dom->saveXML());
118
119
        return $path;
120
    }
121
122
    private function buildPath(): string
123
    {
124
        return $this->tmpDir . '/phpunitConfiguration.initial.infection.xml';
125
    }
126
127
    private function addJUnitLogger(SafeDOMXPath $xPath): void
128
    {
129
        $logging = $this->getOrCreateNode($xPath, 'logging');
130
131
        $junitLog = $xPath->document->createElement('log');
132
        $junitLog->setAttribute('type', 'junit');
133
        $junitLog->setAttribute('target', $this->jUnitFilePath);
134
135
        $logging->appendChild($junitLog);
136
    }
137
138
    private function addCodeCoverageLogger(SafeDOMXPath $xPath): void
139
    {
140
        $logging = $this->getOrCreateNode($xPath, 'logging');
141
142
        $coverageXmlLog = $xPath->document->createElement('log');
143
        $coverageXmlLog->setAttribute('type', 'coverage-xml');
144
        $coverageXmlLog->setAttribute('target', $this->tmpDir . '/' . PhpUnitAdapter::COVERAGE_DIR);
145
146
        $logging->appendChild($coverageXmlLog);
147
    }
148
149
    private function addCoverageFilterWhitelistIfDoesNotExist(SafeDOMXPath $xPath): void
150
    {
151
        $filterNode = $this->getNode($xPath, 'filter');
152
153
        if (!$filterNode) {
154
            $filterNode = $this->createNode($xPath->document, 'filter');
155
156
            $whiteListNode = $xPath->document->createElement('whitelist');
157
158
            foreach ($this->srcDirs as $srcDir) {
159
                $directoryNode = $xPath->document->createElement(
160
                    'directory',
161
                    $srcDir
162
                );
163
164
                $whiteListNode->appendChild($directoryNode);
165
            }
166
167
            $filterNode->appendChild($whiteListNode);
168
        }
169
    }
170
171
    private function getOrCreateNode(SafeDOMXPath $xPath, string $nodeName): DOMElement
172
    {
173
        $node = $this->getNode($xPath, $nodeName);
174
175
        if (!$node) {
176
            $node = $this->createNode($xPath->document, $nodeName);
177
        }
178
        Assert::isInstanceOf($node, DOMElement::class);
179
180
        return $node;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node could return the type DOMNode which includes types incompatible with the type-hinted return DOMElement. Consider adding an additional type-check to rule them out.
Loading history...
181
    }
182
183
    private function getNode(SafeDOMXPath $xPath, string $nodeName): ?DOMNode
184
    {
185
        $nodeList = $xPath->query(sprintf('/phpunit/%s', $nodeName));
186
187
        if ($nodeList->length) {
188
            return $nodeList->item(0);
189
        }
190
191
        return null;
192
    }
193
194
    private function createNode(DOMDocument $dom, string $nodeName): DOMElement
195
    {
196
        $node = $dom->createElement($nodeName);
197
        $document = $dom->documentElement;
198
        Assert::isInstanceOf($document, DOMElement::class);
199
        $document->appendChild($node);
200
201
        return $node;
202
    }
203
204
    private function addRandomTestsOrderAttributesIfNotSet(string $version, SafeDOMXPath $xPath): void
205
    {
206
        if (version_compare($version, '7.2', '<')) {
207
            return;
208
        }
209
210
        if ($this->addAttributeIfNotSet('executionOrder', 'random', $xPath)) {
211
            $this->addAttributeIfNotSet('resolveDependencies', 'true', $xPath);
212
        }
213
    }
214
215
    private function addFailOnAttributesIfNotSet(string $version, SafeDOMXPath $xPath): void
216
    {
217
        if (version_compare($version, '5.2', '<')) {
218
            return;
219
        }
220
221
        $this->addAttributeIfNotSet('failOnRisky', 'true', $xPath);
222
        $this->addAttributeIfNotSet('failOnWarning', 'true', $xPath);
223
    }
224
225
    private function addAttributeIfNotSet(string $attribute, string $value, SafeDOMXPath $xPath): bool
226
    {
227
        $nodeList = $xPath->query(sprintf('/phpunit/@%s', $attribute));
228
229
        if (!$nodeList->length) {
230
            $node = $xPath->query('/phpunit')[0];
231
            $node->setAttribute($attribute, $value);
232
233
            return true;
234
        }
235
236
        return false;
237
    }
238
}
239