Skip to content

Commit 9831b97

Browse files
authored
Merge pull request #16 from 123inkt/ignore-uncovered-methods
Ignore uncovered methods
2 parents 40c1f08 + 1113024 commit 9831b97

15 files changed

+261
-8
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ File: `phpfci.xml`
3030
<phpfci xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3131
xsi:noNamespaceSchemaLocation="vendor/digitalrevolution/phpunit-file-coverage-inspection/resources/phpfci.xsd"
3232
min-coverage="100"
33+
allow-uncovered-methods="false"
3334
>
3435
<custom-coverage>
3536
<!-- directory based coverage rule -->
@@ -39,6 +40,11 @@ File: `phpfci.xml`
3940
<!-- file rule will always superceed a directory rule -->
4041
<file path="src/Lib/Config/File.php" min="80"/>
4142
</custom-coverage>
43+
44+
<!-- when 'allow-uncovered-methods' is set to false, override this behaviour for specific files: -->
45+
<ignore-uncovered-methods>
46+
<file path="src/Command/ExampleCommand.php"/>
47+
</ignore-uncovered-methods>
4248
</phpfci>
4349
```
4450

resources/phpfci.xsd

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,30 @@
1414
</xs:complexType>
1515
</xs:element>
1616

17+
<xs:element name="ignore-uncovered-methods">
18+
<xs:complexType>
19+
<xs:sequence>
20+
<xs:element ref="file" maxOccurs="unbounded"/>
21+
</xs:sequence>
22+
</xs:complexType>
23+
</xs:element>
24+
25+
<xs:element name="file">
26+
<xs:complexType>
27+
<xs:attribute name="path" type="xs:string"/>
28+
</xs:complexType>
29+
</xs:element>
30+
1731
<xs:group name="pathGroup">
1832
<xs:sequence>
1933
<xs:choice maxOccurs="unbounded">
20-
<xs:element name="directory" type="directory"/>
21-
<xs:element name="file" type="file"/>
34+
<xs:element name="directory" type="custom-coverage-directory"/>
35+
<xs:element name="file" type="custom-coverage-file"/>
2236
</xs:choice>
2337
</xs:sequence>
2438
</xs:group>
2539

26-
<xs:complexType name="directory">
40+
<xs:complexType name="custom-coverage-directory">
2741
<xs:simpleContent>
2842
<xs:extension base="xs:string">
2943
<xs:attribute name="path" type="xs:string"/>
@@ -32,7 +46,7 @@
3246
</xs:simpleContent>
3347
</xs:complexType>
3448

35-
<xs:complexType name="file">
49+
<xs:complexType name="custom-coverage-file">
3650
<xs:simpleContent>
3751
<xs:extension base="xs:string">
3852
<xs:attribute name="path" type="xs:string"/>
@@ -46,6 +60,7 @@
4660
<xs:complexType>
4761
<xs:sequence>
4862
<xs:element ref="custom-coverage" minOccurs="0"/>
63+
<xs:element ref="ignore-uncovered-methods" minOccurs="0"/>
4964
</xs:sequence>
5065
<xs:attribute name="min-coverage" type="percentage"/>
5166
<xs:attribute name="allow-uncovered-methods" type="xs:boolean" default="false"/>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DigitalRevolution\CodeCoverageInspection\Lib\IO;
5+
6+
use DigitalRevolution\CodeCoverageInspection\Lib\Utility\XMLUtil;
7+
use DigitalRevolution\CodeCoverageInspection\Model\Config\IgnoreUncoveredMethodFile;
8+
use DigitalRevolution\CodeCoverageInspection\Model\Config\PathInspectionConfig;
9+
use DOMNode;
10+
use RuntimeException;
11+
12+
class IgnoreUncoveredMethodFileFactory
13+
{
14+
public static function createFromNode(DOMNode $item): IgnoreUncoveredMethodFile
15+
{
16+
if ($item->nodeName !== PathInspectionConfig::TYPE_FILE) {
17+
throw new RuntimeException('Invalid node type: ' . $item->nodeName);
18+
}
19+
20+
return new IgnoreUncoveredMethodFile((string)XMLUtil::getAttribute($item, 'path'));
21+
}
22+
}

src/Lib/IO/InspectionConfigFactory.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ public static function fromDOMDocument(string $basePath, DOMDocument $doc): Insp
2222
$inspectionConfig->addPathInspection(PathInspectionConfigFactory::createFromNode($node));
2323
}
2424

25+
// find all ignore uncovered method nodes
26+
$nodes = XMLUtil::query($xpath, "/phpfci/ignore-uncovered-methods/file");
27+
foreach ($nodes as $node) {
28+
$inspectionConfig->addIgnoreUncoveredMethodFile(IgnoreUncoveredMethodFileFactory::createFromNode($node));
29+
}
30+
2531
return $inspectionConfig;
2632
}
2733

src/Lib/Metrics/Inspection/UncoveredMethodsInspection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public function inspect(?PathInspectionConfig $fileConfig, FileMetric $metric):
1717
{
1818
$uncoveredMethod = FileMetricAnalyzer::getUncoveredMethodMetric($metric);
1919

20+
// uncovered method is ignored
21+
if ($this->config->hasIgnoreUncoveredMethodFile($metric->getFilepath())) {
22+
return null;
23+
}
24+
2025
if ($fileConfig === null && $uncoveredMethod !== null && $this->config->isUncoveredAllowed() === false) {
2126
return new Failure($metric, $this->config->getMinimumCoverage(), Failure::MISSING_METHOD_COVERAGE, $uncoveredMethod->getLineNumber());
2227
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DigitalRevolution\CodeCoverageInspection\Model\Config;
5+
6+
class IgnoreUncoveredMethodFile
7+
{
8+
private string $filepath;
9+
10+
public function __construct(string $filepath)
11+
{
12+
// normalize slashes
13+
$this->filepath = str_replace('\\', '/', $filepath);
14+
}
15+
16+
public function getFilepath(): string
17+
{
18+
return $this->filepath;
19+
}
20+
}

src/Model/Config/InspectionConfig.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ class InspectionConfig
88
private int $minimumCoverage;
99
private bool $uncoveredAllowed;
1010
/** @var PathInspectionConfig[] */
11-
private array $customCoverage = [];
11+
private array $customCoverage = [];
12+
/** @var IgnoreUncoveredMethodFile[] */
13+
private array $ignoreUncoveredFiles = [];
1214
private string $basePath;
1315

1416
public function __construct(string $basePath, int $minimumCoverage, bool $uncoveredAllowed = false)
@@ -65,4 +67,22 @@ public function getPathInspection(string $path): ?PathInspectionConfig
6567

6668
return $bestConfig;
6769
}
70+
71+
public function addIgnoreUncoveredMethodFile(IgnoreUncoveredMethodFile $file): self
72+
{
73+
$this->ignoreUncoveredFiles[] = $file;
74+
75+
return $this;
76+
}
77+
78+
public function hasIgnoreUncoveredMethodFile(string $file): bool
79+
{
80+
foreach ($this->ignoreUncoveredFiles as $relativeFilePath) {
81+
if (str_ends_with($file, $relativeFilePath->getFilepath())) {
82+
return true;
83+
}
84+
}
85+
86+
return false;
87+
}
6888
}

tests/Functional/Command/InspectCommand/Data/checkstyle.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@
1212
<file name="/home/workspace/test/case/unnecessary-custom-rule.php">
1313
<error line="1" column="0" severity="error" message="A custom file coverage is configured at 75%, but the current file coverage 90% exceeds the project coverage 80%. Remove `/home/workspace/test/case/unnecessary-custom-rule.php` from phpfci.xml custom-coverage rules." source="phpunit-file-coverage-inspection"/>
1414
</file>
15+
<file name="/home/workspace/test/case/uncovered-method.php">
16+
<error line="16" column="0" severity="error" message="File coverage is above 80%, but method(s) `__construct, myMethod` has/have no coverage at all." source="phpunit-file-coverage-inspection"/>
17+
</file>
1518
</checkstyle>

tests/Functional/Command/InspectCommand/Data/coverage.xml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,39 @@
103103
elements="0"
104104
coveredelements="0"/>
105105
</file>
106+
107+
<!-- coverage 90% and uncovered methods -->
108+
<file name="/home/workspace/test/case/uncovered-method.php">
109+
<line num="16" type="method" name="__construct" visibility="public" complexity="1" crap="2" count="0"/>
110+
<line num="30" type="method" name="myMethod" visibility="public" complexity="3" crap="3" count="2"/>
111+
<metrics loc="11"
112+
ncloc="11"
113+
classes="0"
114+
methods="0"
115+
coveredmethods="0"
116+
conditionals="0"
117+
coveredconditionals="0"
118+
statements="100"
119+
coveredstatements="90"
120+
elements="0"
121+
coveredelements="0"/>
122+
</file>
123+
124+
<!-- coverage 90% and ignore uncovered methods -->
125+
<file name="/home/workspace/test/case/ignored-uncovered-method.php">
126+
<line num="16" type="method" name="__construct" visibility="public" complexity="1" crap="2" count="0"/>
127+
<metrics loc="11"
128+
ncloc="11"
129+
classes="0"
130+
methods="0"
131+
coveredmethods="0"
132+
conditionals="0"
133+
coveredconditionals="0"
134+
statements="100"
135+
coveredstatements="90"
136+
elements="0"
137+
coveredelements="0"/>
138+
</file>
139+
106140
</project>
107141
</coverage>

tests/Functional/Command/InspectCommand/Data/phpfci.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88
<file path="test/case/below-threshold.php" min="55"/>
99
<file path="test/case/unnecessary-custom-rule.php" min="75"/>
1010
</custom-coverage>
11+
<ignore-uncovered-methods>
12+
<file path="test/case/ignored-uncovered-method.php"/>
13+
</ignore-uncovered-methods>
1114
</phpfci>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DigitalRevolution\CodeCoverageInspection\Tests\Unit\Lib\IO;
5+
6+
use DigitalRevolution\CodeCoverageInspection\Lib\IO\IgnoreUncoveredMethodFileFactory;
7+
use DOMDocument;
8+
use DOMException;
9+
use PHPUnit\Framework\TestCase;
10+
use RuntimeException;
11+
12+
/**
13+
* @coversDefaultClass \DigitalRevolution\CodeCoverageInspection\Lib\IO\IgnoreUncoveredMethodFileFactory
14+
*/
15+
class IgnoreUncoveredMethodFileFactoryTest extends TestCase
16+
{
17+
/**
18+
* @covers ::createFromNode
19+
* @throws DOMException
20+
*/
21+
public function testCreateFromNodeInvalidNodeThrowsException(): void
22+
{
23+
$doc = new DOMDocument();
24+
$node = $doc->createElement('foobar');
25+
26+
$this->expectException(RuntimeException::class);
27+
$this->expectExceptionMessage('Invalid node type: foobar');
28+
IgnoreUncoveredMethodFileFactory::createFromNode($node);
29+
}
30+
31+
/**
32+
* @covers ::createFromNode
33+
* @throws DOMException
34+
*/
35+
public function testCreateFromNode(): void
36+
{
37+
$doc = new DOMDocument();
38+
39+
$pathAttr = $doc->createAttribute('path');
40+
$pathAttr->value = 'path/to/file';
41+
42+
$node = $doc->createElement('file');
43+
$node->appendChild($pathAttr);
44+
45+
$config = IgnoreUncoveredMethodFileFactory::createFromNode($node);
46+
static::assertSame('path/to/file', $config->getFilepath());
47+
}
48+
}

tests/Unit/Lib/IO/InspectionConfigFactoryTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,27 @@ public function testFromDOMDocumentWithUncoveredMethodsForcedDisallowed(): void
8181
static::assertFalse($config->isUncoveredAllowed());
8282
}
8383

84+
/**
85+
* @covers ::fromDOMDocument
86+
* @covers ::getInspectionConfig
87+
*/
88+
public function testFromDOMDocumentWithIgnoreUncoveredMethods(): void
89+
{
90+
$xml = '<?xml version="1.0" encoding="UTF-8"?>
91+
<phpfci min-coverage="85">
92+
<ignore-uncovered-methods>
93+
<file path="a/b/c"/>
94+
</ignore-uncovered-methods>
95+
</phpfci>
96+
';
97+
98+
$dom = new DOMDocument();
99+
$dom->loadXML($xml);
100+
101+
$config = InspectionConfigFactory::fromDOMDocument('/tmp/test', $dom);
102+
static::assertTrue($config->hasIgnoreUncoveredMethodFile('a/b/c'));
103+
}
104+
84105
/**
85106
* @covers ::fromDOMDocument
86107
* @covers ::getInspectionConfig

tests/Unit/Lib/Metrics/Inspection/UncoveredMethodsInspectionTest.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace DigitalRevolution\CodeCoverageInspection\Tests\Unit\Lib\Metrics\Inspection;
55

66
use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection\UncoveredMethodsInspection;
7+
use DigitalRevolution\CodeCoverageInspection\Model\Config\IgnoreUncoveredMethodFile;
78
use DigitalRevolution\CodeCoverageInspection\Model\Config\PathInspectionConfig;
89
use DigitalRevolution\CodeCoverageInspection\Model\Config\InspectionConfig;
910
use DigitalRevolution\CodeCoverageInspection\Model\Metric\Failure;
@@ -17,12 +18,13 @@
1718
*/
1819
class UncoveredMethodsInspectionTest extends TestCase
1920
{
21+
private InspectionConfig $config;
2022
private UncoveredMethodsInspection $inspection;
2123

2224
protected function setUp(): void
2325
{
24-
$config = new InspectionConfig('/tmp/', 80);
25-
$this->inspection = new UncoveredMethodsInspection($config);
26+
$this->config = new InspectionConfig('/tmp/', 80);
27+
$this->inspection = new UncoveredMethodsInspection($this->config);
2628
}
2729

2830
/**
@@ -36,7 +38,6 @@ public function testInspectCustomCoverageShouldPass(): void
3638
static::assertNull($this->inspection->inspect($fileConfig, $metric));
3739
}
3840

39-
4041
/**
4142
* @covers ::inspect
4243
*/
@@ -60,4 +61,15 @@ public function testInspectUncoveredMethodsShouldFail(): void
6061
static::assertSame(80, $failure->getMinimumCoverage());
6162
static::assertSame(200, $failure->getLineNumber());
6263
}
64+
65+
/**
66+
* @covers ::inspect
67+
*/
68+
public function testInspectIgnoredUncoveredMethodShouldPass(): void
69+
{
70+
$metric = new FileMetric('/tmp/b', 20, [new MethodMetric('foobar', 200, 0)]);
71+
$this->config->addIgnoreUncoveredMethodFile(new IgnoreUncoveredMethodFile('/tmp/b'));
72+
73+
static::assertNull($this->inspection->inspect(null, $metric));
74+
}
6375
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DigitalRevolution\CodeCoverageInspection\Tests\Unit\Model\Config;
5+
6+
use DigitalRevolution\CodeCoverageInspection\Model\Config\IgnoreUncoveredMethodFile;
7+
use PHPUnit\Framework\TestCase;
8+
9+
/**
10+
* @coversDefaultClass \DigitalRevolution\CodeCoverageInspection\Model\Config\IgnoreUncoveredMethodFile
11+
* @covers ::__construct
12+
*/
13+
class IgnoreUncoveredMethodFileTest extends TestCase
14+
{
15+
/**
16+
* @covers ::getFilepath
17+
*/
18+
public function testGetFilepath(): void
19+
{
20+
$file = new IgnoreUncoveredMethodFile('/test/file\\path.php');
21+
static::assertSame('/test/file/path.php', $file->getFilepath());
22+
}
23+
}

0 commit comments

Comments
 (0)