diff --git a/README.md b/README.md index 99f9d49..e56a341 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,10 @@ Note: if no `--reportGitlab`, `--reportCheckstyle` or `--reportText` is set, it The third required argument and `--report` has been removed, and should be replaced by: `--reportGitlab=`, `--reportCheckstyle=` or `--reportText=` +# Migrating from 2 to 3 +The baseline now also supports multiple coverage files, as the inspect command. +An error is also raised if the baseline custom entry doesn't match the coverage. + ## About us At 123inkt (Part of Digital Revolution B.V.), every day more than 50 development professionals are working on improving our internal ERP diff --git a/src/Command/BaselineCommand.php b/src/Command/BaselineCommand.php index e91b6a7..9d2e9fb 100644 --- a/src/Command/BaselineCommand.php +++ b/src/Command/BaselineCommand.php @@ -12,6 +12,7 @@ use RuntimeException; use SplFileInfo; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -25,15 +26,15 @@ protected function configure(): void { $this->setName("baseline") ->setDescription("Generate phpfci.xml based on a given coverage.xml") - ->addArgument('coverage', InputOption::VALUE_REQUIRED, 'Path to phpunit\'s coverage.xml') - ->addArgument('config', InputOption::VALUE_REQUIRED, 'Path to write the configuration file') + ->addArgument('coverage', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'Path to phpunit\'s coverage.xml') + ->addOption('config', '', InputOption::VALUE_REQUIRED, 'Path to write the configuration file') ->addOption('threshold', '', InputOption::VALUE_REQUIRED, 'Minimum coverage threshold, defaults to 100', 100) ->addOption('baseDir', '', InputOption::VALUE_REQUIRED, 'Base directory from where to determine the relative config paths'); } protected function execute(InputInterface $input, OutputInterface $output): int { - $configArgument = $input->getArgument('config'); + $configArgument = $input->getOption('config'); if (is_array($configArgument)) { if (count($configArgument) === 0) { throw new RuntimeException('Missing config argument'); @@ -41,10 +42,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int $configArgument = reset($configArgument); } - $outputPath = new SplFileInfo((string)$configArgument); - $baseDir = $input->getOption('baseDir') ?? $outputPath->getPath(); - $threshold = $input->getOption('threshold'); - $coverageFilePath = FileUtil::getExistingFile($input->getArgument('coverage')); + $outputPath = new SplFileInfo((string)$configArgument); + $baseDir = $input->getOption('baseDir') ?? $outputPath->getPath(); + $threshold = $input->getOption('threshold'); + $coveragesFilepath = []; + $coverageArgument = $input->getArgument('coverage'); + if (is_array($coverageArgument) === false) { + $output->writeln('Coverage argument should be an array'); + return Command::FAILURE; + } + foreach ($coverageArgument as $coverageFilepath) { + $coveragesFilepath[] = FileUtil::getExistingFile($coverageFilepath); + } if (is_string($baseDir) === false) { $output->writeln("--baseDir argument is not valid. Expecting string argument"); @@ -59,8 +68,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int } // default to 100% coverage - $config = new InspectionConfig($baseDir, (int)$threshold, false); - $metrics = MetricsFactory::getFileMetrics(DOMDocumentFactory::getDOMDocument($coverageFilePath)); + $config = new InspectionConfig($baseDir, (int)$threshold, false); + $domDocuments = []; + foreach ($coveragesFilepath as $coverageFilepath) { + $domDocuments[] = DOMDocumentFactory::getDOMDocument($coverageFilepath); + } + $metrics = MetricsFactory::getFilesMetrics($domDocuments); // analyzer $failures = (new MetricsAnalyzer($metrics, $config))->analyze(); diff --git a/src/Lib/Metrics/Inspection/CustomCoverageAboveGlobalInspection.php b/src/Lib/Metrics/Inspection/CustomCoverageAboveGlobalInspection.php index d8e4fb4..cb4131a 100644 --- a/src/Lib/Metrics/Inspection/CustomCoverageAboveGlobalInspection.php +++ b/src/Lib/Metrics/Inspection/CustomCoverageAboveGlobalInspection.php @@ -21,10 +21,8 @@ public function inspect(?PathInspectionConfig $fileConfig, FileMetric $metric): } $globalCoverage = $this->config->getMinimumCoverage(); - $customCoverage = $fileConfig->getMinimumCoverage(); - // custom coverage is lower than global coverage, and file is above global coverage - if ($customCoverage <= $globalCoverage && $metric->getCoverage() >= $globalCoverage) { + if ($metric->getCoverage() >= $globalCoverage) { return new Failure($metric, $fileConfig->getMinimumCoverage(), Failure::UNNECESSARY_CUSTOM_COVERAGE); } diff --git a/src/Lib/Metrics/Inspection/BelowCustomCoverageInspection.php b/src/Lib/Metrics/Inspection/DifferentCustomCoverageInspection.php similarity index 60% rename from src/Lib/Metrics/Inspection/BelowCustomCoverageInspection.php rename to src/Lib/Metrics/Inspection/DifferentCustomCoverageInspection.php index ecc5a6d..dfbab8c 100644 --- a/src/Lib/Metrics/Inspection/BelowCustomCoverageInspection.php +++ b/src/Lib/Metrics/Inspection/DifferentCustomCoverageInspection.php @@ -10,14 +10,18 @@ /** * File coverage is below custom coverage */ -class BelowCustomCoverageInspection extends AbstractInspection +class DifferentCustomCoverageInspection extends AbstractInspection { public function inspect(?PathInspectionConfig $fileConfig, FileMetric $metric): ?Failure { - if ($fileConfig !== null && $metric->getCoverage() < $fileConfig->getMinimumCoverage()) { + if ($fileConfig !== null && (int)floor($metric->getCoverage()) < $fileConfig->getMinimumCoverage()) { return new Failure($metric, $fileConfig->getMinimumCoverage(), Failure::CUSTOM_COVERAGE_TOO_LOW); } + if ($fileConfig !== null && (int)floor($metric->getCoverage()) > $fileConfig->getMinimumCoverage()) { + return new Failure($metric, $fileConfig->getMinimumCoverage(), Failure::CUSTOM_COVERAGE_TOO_HIGH); + } + return null; } } diff --git a/src/Lib/Metrics/MetricsAnalyzer.php b/src/Lib/Metrics/MetricsAnalyzer.php index 9c66c84..a735564 100644 --- a/src/Lib/Metrics/MetricsAnalyzer.php +++ b/src/Lib/Metrics/MetricsAnalyzer.php @@ -4,7 +4,7 @@ namespace DigitalRevolution\CodeCoverageInspection\Lib\Metrics; use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\AbstractInspection; -use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\BelowCustomCoverageInspection; +use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\DifferentCustomCoverageInspection; use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\BelowGlobalCoverageInspection; use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\CustomCoverageAboveGlobalInspection; use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\UncoveredMethodsInspection; @@ -31,10 +31,10 @@ public function __construct(array $metrics, InspectionConfig $config) $this->config = $config; $this->inspections = [ - new BelowCustomCoverageInspection($config), + new CustomCoverageAboveGlobalInspection($config), + new DifferentCustomCoverageInspection($config), new BelowGlobalCoverageInspection($config), - new UncoveredMethodsInspection($config), - new CustomCoverageAboveGlobalInspection($config) + new UncoveredMethodsInspection($config) ]; } diff --git a/src/Model/Metric/Failure.php b/src/Model/Metric/Failure.php index d4c8dfd..330a780 100644 --- a/src/Model/Metric/Failure.php +++ b/src/Model/Metric/Failure.php @@ -9,6 +9,7 @@ class Failure public const CUSTOM_COVERAGE_TOO_LOW = 2; public const UNNECESSARY_CUSTOM_COVERAGE = 3; public const MISSING_METHOD_COVERAGE = 4; + public const CUSTOM_COVERAGE_TOO_HIGH = 5; private FileMetric $metric; private int $minimumCoverage; diff --git a/src/Renderer/RendererHelper.php b/src/Renderer/RendererHelper.php index ab98795..0730ce3 100644 --- a/src/Renderer/RendererHelper.php +++ b/src/Renderer/RendererHelper.php @@ -19,6 +19,10 @@ public static function renderReason(InspectionConfig $config, Failure $failure): case Failure::CUSTOM_COVERAGE_TOO_LOW: $message = "Custom file coverage is configured at %s%%. Current coverage is at %s%%. Improve coverage for this class."; + return sprintf($message, (string)$failure->getMinimumCoverage(), (string)$failure->getMetric()->getCoverage()); + case Failure::CUSTOM_COVERAGE_TOO_HIGH: + $message = "Custom file coverage is configured at %s%%. Current coverage is at %s%%. Edit the phpfci baseline for this class."; + return sprintf($message, (string)$failure->getMinimumCoverage(), (string)$failure->getMetric()->getCoverage()); case Failure::MISSING_METHOD_COVERAGE: $message = "File coverage is above %s%%, but method(s) `%s` has/have no coverage at all."; diff --git a/tests/Functional/Command/BaselineCommand/BaselineCommandTest.php b/tests/Functional/Command/BaselineCommand/BaselineCommandTest.php index f5f822f..ccb9789 100644 --- a/tests/Functional/Command/BaselineCommand/BaselineCommandTest.php +++ b/tests/Functional/Command/BaselineCommand/BaselineCommandTest.php @@ -20,12 +20,6 @@ class BaselineCommandTest extends TestCase /** @var vfsStreamDirectory */ private $fileSystem; - protected function setUp(): void - { - parent::setUp(); - $this->fileSystem = vfsStream::setup('output'); - } - /** * @throws Exception */ @@ -39,7 +33,7 @@ public function testBaselineCommand(): void // prepare command $command = new BaselineCommand(); - $input = new ArgvInput(['phpfci', '--baseDir', $baseDir, $coveragePath, $output]); + $input = new ArgvInput(['phpfci', $coveragePath, '--baseDir', $baseDir, '--config', $output]); $output = new BufferedOutput(); // run test case @@ -53,4 +47,10 @@ public function testBaselineCommand(): void static::assertSame($expected, $result); } + + protected function setUp(): void + { + parent::setUp(); + $this->fileSystem = vfsStream::setup('output'); + } } diff --git a/tests/Functional/Command/InspectCommand/Data/checkstyle.xml b/tests/Functional/Command/InspectCommand/Data/checkstyle.xml index 4f74988..bab77fb 100644 --- a/tests/Functional/Command/InspectCommand/Data/checkstyle.xml +++ b/tests/Functional/Command/InspectCommand/Data/checkstyle.xml @@ -4,7 +4,10 @@ - + + + + diff --git a/tests/Functional/Command/InspectCommand/Data/coverage.xml b/tests/Functional/Command/InspectCommand/Data/coverage.xml index eed1668..1a3678e 100644 --- a/tests/Functional/Command/InspectCommand/Data/coverage.xml +++ b/tests/Functional/Command/InspectCommand/Data/coverage.xml @@ -62,13 +62,13 @@ conditionals="0" coveredconditionals="0" statements="10" - coveredstatements="8" + coveredstatements="6" elements="0" coveredelements="0"/> - + - + diff --git a/tests/Unit/Lib/Metrics/Inspection/CustomCoverageAboveGlobalInspectionTest.php b/tests/Unit/Lib/Metrics/Inspection/CustomCoverageAboveGlobalInspectionTest.php index 9a42102..bcf1ac8 100644 --- a/tests/Unit/Lib/Metrics/Inspection/CustomCoverageAboveGlobalInspectionTest.php +++ b/tests/Unit/Lib/Metrics/Inspection/CustomCoverageAboveGlobalInspectionTest.php @@ -47,13 +47,4 @@ public function testInspectCoverageAboveGlobalCoverageShouldFail(): void static::assertSame(Failure::UNNECESSARY_CUSTOM_COVERAGE, $failure->getReason()); static::assertSame(40, $failure->getMinimumCoverage()); } - - public function testInspectCoverageCustomCoverageAboveGlobalCoverageShouldPass(): void - { - // global is 80 - $fileConfig = new PathInspectionConfig(PathInspectionConfig::TYPE_FILE, '/tmp/b', 85); - $metric = new FileMetric('/tmp/b/', 0, 83, [], []); - - static::assertNull($this->inspection->inspect($fileConfig, $metric)); - } } diff --git a/tests/Unit/Lib/Metrics/Inspection/BelowCustomCoverageInspectionTest.php b/tests/Unit/Lib/Metrics/Inspection/DifferentCustomCoverageInspectionTest.php similarity index 64% rename from tests/Unit/Lib/Metrics/Inspection/BelowCustomCoverageInspectionTest.php rename to tests/Unit/Lib/Metrics/Inspection/DifferentCustomCoverageInspectionTest.php index b8a9636..71fe2db 100644 --- a/tests/Unit/Lib/Metrics/Inspection/BelowCustomCoverageInspectionTest.php +++ b/tests/Unit/Lib/Metrics/Inspection/DifferentCustomCoverageInspectionTest.php @@ -4,7 +4,7 @@ namespace DigitalRevolution\CodeCoverageInspection\Tests\Unit\Lib\Metrics\Inspection; use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\AbstractInspection; -use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\BelowCustomCoverageInspection; +use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\DifferentCustomCoverageInspection; use DigitalRevolution\CodeCoverageInspection\Model\Config\InspectionConfig; use DigitalRevolution\CodeCoverageInspection\Model\Config\PathInspectionConfig; use DigitalRevolution\CodeCoverageInspection\Model\Metric\Failure; @@ -12,16 +12,16 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -#[CoversClass(BelowCustomCoverageInspection::class)] +#[CoversClass(DifferentCustomCoverageInspection::class)] #[CoversClass(AbstractInspection::class)] -class BelowCustomCoverageInspectionTest extends TestCase +class DifferentCustomCoverageInspectionTest extends TestCase { - private BelowCustomCoverageInspection $inspection; + private DifferentCustomCoverageInspection $inspection; protected function setUp(): void { $config = new InspectionConfig('/tmp/', 80); - $this->inspection = new BelowCustomCoverageInspection($config); + $this->inspection = new DifferentCustomCoverageInspection($config); } public function testInspectNoCustomCoverageShouldPass(): void @@ -43,10 +43,24 @@ public function testInspectCoverageBelowCustomCoverageShouldFail(): void static::assertSame(40, $failure->getMinimumCoverage()); } - public function testInspectCoverageAboveCustomCoverageShouldPass(): void + /** + * Custom coverage 40% + */ + public function testInspectCoverageAboveCustomCoverageShouldFail(): void { - $fileConfig = new PathInspectionConfig(PathInspectionConfig::TYPE_FILE, '/tmp/b', 40); - $metric = new FileMetric('/tmp/a/', 0, 60, [], []); + $fileConfig = new PathInspectionConfig(PathInspectionConfig::TYPE_FILE, '/tmp/b', 20); + $metric = new FileMetric('/tmp/a/', 0, 40, [], []); + + $failure = $this->inspection->inspect($fileConfig, $metric); + static::assertNotNull($failure); + static::assertSame(Failure::CUSTOM_COVERAGE_TOO_HIGH, $failure->getReason()); + static::assertSame(20, $failure->getMinimumCoverage()); + } + + public function testInspectCoverageCustomCoverageShouldPass(): void + { + $fileConfig = new PathInspectionConfig(PathInspectionConfig::TYPE_FILE, '/tmp/b', 50); + $metric = new FileMetric('/tmp/a/', 0, 50.8, [], []); static::assertNull($this->inspection->inspect($fileConfig, $metric)); } diff --git a/tests/Unit/Lib/Metrics/MetricsAnalyzerTest.php b/tests/Unit/Lib/Metrics/MetricsAnalyzerTest.php index a4f473a..4a63c4d 100644 --- a/tests/Unit/Lib/Metrics/MetricsAnalyzerTest.php +++ b/tests/Unit/Lib/Metrics/MetricsAnalyzerTest.php @@ -41,7 +41,7 @@ public function testAnalyzeFileWithCustomCoverageRuleShouldPass(): void { $metrics[] = new FileMetric('/a/b/c/test.php', 0, 45, [], []); $config = new InspectionConfig('/a/', 80, false); - $config->addPathInspection(new PathInspectionConfig(PathInspectionConfig::TYPE_FILE, 'b/c/test.php', 40)); + $config->addPathInspection(new PathInspectionConfig(PathInspectionConfig::TYPE_FILE, 'b/c/test.php', 45)); $analyzer = new MetricsAnalyzer($metrics, $config); $result = $analyzer->analyze(); @@ -66,12 +66,12 @@ public function testAnalyzeFileWithCustomCoverageAboveGlobalCoverageShouldFail() $metric = new FileMetric('/a/b/c/test.php', 0, 90, [], []); $metrics = [$metric]; $config = new InspectionConfig('/a/', 80, false); - $config->addPathInspection(new PathInspectionConfig(PathInspectionConfig::TYPE_FILE, 'b/c/test.php', 50)); + $config->addPathInspection(new PathInspectionConfig(PathInspectionConfig::TYPE_FILE, 'b/c/test.php', 90)); $analyzer = new MetricsAnalyzer($metrics, $config); $result = $analyzer->analyze(); static::assertCount(1, $result); - static::assertEquals([new Failure($metric, 50, Failure::UNNECESSARY_CUSTOM_COVERAGE)], $result); + static::assertEquals([new Failure($metric, 90, Failure::UNNECESSARY_CUSTOM_COVERAGE)], $result); } public function testAnalyzeFileWithUncoveredMethodsShouldFail(): void diff --git a/tests/Unit/Renderer/RendererHelperTest.php b/tests/Unit/Renderer/RendererHelperTest.php index 39a007e..f3fad70 100644 --- a/tests/Unit/Renderer/RendererHelperTest.php +++ b/tests/Unit/Renderer/RendererHelperTest.php @@ -17,11 +17,6 @@ class RendererHelperTest extends TestCase { private InspectionConfig $config; - protected function setUp(): void - { - $this->config = new InspectionConfig('base-path', 80); - } - public function testRenderReasonGlobalCoverageTooLow(): void { $metric = new FileMetric('foobar', 0, 80, [], []); @@ -40,6 +35,18 @@ public function testRenderReasonCustomCoverageTooLow(): void static::assertSame('Custom file coverage is configured at 30%. Current coverage is at 70%. Improve coverage for this class.', $message); } + public function testRenderReasonCustomCoverageTooHigh(): void + { + $metric = new FileMetric('foobar', 0, 50, [], []); + $failure = new Failure($metric, 70, Failure::CUSTOM_COVERAGE_TOO_HIGH, 5); + + $message = RendererHelper::renderReason($this->config, $failure); + static::assertSame( + 'Custom file coverage is configured at 70%. Current coverage is at 50%. Edit the phpfci baseline for this class.', + $message + ); + } + public function testRenderReasonMissingMethodCoverage(): void { $metric = new FileMetric('foobar', 0, 70, [new MethodMetric('coveredMethod', 100, 80), new MethodMetric('uncoveredMethod', 105, 0)], []); @@ -70,4 +77,9 @@ public function testRenderReasonShouldThrowExceptionWhenInvalid(): void $this->expectException(RuntimeException::class); RendererHelper::renderReason($this->config, $failure); } + + protected function setUp(): void + { + $this->config = new InspectionConfig('base-path', 80); + } }