Skip to content

Commit

Permalink
Merge pull request #7 from robiningelbrecht/add-exit-on-low-coverage-…
Browse files Browse the repository at this point in the history
…per-rule

Fix bug on determining exit status
  • Loading branch information
robiningelbrecht committed Sep 19, 2023
2 parents 0b372dd + efacca2 commit cabfd67
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 73 deletions.
5 changes: 4 additions & 1 deletion src/ConsoleOutput.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ public function __construct(
/**
* @param \RobinIngelbrecht\PHPUnitCoverageTools\MinCoverage\MinCoverageResult[] $results
*/
public function print(array $results, ResultStatus $finalStatus): void
public function print(array $results): void
{
$statusWeights = array_map(fn (MinCoverageResult $result) => $result->getStatus()->getWeight(), $results);
$finalStatus = ResultStatus::fromWeight(max($statusWeights));

$this->output->writeln('');
$tableStyle = new TableStyle();
$tableStyle
Expand Down
22 changes: 2 additions & 20 deletions src/MinCoverage/MinCoverageResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,6 @@ public function exitOnLowCoverage(): bool
return $this->exitOnLowCoverage;
}

public static function fromPatternAndNumbers(
string $pattern,
int $expectedMinCoverage,
float $actualMinCoverage,
int $numberOfTrackedLines,
int $numberOfCoveredLines,
bool $exitOnLowCoverage
): self {
return new self(
pattern: $pattern,
expectedMinCoverage: $expectedMinCoverage,
actualMinCoverage: $actualMinCoverage,
numberOfTrackedLines: $numberOfTrackedLines,
numberOfCoveredLines: $numberOfCoveredLines,
exitOnLowCoverage: $exitOnLowCoverage
);
}

/**
* @param \RobinIngelbrecht\PHPUnitCoverageTools\MinCoverage\CoverageMetric[] $metrics
*
Expand All @@ -86,7 +68,7 @@ public static function mapFromRulesAndMetrics(
$pattern = $minCoverageRule->getPattern();
$minCoverage = $minCoverageRule->getMinCoverage();
if (MinCoverageRule::TOTAL === $minCoverageRule->getPattern() && $metricTotal) {
$results[] = MinCoverageResult::fromPatternAndNumbers(
$results[] = new MinCoverageResult(
pattern: $pattern,
expectedMinCoverage: $minCoverage,
actualMinCoverage: $metricTotal->getTotalPercentageCoverage(),
Expand All @@ -107,7 +89,7 @@ public static function mapFromRulesAndMetrics(
$coveragePercentage += ($metric->getTotalPercentageCoverage() * $weight);
}

$results[] = MinCoverageResult::fromPatternAndNumbers(
$results[] = new MinCoverageResult(
pattern: $pattern,
expectedMinCoverage: $minCoverage,
actualMinCoverage: round($coveragePercentage, 2),
Expand Down
10 changes: 3 additions & 7 deletions src/Subscriber/Application/ApplicationFinishedSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,13 @@ public function notify(Finished $event): void
metrics: $metrics,
metricTotal: $metricTotal,
);
$statusWeights = array_map(fn (MinCoverageResult $result) => $result->getStatus()->getWeight(), $results);
$finalStatus = ResultStatus::fromWeight(max($statusWeights));

$this->consoleOutput->print($results, $finalStatus);
$this->consoleOutput->print($results);

$needsExit = !empty(array_filter(
$results,
fn (MinCoverageResult $minCoverageResult) => $minCoverageResult->exitOnLowCoverage())
fn (MinCoverageResult $minCoverageResult) => $minCoverageResult->exitOnLowCoverage() && ResultStatus::FAILED === $minCoverageResult->getStatus())
);
if (ResultStatus::FAILED === $finalStatus
&& $needsExit) {
if ($needsExit) {
$this->exitter->exit();
}
}
Expand Down
178 changes: 133 additions & 45 deletions tests/Subscriber/Application/ApplicationFinishedSubscriberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ class ApplicationFinishedSubscriberTest extends TestCase
public function testNotifyWithAtLeastOneFailedRule(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->once())
->method('exit');

$spyOutput = new SpyOutput();
$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-with-failed-rule.php'),
Expand Down Expand Up @@ -61,12 +61,18 @@ public function testNotifyWithAtLeastOneFailedRule(): void

public function testNotifyWithAWarning(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-with-warning.php'),
cleanUpCloverXml: false,
exitter: new Exitter(),
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);

Expand All @@ -91,12 +97,18 @@ public function testNotifyWithAWarning(): void

public function testNotifyWhenCoverageIsOk(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-success.php'),
cleanUpCloverXml: false,
exitter: new Exitter(),
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);

Expand All @@ -121,12 +133,18 @@ public function testNotifyWhenCoverageIsOk(): void

public function testNotifyWithOnlyTotal(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-total-only.php'),
cleanUpCloverXml: false,
exitter: new Exitter(),
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);

Expand All @@ -151,12 +169,18 @@ public function testNotifyWithOnlyTotal(): void

public function testNotifyWithoutTotal(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-without-total.php'),
cleanUpCloverXml: false,
exitter: new Exitter(),
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);

Expand All @@ -179,63 +203,51 @@ public function testNotifyWithoutTotal(): void
$this->assertMatchesTextSnapshot($spyOutput);
}

public function testNotifyWithDuplicatePatterns(): void
{
$spyOutput = new SpyOutput();

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Make sure all coverage rule patterns are unique');

new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-with-duplicates.php'),
cleanUpCloverXml: false,
exitter: new Exitter(),
consoleOutput: new ConsoleOutput($spyOutput),
);
}

public function testNotifyWithInvalidRules(): void
public function testNotifyWithRulesThatDoNotExit(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('MinCoverage has to be value between 0 and 100. 203 given');
$exitter
->expects($this->never())
->method('exit');

new ApplicationFinishedSubscriber(
$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-invalid.php'),
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-no-exit.php'),
cleanUpCloverXml: false,
exitter: new Exitter(),
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);
}

public function testNotifyWithInvalidRuleInstances(): void
{
$spyOutput = new SpyOutput();

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Make sure all coverage rules are of instance RobinIngelbrecht\PHPUnitCoverageTools\MinCoverage\MinCoverageRule');
$subscriber->notify(event: new Finished(
new Info(
current: new Snapshot(
time: HRTime::fromSecondsAndNanoseconds(1, 0),
memoryUsage: MemoryUsage::fromBytes(100),
peakMemoryUsage: MemoryUsage::fromBytes(100),
garbageCollectorStatus: new GarbageCollectorStatus(0, 0, 0, 0, null, null, null, null, null, null, null, null)
),
durationSinceStart: Duration::fromSecondsAndNanoseconds(1, 0),
memorySinceStart: MemoryUsage::fromBytes(100),
durationSincePrevious: Duration::fromSecondsAndNanoseconds(1, 0),
memorySincePrevious: MemoryUsage::fromBytes(100),
),
0
));

new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-invalid-rule-instances.php'),
cleanUpCloverXml: false,
exitter: new Exitter(),
consoleOutput: new ConsoleOutput($spyOutput),
);
$this->assertMatchesTextSnapshot($spyOutput);
}

public function testDivideByZero(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$spyOutput = new SpyOutput();
$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover-test-divide-by-zero.xml',
minCoverageRules: MinCoverageRules::fromInt(100, true),
Expand Down Expand Up @@ -265,12 +277,18 @@ public function testDivideByZero(): void

public function testNotifyWithNonExistingCloverFile(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover-wrong.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-success.php'),
cleanUpCloverXml: false,
exitter: new Exitter(),
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);

Expand All @@ -295,12 +313,18 @@ public function testNotifyWithNonExistingCloverFile(): void

public function testNotifyWithInvalidCloverFile(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover-invalid.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-success.php'),
cleanUpCloverXml: false,
exitter: new Exitter(),
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);

Expand All @@ -327,13 +351,14 @@ public function testNotifyWithInvalidCloverFile(): void
public function testNotifyWithCleanUpCloverFile(): void
{
copy(dirname(__DIR__, 2).'/clover.xml', dirname(__DIR__, 2).'/clover-to-delete.xml');

$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->once())
->method('exit');

$spyOutput = new SpyOutput();
$subscriber = new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover-to-delete.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-with-failed-rule.php'),
Expand Down Expand Up @@ -361,6 +386,69 @@ public function testNotifyWithCleanUpCloverFile(): void
$this->assertFileDoesNotExist(dirname(__DIR__, 2).'/clover-to-delete.xml');
}

public function testNotifyWithDuplicatePatterns(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Make sure all coverage rule patterns are unique');

new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-with-duplicates.php'),
cleanUpCloverXml: false,
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);
}

public function testNotifyWithInvalidRules(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('MinCoverage has to be value between 0 and 100. 203 given');

new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-rules-invalid.php'),
cleanUpCloverXml: false,
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);
}

public function testNotifyWithInvalidRuleInstances(): void
{
$exitter = $this->createMock(Exitter::class);
$spyOutput = new SpyOutput();

$exitter
->expects($this->never())
->method('exit');

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Make sure all coverage rules are of instance RobinIngelbrecht\PHPUnitCoverageTools\MinCoverage\MinCoverageRule');

new ApplicationFinishedSubscriber(
relativePathToCloverXml: 'tests/clover.xml',
minCoverageRules: MinCoverageRules::fromConfigFile('tests/Subscriber/Application/min-coverage-invalid-rule-instances.php'),
cleanUpCloverXml: false,
exitter: $exitter,
consoleOutput: new ConsoleOutput($spyOutput),
);
}

public function testFromConfigurationAndParameters(): void
{
$this->assertEquals(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

+------------------------------------------------------------------<fg=black;bg=yellow;options=bold> Code coverage results </>---+------------+----------+-------------------------+------------+
|<bold> Pattern </bold>|<bold> Expected </bold>|<bold> Actual </bold>|<bold> </bold>|<bold> Exit on<fg=default;bg=default></> </bold>|
|<bold> </bold>|<bold> </bold>|<bold> </bold>|<bold> </bold>|<bold> fail? </bold>|
+--------------------------------------------------------------------------------------------+------------+----------+-------------------------+------------+
|<fg=default;bg=default> RobinIngelbrecht\PHPUnitCoverageTools\CoverageMetrics </>| 100% | <failed>0%</failed> |<fg=default;bg=default> <bold>0</bold> of 48 lines covered </>| No |
|<fg=default;bg=default> RobinIngelbrecht\PHPUnitCoverageTools\Subscriber\Application\ApplicationFinishedSubscriber </>| 100% | <failed>40%</failed> |<fg=default;bg=default> <bold>18</bold> of 45 lines covered </>| No |
|<fg=default;bg=default> RobinIngelbrecht\NonExistingNameSpace </>| 100% | <warning>0%</warning> |<fg=default;bg=default> No lines to track...? </>| No |
|<fg=default;bg=default> Total </>| 20% | <success>28.57%</success> |<fg=default;bg=default> <bold>30</bold> of 105 lines covered </>| No |
+--------------------------------------------------------------------------------------------+------------+----------+-------------------------+------------+
|<failed> Not all minimum code coverage rules passed, please try again... :) </failed>|
+--------------------------------------------------------------------------------------------+------------+----------+-------------------------+------------+
Loading

0 comments on commit cabfd67

Please sign in to comment.