Skip to content

Commit

Permalink
Introduce XmlConfigurationVersionProvider (#1378)
Browse files Browse the repository at this point in the history
Fixes #1283 to the point we're using Infection with the newest PHPUnit configuration.
  • Loading branch information
sanmai committed Oct 27, 2020
1 parent a209c95 commit 3119045
Show file tree
Hide file tree
Showing 8 changed files with 482 additions and 58 deletions.
10 changes: 5 additions & 5 deletions phpunit.xml.dist
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/schema/9.2.xsd"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="./vendor/autoload.php"
colors="true"
executionOrder="random"
Expand All @@ -23,11 +23,11 @@
</testsuite>
</testsuites>

<filter>
<whitelist>
<coverage>
<include>
<directory>src</directory>
</whitelist>
</filter>
</include>
</coverage>

<groups>
<exclude>
Expand Down
2 changes: 2 additions & 0 deletions src/TestFramework/PhpUnit/Adapter/PhpUnitAdapterFactory.php
Expand Up @@ -44,6 +44,7 @@
use Infection\TestFramework\PhpUnit\Config\Builder\MutationConfigBuilder;
use Infection\TestFramework\PhpUnit\Config\Path\PathReplacer;
use Infection\TestFramework\PhpUnit\Config\XmlConfigurationManipulator;
use Infection\TestFramework\PhpUnit\Config\XmlConfigurationVersionProvider;
use Infection\TestFramework\VersionParser;
use function Safe\file_get_contents;
use Symfony\Component\Filesystem\Filesystem;
Expand Down Expand Up @@ -87,6 +88,7 @@ public static function create(
$tmpDir,
$testFrameworkConfigContent,
$configManipulator,
new XmlConfigurationVersionProvider(),
$sourceDirectories
),
new MutationConfigBuilder(
Expand Down
52 changes: 16 additions & 36 deletions src/TestFramework/PhpUnit/Config/Builder/InitialConfigBuilder.php
Expand Up @@ -36,10 +36,9 @@
namespace Infection\TestFramework\PhpUnit\Config\Builder;

use DOMDocument;
use DOMElement;
use DOMNode;
use Infection\TestFramework\Config\InitialConfigBuilder as ConfigBuilder;
use Infection\TestFramework\PhpUnit\Config\XmlConfigurationManipulator;
use Infection\TestFramework\PhpUnit\Config\XmlConfigurationVersionProvider;
use Infection\TestFramework\SafeDOMXPath;
use function Safe\file_put_contents;
use function Safe\sprintf;
Expand All @@ -54,6 +53,7 @@ class InitialConfigBuilder implements ConfigBuilder
private $tmpDir;
private $originalXmlConfigContent;
private $configManipulator;
private $versionProvider;
private $srcDirs;

/**
Expand All @@ -63,6 +63,7 @@ public function __construct(
string $tmpDir,
string $originalXmlConfigContent,
XmlConfigurationManipulator $configManipulator,
XmlConfigurationVersionProvider $versionProvider,
array $srcDirs
) {
Assert::notEmpty(
Expand All @@ -73,6 +74,7 @@ public function __construct(
$this->tmpDir = $tmpDir;
$this->originalXmlConfigContent = $originalXmlConfigContent;
$this->configManipulator = $configManipulator;
$this->versionProvider = $versionProvider;
$this->srcDirs = $srcDirs;
}

Expand All @@ -91,7 +93,7 @@ public function build(string $version): string

$this->configManipulator->validate($path, $xPath);

$this->addCoverageFilterWhitelistIfDoesNotExist($xPath);
$this->addCoverageNodes($version, $xPath);
$this->addRandomTestsOrderAttributesIfNotSet($version, $xPath);
$this->addFailOnAttributesIfNotSet($version, $xPath);
$this->configManipulator->replaceWithAbsolutePaths($xPath);
Expand All @@ -112,50 +114,28 @@ private function buildPath(): string
return $this->tmpDir . '/phpunitConfiguration.initial.infection.xml';
}

private function addCoverageFilterWhitelistIfDoesNotExist(SafeDOMXPath $xPath): void
private function addCoverageNodes(string $version, SafeDOMXPath $xPath): void
{
$filterWhitelistNode = $this->getNode($xPath, 'filter/whitelist');
$coverageIncludeNode = $this->getNode($xPath, 'coverage/include');
if (version_compare($version, '10', '>=')) {
$this->configManipulator->addCoverageIncludeNodesUnlessTheyExist($xPath, $this->srcDirs);

if ($filterWhitelistNode || $coverageIncludeNode) {
return;
}

$filterNode = $this->createNode($xPath->document, 'filter');
if (version_compare($version, '9.3', '<')) {
$this->configManipulator->addLegacyCoverageWhitelistNodesUnlessTheyExist($xPath, $this->srcDirs);

$whiteListNode = $xPath->document->createElement('whitelist');

foreach ($this->srcDirs as $srcDir) {
$directoryNode = $xPath->document->createElement(
'directory',
$srcDir
);

$whiteListNode->appendChild($directoryNode);
return;
}

$filterNode->appendChild($whiteListNode);
}

private function getNode(SafeDOMXPath $xPath, string $nodeName): ?DOMNode
{
$nodeList = $xPath->query(sprintf('/phpunit/%s', $nodeName));
// For versions between 9.3 and 10.0, fallback to version provider
if (version_compare($this->versionProvider->provide($xPath), '9.3', '>=')) {
$this->configManipulator->addCoverageIncludeNodesUnlessTheyExist($xPath, $this->srcDirs);

if ($nodeList->length) {
return $nodeList->item(0);
return;
}

return null;
}

private function createNode(DOMDocument $dom, string $nodeName): DOMElement
{
$node = $dom->createElement($nodeName);
$document = $dom->documentElement;
Assert::isInstanceOf($document, DOMElement::class);
$document->appendChild($node);

return $node;
$this->configManipulator->addLegacyCoverageWhitelistNodesUnlessTheyExist($xPath, $this->srcDirs);
}

private function addRandomTestsOrderAttributesIfNotSet(string $version, SafeDOMXPath $xPath): void
Expand Down
58 changes: 58 additions & 0 deletions src/TestFramework/PhpUnit/Config/XmlConfigurationManipulator.php
Expand Up @@ -35,6 +35,7 @@

namespace Infection\TestFramework\PhpUnit\Config;

use DOMDocument;
use DOMElement;
use const FILTER_VALIDATE_URL;
use function filter_var;
Expand Down Expand Up @@ -129,6 +130,22 @@ public function removeExistingPrinters(SafeDOMXPath $xPath): void
);
}

/**
* @param string[] $srcDirs
*/
public function addLegacyCoverageWhitelistNodesUnlessTheyExist(SafeDOMXPath $xPath, array $srcDirs): void
{
$this->addCoverageNodesUnlessTheyExist('filter', 'whitelist', $xPath, $srcDirs);
}

/**
* @param string[] $srcDirs
*/
public function addCoverageIncludeNodesUnlessTheyExist(SafeDOMXPath $xPath, array $srcDirs): void
{
$this->addCoverageNodesUnlessTheyExist('coverage', 'include', $xPath, $srcDirs);
}

public function validate(string $configPath, SafeDOMXPath $xPath): bool
{
if ($xPath->query('/phpunit')->length === 0) {
Expand Down Expand Up @@ -163,6 +180,47 @@ public function removeDefaultTestSuite(SafeDOMXPath $xPath): void
);
}

/**
* @param string[] $srcDirs
*/
private function addCoverageNodesUnlessTheyExist(string $parentName, string $listName, SafeDOMXPath $xPath, array $srcDirs): void
{
if ($this->nodeExists($xPath, "{$parentName}/{$listName}")) {
return;
}

$filterNode = $this->createNode($xPath->document, $parentName);

$listNode = $xPath->document->createElement($listName);

foreach ($srcDirs as $srcDir) {
$directoryNode = $xPath->document->createElement(
'directory',
$srcDir
);

$listNode->appendChild($directoryNode);
}

$filterNode->appendChild($listNode);
}

private function nodeExists(SafeDOMXPath $xPath, string $nodeName): bool
{
return $xPath->query(sprintf('/phpunit/%s', $nodeName))->length > 0;
}

private function createNode(DOMDocument $dom, string $nodeName): DOMElement
{
$node = $dom->createElement($nodeName);
$document = $dom->documentElement;

Assert::isInstanceOf($document, DOMElement::class);
$document->appendChild($node);

return $node;
}

private function getXmlErrorsString(): string
{
$errorsString = '';
Expand Down
118 changes: 118 additions & 0 deletions src/TestFramework/PhpUnit/Config/XmlConfigurationVersionProvider.php
@@ -0,0 +1,118 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\TestFramework\PhpUnit\Config;

use Infection\TestFramework\SafeDOMXPath;
use function Safe\preg_match;

/**
* @internal
*/
final class XmlConfigurationVersionProvider
{
private const LAST_LEGACY_VERSION = '9.2';
private const NEXT_MAINSTREAM_VERSION = '9.3';

public function provide(SafeDOMXPath $xPath): string
{
// <coverage>
if ($xPath->query('/phpunit/coverage')->length) {
return self::NEXT_MAINSTREAM_VERSION;
}

// <logging><log type="*">
if ($xPath->query('/phpunit/logging/log')->length) {
return self::LAST_LEGACY_VERSION;
}

// <logging><*> where <*> isn't <log>
if ($xPath->query('/phpunit/logging/*[name(.) != "log"]')->length) {
return self::NEXT_MAINSTREAM_VERSION;
}

// <filter><whitelist>
if ($xPath->query('/phpunit/filter')->length) {
return self::LAST_LEGACY_VERSION;
}

foreach ([
'disableCodeCoverageIgnore', // <phpunit disableCodeCoverageIgnore="true">
'ignoreDeprecatedCodeUnitsFromCodeCoverage', // <phpunit ignoreDeprecatedCodeUnitsFromCodeCoverage="true">
] as $legacyAttribute) {
if ($xPath->query("/phpunit[@{$legacyAttribute}]")->length) {
return self::LAST_LEGACY_VERSION;
}
}

$schemaUri = $this->getSchemaURI($xPath);

if ($schemaUri === null) {
// Best guess it is a legacy version: config upgrader will add a path to XSD
return self::LAST_LEGACY_VERSION;
}

/*
* We're looking for these:
*
* vendor/phpunit/phpunit/schema/9.2.xsd
* http://schema.phpunit.de/6.0/phpunit.xsd
* https://schema.phpunit.de/9.3/phpunit.xsd
*/
$match = [];

if (preg_match('#(\d+\.\d)(/phpunit)?\.xsd$#', $schemaUri, $match)) {
return $match[1];
}

// Without any clues we assume it's a legacy version: it's most prevalent
return self::LAST_LEGACY_VERSION;
}

private function getSchemaURI(SafeDOMXPath $xPath): ?string
{
if ($xPath->query('namespace::xsi')->length === 0) {
return null;
}

$schema = $xPath->query('/phpunit/@xsi:noNamespaceSchemaLocation');

if ($schema->length === 0) {
return null;
}

return $schema[0]->nodeValue;
}
}

0 comments on commit 3119045

Please sign in to comment.