Skip to content

Commit

Permalink
Add notice to console output if actual msi is higher than required msi (
Browse files Browse the repository at this point in the history
  • Loading branch information
theofidry committed Dec 5, 2019
1 parent 8a3976c commit 48dcbd2
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 1 deletion.
8 changes: 8 additions & 0 deletions src/Command/InfectionCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,14 @@ private function checkMetrics(): bool
return false;
}

if ($constraintChecker->isActualOverRequired()) {
$this->consoleOutput->logMinMsiCanGetIncreasedNotice(
$this->container['metrics'],
$constraintChecker->getMinRequiredValue(),
$constraintChecker->getActualOverRequiredType()
);
}

$this->eventDispatcher->dispatch(new ApplicationExecutionFinished());

return true;
Expand Down
32 changes: 32 additions & 0 deletions src/Console/ConsoleOutput.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

namespace Infection\Console;

use Infection\Exception\InvalidTypeException;
use Infection\Mutant\Exception\MsiCalculationException;
use Infection\Mutant\MetricsCalculator;
use Infection\Process\Runner\TestRunConstraintChecker;
Expand All @@ -47,6 +48,7 @@ final class ConsoleOutput
{
private const RUNNING_WITH_DEBUGGER_NOTE = 'You are running Infection with %s enabled.';
private const CI_FLAG_ERROR = 'The minimum required %s percentage should be %s%%, but actual is %s%%. Improve your tests!';
private const MIN_MSI_CAN_GET_INCREASED_NOTICE = 'The %s is %s%% percent points over the required %s. Consider increasing the required %s percentage the next time you run infection.';

/**
* @var SymfonyStyle
Expand Down Expand Up @@ -87,6 +89,36 @@ public function logBadMsiErrorMessage(MetricsCalculator $metricsCalculator, floa
);
}

/**
* @throws InvalidTypeException
*/
public function logMinMsiCanGetIncreasedNotice(MetricsCalculator $metricsCalculator, float $minMsi, string $type): void
{
if ($type !== TestRunConstraintChecker::MSI_OVER_MIN_MSI && $type !== TestRunConstraintChecker::COVERED_MSI_OVER_MIN_MSI) {
throw InvalidTypeException::create($type);
}

if ($type === TestRunConstraintChecker::MSI_OVER_MIN_MSI) {
$typeString = 'MSI';
$msi = $metricsCalculator->getMutationScoreIndicator();
} else {
$typeString = 'Covered Code MSI';
$msi = $metricsCalculator->getCoveredCodeMutationScoreIndicator();
}

$msiDifference = $msi - $minMsi;

$this->io->note(
sprintf(
self::MIN_MSI_CAN_GET_INCREASED_NOTICE,
$typeString,
$msiDifference,
$typeString,
$typeString
)
);
}

public function logRunningWithDebugger(string $debugger): void
{
$this->io->writeln(sprintf(self::RUNNING_WITH_DEBUGGER_NOTE, $debugger));
Expand Down
51 changes: 51 additions & 0 deletions src/Exception/InvalidTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\Exception;

use Exception;

/**
* @internal
*/
final class InvalidTypeException extends Exception
{
public static function create(string $type): self
{
return new self(
sprintf('Invalid type "%s" passed.', $type)
);
}
}
55 changes: 54 additions & 1 deletion src/Process/Runner/TestRunConstraintChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ final class TestRunConstraintChecker

public const COVERED_MSI_FAILURE = 'min-covered-msi';

public const MSI_OVER_MIN_MSI = 'msi-over-min-msi';

public const COVERED_MSI_OVER_MIN_MSI = 'covered-msi-over-min-msi';

private const VALUE_OVER_REQUIRED_TOLERANCE = 0.1;

/**
* @var MetricsCalculator
*/
Expand All @@ -71,6 +77,11 @@ final class TestRunConstraintChecker
*/
private $failureType = '';

/**
* @var string
*/
private $actualOverRequiredType = '';

public function __construct(
MetricsCalculator $metricsCalculator,
bool $ignoreMsiWithNoMutations,
Expand Down Expand Up @@ -104,14 +115,38 @@ public function hasTestRunPassedConstraints(): bool
return true;
}

public function isActualOverRequired(): bool
{
if ($this->hasMsiOverRequired()) {
$this->actualOverRequiredType = self::MSI_OVER_MIN_MSI;

return true;
}

if ($this->hasCoveredMsiOverRequired()) {
$this->actualOverRequiredType = self::COVERED_MSI_OVER_MIN_MSI;

return true;
}

return false;
}

public function getErrorType(): string
{
return $this->failureType;
}

public function getActualOverRequiredType(): string
{
return $this->actualOverRequiredType;
}

public function getMinRequiredValue(): float
{
return $this->failureType === self::MSI_FAILURE ? $this->minMsi : $this->minCoveredMsi;
return
($this->failureType === self::MSI_FAILURE || $this->actualOverRequiredType === self::MSI_OVER_MIN_MSI)
? $this->minMsi : $this->minCoveredMsi;
}

private function hasBadMsi(): bool
Expand All @@ -123,4 +158,22 @@ private function hasBadCoveredMsi(): bool
{
return $this->minCoveredMsi && ($this->metricsCalculator->getCoveredCodeMutationScoreIndicator() < $this->minCoveredMsi);
}

private function hasMsiOverRequired(): bool
{
if ($this->minMsi === 0.0) {
return false;
}

return $this->metricsCalculator->getMutationScoreIndicator() > $this->minMsi + self::VALUE_OVER_REQUIRED_TOLERANCE;
}

private function hasCoveredMsiOverRequired(): bool
{
if ($this->minCoveredMsi === 0.0) {
return false;
}

return $this->metricsCalculator->getCoveredCodeMutationScoreIndicator() > $this->minCoveredMsi + self::VALUE_OVER_REQUIRED_TOLERANCE;
}
}
41 changes: 41 additions & 0 deletions tests/phpunit/Console/ConsoleOutputTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use Infection\Console\ConsoleOutput;
use Infection\Mutant\Exception\MsiCalculationException;
use Infection\Mutant\MetricsCalculator;
use Infection\Process\Runner\TestRunConstraintChecker;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Style\SymfonyStyle;

Expand Down Expand Up @@ -123,4 +124,44 @@ public function test_log_not_in_control_of_exit_codes(): void
$consoleOutput = new ConsoleOutput($io);
$consoleOutput->logNotInControlOfExitCodes();
}

public function test_log_min_msi_can_get_increased_notice_for_msi(): void
{
$actualMsi = 10.0;
$minMsi = 5.0;
$msiDifference = $actualMsi - $minMsi;

$io = $this->createMock(SymfonyStyle::class);
$io->expects($this->once())->method('note')
->with(
'The MSI is ' . $msiDifference . '% percent points over the required MSI. ' .
'Consider increasing the required MSI percentage the next time you run infection.');
$metricsCalculator = $this->createMock(MetricsCalculator::class);

$metricsCalculator->expects($this->once())->method('getMutationScoreIndicator')
->willReturn($actualMsi);

$consoleOutput = new ConsoleOutput($io);
$consoleOutput->logMinMsiCanGetIncreasedNotice($metricsCalculator, $minMsi, TestRunConstraintChecker::MSI_OVER_MIN_MSI);
}

public function test_log_min_msi_can_get_increased_notice_for_covered_msi(): void
{
$actualMsi = 10.0;
$minMsi = 5.0;
$msiDifference = $actualMsi - $minMsi;

$io = $this->createMock(SymfonyStyle::class);
$io->expects($this->once())->method('note')
->with(
'The Covered Code MSI is ' . $msiDifference . '% percent points over the required Covered Code MSI. ' .
'Consider increasing the required Covered Code MSI percentage the next time you run infection.');
$metricsCalculator = $this->createMock(MetricsCalculator::class);

$metricsCalculator->expects($this->once())->method('getCoveredCodeMutationScoreIndicator')
->willReturn($actualMsi);

$consoleOutput = new ConsoleOutput($io);
$consoleOutput->logMinMsiCanGetIncreasedNotice($metricsCalculator, $minMsi, TestRunConstraintChecker::COVERED_MSI_OVER_MIN_MSI);
}
}
54 changes: 54 additions & 0 deletions tests/phpunit/Exception/InvalidTypeExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\Tests\Exception;

use Infection\Exception\InvalidTypeException;
use PHPUnit\Framework\TestCase;

final class InvalidTypeExceptionTest extends TestCase
{
public function test_it_has_correct_user_facing_message(): void
{
$type = 'FooType';

$exception = InvalidTypeException::create($type);

$this->assertSame(
'Invalid type "' . $type . '" passed.',
$exception->getMessage()
);
}
}
60 changes: 60 additions & 0 deletions tests/phpunit/Process/Runner/TestRunConstraintCheckerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

namespace Infection\Tests\Process\Runner;

use Generator;
use Infection\Mutant\MetricsCalculator;
use Infection\Process\Runner\TestRunConstraintChecker;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -202,4 +203,63 @@ public function test_run_passes_on_exactly_covered_min_msi(): void

$this->assertTrue($constraintChecker->hasTestRunPassedConstraints());
}

/**
* @dataProvider isMsiOverMinimumMsiProvider
*/
public function test_is_msi_over_minimum_msi_for_msi(?float $actualMsi, float $minMsi, bool $expected): void
{
$metrics = $this->createMock(MetricsCalculator::class);
$metrics->expects($this->once())->method('getMutationScoreIndicator')->willReturn($actualMsi);

$constraintChecker = new TestRunConstraintChecker(
$metrics,
false,
$minMsi,
0.0
);

$this->assertSame($expected, $constraintChecker->isActualOverRequired());
}

/**
* @dataProvider isMsiOverMinimumMsiProvider
*/
public function test_is_msi_over_minimum_msi_for_covered_msi(float $actualCoveredMsi, float $minCoveredMsi, bool $expected): void
{
$metrics = $this->createMock(MetricsCalculator::class);
$metrics->expects($this->once())->method('getCoveredCodeMutationScoreIndicator')->willReturn($actualCoveredMsi);

$constraintChecker = new TestRunConstraintChecker(
$metrics,
false,
0.0,
$minCoveredMsi
);

$this->assertSame($expected, $constraintChecker->isActualOverRequired());
}

public function isMsiOverMinimumMsiProvider(): Generator
{
$minMsi = 10.0;

yield [
'actualMsi' => $minMsi + 1.0,
'minMsi' => $minMsi,
'expected' => true,
];

yield [
'actualMsi' => $minMsi,
'minMsi' => $minMsi,
'expected' => false,
];

yield [
'actualMsi' => $minMsi - 1.0,
'minMsi' => $minMsi,
'expected' => false,
];
}
}

0 comments on commit 48dcbd2

Please sign in to comment.