From ab06715d5da37069bd715efda8aae8ab09aea018 Mon Sep 17 00:00:00 2001 From: Arne Blankerts Date: Sun, 26 Jul 2020 15:18:31 +0200 Subject: [PATCH] Tentatively implements #4288 --- .../Migration/MigrationBuilder.php | 54 +++++++++++++++++ .../Migration/MigrationBuilderException.php | 9 +++ .../Migration/MigrationException.php | 9 +++ .../Migration/Migrations/ConvertLogTypes.php | 42 +++++++++++++ .../Migrations/CoverageCloverToReport.php | 19 ++++++ .../Migrations/CoverageCrap4jToReport.php | 21 +++++++ .../Migrations/CoverageHtmlToReport.php | 21 +++++++ .../Migrations/CoveragePhpToReport.php | 19 ++++++ .../Migrations/CoverageTextToReport.php | 21 +++++++ .../Migrations/CoverageXmlToReport.php | 19 ++++++ .../Migrations/IntroduceCoverageElement.php | 30 ++++++++++ .../Migrations/LogToReportMigration.php | 59 +++++++++++++++++++ .../Migration/Migrations/Migration.php | 9 +++ ...ttributesFromFilterWhitelistToCoverage.php | 35 +++++++++++ .../MoveAttributesFromRootToCoverage.php | 33 +++++++++++ .../MoveWhitelistDirectoriesToCoverage.php | 34 +++++++++++ .../MoveWhitelistExcludesToCoverage.php | 29 +++++++++ .../Migrations/RemoveEmptyFilter.php | 33 +++++++++++ .../XmlConfiguration/Migration/Migrator.php | 15 +++-- tests/_files/Migration/phpunit-9.2.xml | 33 +++++++++++ tests/_files/Migration/phpunit-9.3.xml | 37 ++++++++++++ .../XmlConfigurationMigration/input-9.2.xml | 1 - .../XmlConfigurationMigration/output-9.3.xml | 1 - tests/unit/TextUI/MigrationTest.php | 3 +- 24 files changed, 579 insertions(+), 7 deletions(-) create mode 100644 src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php create mode 100644 src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php create mode 100644 src/TextUI/XmlConfiguration/Migration/MigrationException.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistDirectoriesToCoverage.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php create mode 100644 src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php create mode 100644 tests/_files/Migration/phpunit-9.2.xml create mode 100644 tests/_files/Migration/phpunit-9.3.xml diff --git a/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php b/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php new file mode 100644 index 00000000000..92ce9934ed1 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function version_compare; + +final class MigrationBuilder { + + private const availableMigrations = [ + '9.2' => [ + IntroduceCoverageElement::class, + MoveAttributesFromRootToCoverage::class, + MoveAttributesFromFilterWhitelistToCoverage::class, + MoveWhitelistDirectoriesToCoverage::class, + MoveWhitelistExcludesToCoverage::class, + RemoveEmptyFilter::class, + CoverageCloverToReport::class, + CoverageCrap4jToReport::class, + CoverageHtmlToReport::class, + CoveragePhpToReport::class, + CoverageTextToReport::class, + CoverageXmlToReport::class, + ConvertLogTypes::class + ] + ]; + + public function build(string $fromVersion): array + { + if (version_compare($fromVersion, '9.2', '<')) { + throw new MigrationBuilderException('Versions before 9.2 are not supported.'); + } + + $stack = []; + + foreach(self::availableMigrations as $version => $migrations) { + if (version_compare($version, $fromVersion, '<')) { + continue; + } + + foreach($migrations as $migration) { + $stack[] = new $migration; + } + } + + return $stack; + } +} diff --git a/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php b/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php new file mode 100644 index 00000000000..f8ec6208a9f --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php @@ -0,0 +1,9 @@ +getElementsByTagName('logging')->item(0); + if (!$logging instanceof DOMElement) { + return; + } + $types = [ + 'junit' => 'junit', + 'teamcity' => 'teamcity', + 'testdox-html' => 'testdoxHtml', + 'testdox-text' => 'testdoxText', + 'testdox-xml' => 'testdoxXml', + 'plain' => 'text' + ]; + + $logNodes = []; + foreach($logging->getElementsByTagName('log') as $logNode) { + if (!isset($types[$logNode->getAttribute('type')])) { + continue; + } + + $logNodes[] = $logNode; + } + + foreach($logNodes as $oldNode) { + $newLogNode = $document->createElement($types[$oldNode->getAttribute('type')]); + $newLogNode->setAttribute('outputFile', $oldNode->getAttribute('target')); + + $logging->replaceChild($newLogNode, $oldNode); + } + + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php new file mode 100644 index 00000000000..60c58241000 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php @@ -0,0 +1,19 @@ +ownerDocument->createElement('clover'); + $clover->setAttribute('outputFile', $logNode->getAttribute('target')); + + return $clover; + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php new file mode 100644 index 00000000000..c072d6fd7ed --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php @@ -0,0 +1,21 @@ +ownerDocument->createElement('crap4j'); + $crap4j->setAttribute('outputFile', $logNode->getAttribute('target')); + + $this->migrateAttributes($logNode, $crap4j, ['threshold']); + + return $crap4j; + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php new file mode 100644 index 00000000000..8b397b3f743 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php @@ -0,0 +1,21 @@ +ownerDocument->createElement('html'); + $html->setAttribute('outputDirectory', $logNode->getAttribute('target')); + + $this->migrateAttributes($logNode, $html, ['lowUpperBound', 'highLowerBound']); + + return $html; + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php b/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php new file mode 100644 index 00000000000..6516a61e866 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php @@ -0,0 +1,19 @@ +ownerDocument->createElement('php'); + $php->setAttribute('outputFile', $logNode->getAttribute('target')); + + return $php; + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php new file mode 100644 index 00000000000..b7842909e21 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php @@ -0,0 +1,21 @@ +ownerDocument->createElement('text'); + $text->setAttribute('outputFile', $logNode->getAttribute('target')); + + $this->migrateAttributes($logNode, $text, ['showUncoveredFiles', 'showOnlySummary']); + + return $text; + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php new file mode 100644 index 00000000000..3ae8d8a128b --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php @@ -0,0 +1,19 @@ +ownerDocument->createElement('xml'); + $xml->setAttribute('outputDirectory', $logNode->getAttribute('target')); + + return $xml; + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php b/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php new file mode 100644 index 00000000000..06535604089 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php @@ -0,0 +1,30 @@ +document = $document; + $this->coverage = $document->createElement('coverage'); + + $this->document->documentElement->insertBefore( + $this->coverage, + $this->document->documentElement->firstChild + ); + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php b/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php new file mode 100644 index 00000000000..e2df6a1e684 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php @@ -0,0 +1,59 @@ +getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $logNode = $this->findLogNode($document); + if ($logNode === null) { + return; + } + + $reportChild = $this->toReportFormat($logNode); + + $report = $coverage->getElementsByTagName('report')->item(0); + if ($report === null) { + $report = $coverage->appendChild($document->createElement('report')); + } + + $report->appendChild($reportChild); + $logNode->parentNode->removeChild($logNode); + } + + private function findLogNode($document): ?DOMElement { + $logNode = (new DOMXPath($document))->query( + sprintf('//logging/log[@type="%s"]', $this->forType()) + )->item(0); + + if (!$logNode instanceof DOMElement) { + return null; + } + + return $logNode; + } + + protected function migrateAttributes(DOMElement $src, DOMElement $dest, array $attributes): void { + foreach($attributes as $attr) { + if (!$src->hasAttribute($attr)) { + continue; + } + + $dest->setAttribute($attr, $src->getAttribute($attr)); + $src->removeAttribute($attr); + } + } + + abstract protected function forType(): string; + abstract protected function toReportFormat(DOMElement $logNode): DOMElement; + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php b/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php new file mode 100644 index 00000000000..a79f77d7357 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php @@ -0,0 +1,9 @@ +getElementsByTagName('whitelist')->item(0); + if (!$whitelist) { + return; + } + + /** @var ?DOMElement $coverage */ + $coverage = $document->getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $map = [ + 'addUncoveredFilesFromWhitelist' => 'includeUncoveredFiles', + 'processUncoveredFilesFromWhitelist' => 'processUncoveredFiles' + ]; + + foreach($map as $old => $new) { + if (!$whitelist->hasAttribute($old)) { + continue; + } + + $coverage->setAttribute($new, $whitelist->getAttribute($old)); + $whitelist->removeAttribute($old); + } + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php b/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php new file mode 100644 index 00000000000..2a21fd2a9dc --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php @@ -0,0 +1,33 @@ + 'disableCodeCoverageIgnore', + 'ignoreDeprecatedCodeUnitsFromCodeCoverage' => 'ignoreDeprecatedCodeUnits' + ]; + + $root = $document->documentElement; + + /** @var ?DOMElement $coverage */ + $coverage = $document->getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + foreach($map as $old => $new) { + if (!$root->hasAttribute($old)) { + continue; + } + + $coverage->setAttribute($new, $root->getAttribute($old)); + $root->removeAttribute($old); + } + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistDirectoriesToCoverage.php b/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistDirectoriesToCoverage.php new file mode 100644 index 00000000000..42594ff61d7 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistDirectoriesToCoverage.php @@ -0,0 +1,34 @@ +getElementsByTagName('whitelist')->item(0); + if ($whitelist === null) { + return; + } + + /** @var ?DOMElement $coverage */ + $coverage = $document->getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $include = $document->createElement('include'); + $coverage->appendChild($include); + + foreach($whitelist->childNodes as $child) { + if (!$child instanceof DOMElement || $child->nodeName !== 'directory') { + continue; + } + + $include->appendChild($child); + } + + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php b/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php new file mode 100644 index 00000000000..a5d6591c318 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php @@ -0,0 +1,29 @@ +getElementsByTagName('whitelist')->item(0); + if ($whitelist === null) { + return; + } + + $exclude = $whitelist->getElementsByTagName('exclude')->item(0); + if ($exclude === null) { + return; + } + + /** @var ?DOMElement $coverage */ + $coverage = $document->getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $coverage->appendChild($exclude); + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php b/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php new file mode 100644 index 00000000000..3857c6ae052 --- /dev/null +++ b/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php @@ -0,0 +1,33 @@ +getElementsByTagName('whitelist')->item(0); + if ($whitelist instanceof DOMElement) { + $this->ensureEmpty($whitelist); + $whitelist->parentNode->removeChild($whitelist); + } + + $filter = $document->getElementsByTagName('filter')->item(0); + if ($filter instanceof DOMElement) { + $this->ensureEmpty($filter); + $filter->parentNode->removeChild($filter); + } + } + + private function ensureEmpty(DOMElement $element) { + if ($element->attributes->length > 0) { + throw new MigrationException(sprintf('%s element has unexpected attributes', $element->nodeName)); + } + + if ($element->getElementsByTagName('*')->length > 0) { + throw new MigrationException(sprintf('%s element has unexpected children', $element->nodeName)); + } + } + +} diff --git a/src/TextUI/XmlConfiguration/Migration/Migrator.php b/src/TextUI/XmlConfiguration/Migration/Migrator.php index 14b81e78a47..805c93e7ddc 100644 --- a/src/TextUI/XmlConfiguration/Migration/Migrator.php +++ b/src/TextUI/XmlConfiguration/Migration/Migrator.php @@ -17,7 +17,7 @@ */ final class Migrator { - public function migrateFrom92To93(string $filename): string + public function migrate(string $filename): string { $oldXsdFilename = __DIR__ . '/phpunit-9.2.xsd'; @@ -25,14 +25,14 @@ public function migrateFrom92To93(string $filename): string $oldXsdFilename = __PHPUNIT_PHAR_ROOT__ . '/src/TextUI/XmlConfiguration/Migration/phpunit-9.2.xsd'; } - $oldDocument = Xml::loadFile( + $configurationDocument = Xml::loadFile( $filename, false, true, true ); - $validationErrors = Xml::validate($oldDocument, $oldXsdFilename); + $validationErrors = Xml::validate($configurationDocument, $oldXsdFilename); if (!empty($validationErrors)) { $message = \sprintf( @@ -51,6 +51,13 @@ public function migrateFrom92To93(string $filename): string throw new Exception($message); } - return ''; + foreach((new MigrationBuilder)->build('9.2') as $migration) { + $migration->migrate($configurationDocument); + } + + $configurationDocument->formatOutput = true; + $configurationDocument->preserveWhiteSpace = false; + + return $configurationDocument->saveXML(); } } diff --git a/tests/_files/Migration/phpunit-9.2.xml b/tests/_files/Migration/phpunit-9.2.xml new file mode 100644 index 00000000000..f81559aaaa7 --- /dev/null +++ b/tests/_files/Migration/phpunit-9.2.xml @@ -0,0 +1,33 @@ + + + + + + + src + + + src/generated + src/autoload.php + + + + + + + + + + + + + + + + + diff --git a/tests/_files/Migration/phpunit-9.3.xml b/tests/_files/Migration/phpunit-9.3.xml new file mode 100644 index 00000000000..9eb293ff999 --- /dev/null +++ b/tests/_files/Migration/phpunit-9.3.xml @@ -0,0 +1,37 @@ + + + + + + + src + + + + src/generated + src/autoload.php + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/_files/XmlConfigurationMigration/input-9.2.xml b/tests/_files/XmlConfigurationMigration/input-9.2.xml index f81559aaaa7..d9b80a3bcfe 100644 --- a/tests/_files/XmlConfigurationMigration/input-9.2.xml +++ b/tests/_files/XmlConfigurationMigration/input-9.2.xml @@ -4,7 +4,6 @@ cacheTokens="true" disableCodeCoverageIgnore="true" ignoreDeprecatedCodeUnitsFromCodeCoverage="true"> - - assertStringEqualsFile( __DIR__ . '/../../_files/XmlConfigurationMigration/output-9.3.xml', - (new Migrator)->migrateFrom92To93( + (new Migrator)->migrate( __DIR__ . '/../../_files/XmlConfigurationMigration/input-9.2.xml' ) );