diff --git a/src/Mutant/Calculator.php b/src/Mutant/Calculator.php index ebd968e2f..235f8a69d 100644 --- a/src/Mutant/Calculator.php +++ b/src/Mutant/Calculator.php @@ -61,18 +61,25 @@ final class Calculator */ private $coveredMutationScoreIndicator; + /** + * @var bool + */ + private $treatTimeoutsAsEscapes = false; + public function __construct( int $killedCount, int $errorCount, int $timedOutCount, int $notTestedCount, - int $totalCount + int $totalCount, + bool $treatTimeoutsAsEscapes = false ) { $this->killedCount = $killedCount; $this->errorCount = $errorCount; $this->timedOutCount = $timedOutCount; $this->notTestedCount = $notTestedCount; $this->totalCount = $totalCount; + $this->treatTimeoutsAsEscapes = $treatTimeoutsAsEscapes; } public static function fromMetrics(MetricsCalculator $calculator): self @@ -82,7 +89,8 @@ public static function fromMetrics(MetricsCalculator $calculator): self $calculator->getErrorCount(), $calculator->getTimedOutCount(), $calculator->getNotTestedCount(), - $calculator->getTotalMutantsCount() + $calculator->getTotalMutantsCount(), + $calculator->getTreatTimeoutsAsEscapes() ); } @@ -96,7 +104,11 @@ public function getMutationScoreIndicator(): float } $score = 0.; - $coveredTotal = $this->killedCount + $this->timedOutCount + $this->errorCount; + $coveredTotal = $this->killedCount + $this->errorCount; + + if (!$this->treatTimeoutsAsEscapes) { + $coveredTotal += $this->timedOutCount; + } $totalCount = $this->totalCount; if ($totalCount !== 0) { @@ -137,7 +149,11 @@ public function getCoveredCodeMutationScoreIndicator(): float $score = 0.; $testedTotal = $this->totalCount - $this->notTestedCount; - $coveredTotal = $this->killedCount + $this->timedOutCount + $this->errorCount; + $coveredTotal = $this->killedCount + $this->errorCount; + + if (!$this->treatTimeoutsAsEscapes) { + $coveredTotal += $this->timedOutCount; + } if ($testedTotal !== 0) { $score = 100 * $coveredTotal / $testedTotal; diff --git a/src/Mutant/MetricsCalculator.php b/src/Mutant/MetricsCalculator.php index 69f35145c..d8fb864ff 100644 --- a/src/Mutant/MetricsCalculator.php +++ b/src/Mutant/MetricsCalculator.php @@ -85,7 +85,12 @@ class MetricsCalculator */ private $calculator; - public function __construct() + /** + * @var bool + */ + private $treatTimeoutsAsEscapes = false; + + public function __construct(bool $treatTimeoutsAsEscapes = false) { $this->killedExecutionResults = new SortableMutantExecutionResults(); $this->errorExecutionResults = new SortableMutantExecutionResults(); @@ -93,6 +98,7 @@ public function __construct() $this->timedOutExecutionResults = new SortableMutantExecutionResults(); $this->notCoveredExecutionResults = new SortableMutantExecutionResults(); $this->allExecutionResults = new SortableMutantExecutionResults(); + $this->treatTimeoutsAsEscapes = $treatTimeoutsAsEscapes; } public function collect(MutantExecutionResult ...$executionResults): void @@ -243,6 +249,14 @@ public function getCoveredCodeMutationScoreIndicator(): float return $this->getCalculator()->getCoveredCodeMutationScoreIndicator(); } + /** + * Are mutation timeouts treated as escapes? + */ + public function getTreatTimeoutsAsEscapes(): bool + { + return $this->treatTimeoutsAsEscapes; + } + private function getCalculator(): Calculator { return $this->calculator ?? Calculator::fromMetrics($this); diff --git a/tests/phpunit/Logger/CreateMetricsCalculator.php b/tests/phpunit/Logger/CreateMetricsCalculator.php index 5126fc676..a2d0dc946 100644 --- a/tests/phpunit/Logger/CreateMetricsCalculator.php +++ b/tests/phpunit/Logger/CreateMetricsCalculator.php @@ -46,9 +46,9 @@ trait CreateMetricsCalculator { - private function createCompleteMetricsCalculator(): MetricsCalculator + private function createCompleteMetricsCalculator(bool $timeoutAsEscape = false): MetricsCalculator { - $calculator = new MetricsCalculator(); + $calculator = new MetricsCalculator($timeoutAsEscape); $calculator->collect( $this->createMutantExecutionResult( diff --git a/tests/phpunit/Mutant/CalculatorTest.php b/tests/phpunit/Mutant/CalculatorTest.php index 25293e2c9..0bdab8f27 100644 --- a/tests/phpunit/Mutant/CalculatorTest.php +++ b/tests/phpunit/Mutant/CalculatorTest.php @@ -83,6 +83,44 @@ public function test_it_can_calculate_the_scores( $this->assertSame($expectedCoveredMsi, $calculator->getCoveredCodeMutationScoreIndicator()); } + /** + * @dataProvider metricsWithTimeoutProvider + */ + public function test_it_can_calculate_the_scores_while_counting_timeouts_as_escapes( + int $killedCount, + int $errorCount, + int $escapedCount, + int $timedOutCount, + int $notTestedCount, + float $expectedMsi, + float $expectedCoverageRate, + float $expectedCoveredMsi + ): void { + $calculator = new Calculator( + $killedCount, + $errorCount, + $timedOutCount, + $notTestedCount, + array_sum([ + $killedCount, + $errorCount, + $escapedCount, + $timedOutCount, + $notTestedCount, + ]), + true + ); + + $this->assertSame($expectedMsi, $calculator->getMutationScoreIndicator()); + $this->assertSame($expectedCoverageRate, $calculator->getCoverageRate()); + $this->assertSame($expectedCoveredMsi, $calculator->getCoveredCodeMutationScoreIndicator()); + + // The calls are idempotent + $this->assertSame($expectedMsi, $calculator->getMutationScoreIndicator()); + $this->assertSame($expectedCoverageRate, $calculator->getCoverageRate()); + $this->assertSame($expectedCoveredMsi, $calculator->getCoveredCodeMutationScoreIndicator()); + } + /** * @dataProvider metricsCalculatorProvider */ @@ -99,6 +137,22 @@ public function test_it_can_be_created_from_a_metrics_calculator( $this->assertSame($expectedCoveredMsi, $calculator->getCoveredCodeMutationScoreIndicator()); } + /** + * @dataProvider metricsCalculatorWithTimeoutProvider + */ + public function test_it_can_be_created_from_a_metrics_calculator_while_counting_timeouts_as_escapes( + MetricsCalculator $metricsCalculator, + float $expectedMsi, + float $expectedCoverageRate, + float $expectedCoveredMsi + ): void { + $calculator = Calculator::fromMetrics($metricsCalculator); + + $this->assertSame($expectedMsi, $calculator->getMutationScoreIndicator()); + $this->assertSame($expectedCoverageRate, $calculator->getCoverageRate()); + $this->assertSame($expectedCoveredMsi, $calculator->getCoveredCodeMutationScoreIndicator()); + } + public function metricsProvider(): Generator { yield 'empty' => [ @@ -146,6 +200,53 @@ public function metricsProvider(): Generator ]; } + public function metricsWithTimeoutProvider(): Generator + { + yield 'empty' => [ + 0, + 0, + 0, + 0, + 0, + 0., + 0., + 0., + ]; + + yield 'int scores' => [ + 1, + 0, + 9, + 0, + 0, + 10., + 100.0, + 10.0, + ]; + + yield 'nominal' => [ + 7, + 2, + 2, + 2, + 1, + 64.285714285714292, + 92.85714285714286, + 69.230769230769226, + ]; + + yield 'nominal no non-tested' => [ + 7, + 2, + 2, + 2, + 0, + 69.230769230769226, + 100, + 69.230769230769226, + ]; + } + public function metricsCalculatorProvider(): Generator { yield 'empty' => [ @@ -162,4 +263,21 @@ public function metricsCalculatorProvider(): Generator 75.0, ]; } + + public function metricsCalculatorWithTimeoutProvider(): Generator + { + yield 'empty' => [ + new MetricsCalculator(true), + 0., + 0., + 0., + ]; + + yield 'nominal' => [ + $this->createCompleteMetricsCalculator(true), + 40.0, + 80.0, + 50.0, + ]; + } }