Skip to content

Commit

Permalink
485: Include output from Psalm in report when mutant killed by static…
Browse files Browse the repository at this point in the history
… analysis
  • Loading branch information
bdsl committed Dec 2, 2023
1 parent 2d6204d commit f0265a0
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,36 @@
namespace Roave\InfectionStaticAnalysis\Psalm;

use Infection\Mutant\Mutant;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Internal\Analyzer\ProjectAnalyzer;

use function array_key_exists;
use function array_map;
use function count;
use function implode;

/**
* @internal
* @psalm-suppress InternalProperty - we use Psalm's internal IssueData class here. Afaik the only other way to
* display details of the issues would be to use one of the subclasses of \Psalm\Report, but I think none are exactly
* what we want. Probably we can accept the risk of Psalm's internals changing and breaking this.
*
* @final not explicitly final because we don't yet have a uniform API for this type of analysis
*/
class RunStaticAnalysisAgainstMutant
{
private bool $alreadyVisitedStubs = false;

/** @var IssueData[] */
private array $psalmIssuesFromLastMutant = [];

public function __construct(private ProjectAnalyzer $projectAnalyzer)
{
}

public function isMutantStillValidAccordingToStaticAnalysis(Mutant $mutant): bool
{
$this->psalmIssuesFromLastMutant = [];

$path = $mutant->getFilePath();
$paths = [$mutant->getFilePath()];
$codebase = $this->projectAnalyzer->getCodebase();
Expand All @@ -48,13 +58,18 @@ public function isMutantStillValidAccordingToStaticAnalysis(Mutant $mutant): boo
$codebase->reloadFiles($this->projectAnalyzer, $paths);
$codebase->analyzer->analyzeFiles($this->projectAnalyzer, count($paths), false);

$mutationValid = ! array_key_exists(
$path,
$codebase->file_reference_provider->getExistingIssues(),
);
$this->psalmIssuesFromLastMutant = $codebase->file_reference_provider->getExistingIssues()[$path] ?? [];

$codebase->invalidateInformationForFile($path);

return $mutationValid;
return $this->psalmIssuesFromLastMutant === [];
}

public function formatLastIssues(): string
{
return implode(
"\n\n",
array_map(static fn (IssueData $issueData) => ($issueData->type . ': ' . $issueData->message . "\n{$issueData->file_name}:{$issueData->line_from}"), $this->psalmIssuesFromLastMutant),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ public function createFromProcess(MutantProcess $mutantProcess): MutantExecution
assert(is_int($originalEndFilePosition));

return new MutantExecutionResult(
$result->getProcessCommandLine(),
$result->getProcessOutput(),
'Static Analysis',
$this->runStaticAnalysis->formatLastIssues(),
DetectionStatus::KILLED, // Mutant was squished by static analysis
later(static fn () => yield $result->getMutantDiff()),
$result->getMutantHash(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ function add(int $a, int $b): int {
}
PHP,
)));

self::assertStringContainsString(
"InvalidReturnStatement: The inferred type 'int<min, max>' does not match the declared return type 'int<1, max>' for add",
$this->runStaticAnalysis->formatLastIssues(),
);

self::assertStringContainsString(
"InvalidReturnType: The declared return type 'int<1, max>' for add is incorrect, got 'int<min, max>'",
$this->runStaticAnalysis->formatLastIssues(),
);
}

public function testWillConsiderMutantReferencingProjectFilesAsValid(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,13 @@ public function testWillKillMutantsThatEscapedAndFailedStaticAnalysis(): void
->method('isMutantStillValidAccordingToStaticAnalysis')
->willReturn(false);

$this->staticAnalysis->expects(self::any())
->method('formatLastIssues')
->willReturn('formatted Psalm issues');

$nextFactoryResult = new MutantExecutionResult(
'echo hi',
'output',
'formatted Psalm issues',
DetectionStatus::ESCAPED,
now('diff'),
'a-hash',
Expand Down Expand Up @@ -136,7 +140,7 @@ public function testWillKillMutantsThatEscapedAndFailedStaticAnalysis(): void
self::assertEquals($nextFactoryResult->getMutantDiff(), $result->getMutantDiff());
self::assertEquals($nextFactoryResult->getMutantHash(), $result->getMutantHash());
self::assertEquals($nextFactoryResult->getProcessOutput(), $result->getProcessOutput());
self::assertEquals($nextFactoryResult->getProcessCommandLine(), $result->getProcessCommandLine());
self::assertEquals('Static Analysis', $result->getProcessCommandLine());
self::assertSame(DetectionStatus::KILLED, $result->getDetectionStatus());

$reflectionOriginalStartFileLocation = new ReflectionProperty(MutantExecutionResult::class, 'originalStartFilePosition');
Expand Down

0 comments on commit f0265a0

Please sign in to comment.