Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the Stryker HTML report 2 #1625

Merged
merged 8 commits into from Jan 8, 2022
6 changes: 6 additions & 0 deletions devTools/phpstan-src.neon
Expand Up @@ -37,6 +37,12 @@ parameters:
-
path: '../src/Differ/DiffChangedLinesParser.php'
message: '#Method Infection\\Differ\\DiffChangedLinesParser::parse\(\) should return array\<string, array\<int, Infection\\Differ\\ChangedLinesRange\>\>#'
-
path: '../src/Logger/Html/StrykerHtmlReportBuilder.php'
message: '#return type has no value type specified in iterable type array#'
-
path: '../src/Logger/Html/StrykerHtmlReportBuilder.php'
message: '#return type with generic class ArrayObject does not specify its types\: TKey, TValue#'
level: 8
paths:
- ../src
Expand Down
353 changes: 353 additions & 0 deletions resources/mutation-testing-report-schema.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion resources/schema.json
Expand Up @@ -52,12 +52,16 @@
},
"summary": {
"type": "string",
"definition": "Summary log file, which display the amount of mutants per category, (Killed, Errored, Escaped, Timed Out & Not Covered). More intended for internal purposes."
"definition": "Summary log file, which displays the amount of mutants per category, (Killed, Errored, Escaped, Timed Out & Not Covered). More intended for internal purposes."
},
"json": {
"type": "string",
"definition": "JSON log file, which contains information about all mutants, as well as the source and mutated code and test framework output. Useful for using on CI servers to be able to programmatically analyze it."
},
"html": {
"type": "string",
"definition": "HTML report, which displays MSI values as well as mutated files with Killed and Escaped mutants with diffs for them. Human readable report, similar to PHPUnit HTML report."
},
"debug": {
"type": "string",
"description": "Debug log file, which displays what mutations were found on what line, per category. More intended for internal purposes."
Expand Down
10 changes: 10 additions & 0 deletions src/Command/RunCommand.php
Expand Up @@ -118,6 +118,8 @@ final class RunCommand extends BaseCommand
/** @var string */
private const OPTION_LOGGER_GITHUB = 'logger-github';

private const OPTION_LOGGER_HTML = 'logger-html';

private const OPTION_USE_NOOP_MUTATORS = 'noop';

private const OPTION_EXECUTE_ONLY_COVERING_TEST_CASES = 'only-covering-test-cases';
Expand Down Expand Up @@ -264,6 +266,12 @@ protected function configure(): void
InputOption::VALUE_NONE,
'Log escaped Mutants as GitHub Annotations.',
)
->addOption(
self::OPTION_LOGGER_HTML,
null,
InputOption::VALUE_REQUIRED,
'Path to HTML report file, similar to PHPUnit HTML report.',
)
->addOption(
self::OPTION_USE_NOOP_MUTATORS,
null,
Expand Down Expand Up @@ -388,6 +396,7 @@ private function createContainer(IO $io, LoggerInterface $logger): Container
$testFramework = trim((string) $input->getOption(self::OPTION_TEST_FRAMEWORK));
$testFrameworkExtraOptions = trim((string) $input->getOption(self::OPTION_TEST_FRAMEWORK_OPTIONS));
$initialTestsPhpOptions = trim((string) $input->getOption(self::OPTION_INITIAL_TESTS_PHP_OPTIONS));
$htmlFileLogPath = trim((string) $input->getOption(self::OPTION_LOGGER_HTML));

/** @var string|null $minMsi */
$minMsi = $input->getOption(self::OPTION_MIN_MSI);
Expand Down Expand Up @@ -473,6 +482,7 @@ private function createContainer(IO $io, LoggerInterface $logger): Container
$isForGitDiffLines,
$gitDiffBase,
(bool) $input->getOption(self::OPTION_LOGGER_GITHUB),
$htmlFileLogPath === '' ? Container::DEFAULT_HTML_LOGGER_PATH : $htmlFileLogPath,
(bool) $input->getOption(self::OPTION_USE_NOOP_MUTATORS),
(bool) $input->getOption(self::OPTION_EXECUTE_ONLY_COVERING_TEST_CASES)
);
Expand Down
9 changes: 7 additions & 2 deletions src/Configuration/ConfigurationFactory.php
Expand Up @@ -119,6 +119,7 @@ public function create(
bool $isForGitDiffLines,
?string $gitDiffBase,
bool $useGitHubLogger,
?string $htmlLogFilePath,
bool $useNoopMutators,
bool $executeOnlyCoveringTestCases
): Configuration {
Expand Down Expand Up @@ -150,7 +151,7 @@ public function create(
),
$this->retrieveFilter($filter, $gitDiffFilter, $isForGitDiffLines, $gitDiffBase),
$schema->getSource()->getExcludes(),
$this->retrieveLogs($schema->getLogs(), $useGitHubLogger),
$this->retrieveLogs($schema->getLogs(), $useGitHubLogger, $htmlLogFilePath),
$logVerbosity,
$namespacedTmpDir,
$this->retrievePhpUnit($schema, $configDir),
Expand Down Expand Up @@ -315,12 +316,16 @@ private function retrieveFilter(string $filter, ?string $gitDiffFilter, bool $is
return $this->gitDiffFileProvider->provide($gitDiffFilter, $baseBranch);
}

private function retrieveLogs(Logs $logs, bool $useGitHubLogger): Logs
private function retrieveLogs(Logs $logs, bool $useGitHubLogger, ?string $htmlLogFilePath): Logs
{
if ($useGitHubLogger) {
$logs->setUseGitHubAnnotationsLogger($useGitHubLogger);
}

if ($htmlLogFilePath !== null) {
$logs->setHtmlLogFilePath($htmlLogFilePath);
}

return $logs;
}
}
14 changes: 14 additions & 0 deletions src/Configuration/Entry/Logs.php
Expand Up @@ -42,6 +42,7 @@
class Logs
{
private ?string $textLogFilePath;
private ?string $htmlLogFilePath;
private ?string $summaryLogFilePath;
private ?string $jsonLogFilePath;
private ?string $debugLogFilePath;
Expand All @@ -51,6 +52,7 @@ class Logs

public function __construct(
?string $textLogFilePath,
?string $htmlLogFilePath,
?string $summaryLogFilePath,
?string $jsonLogFilePath,
?string $debugLogFilePath,
Expand All @@ -59,6 +61,7 @@ public function __construct(
?Badge $badge
) {
$this->textLogFilePath = $textLogFilePath;
$this->htmlLogFilePath = $htmlLogFilePath;
$this->summaryLogFilePath = $summaryLogFilePath;
$this->jsonLogFilePath = $jsonLogFilePath;
$this->debugLogFilePath = $debugLogFilePath;
Expand All @@ -75,6 +78,7 @@ public static function createEmpty(): self
null,
null,
null,
null,
false,
null
);
Expand All @@ -85,6 +89,16 @@ public function getTextLogFilePath(): ?string
return $this->textLogFilePath;
}

public function getHtmlLogFilePath(): ?string
{
return $this->htmlLogFilePath;
}

public function setHtmlLogFilePath(string $htmlLogFilePath): void
{
$this->htmlLogFilePath = $htmlLogFilePath;
}

public function getSummaryLogFilePath(): ?string
{
return $this->summaryLogFilePath;
Expand Down
1 change: 1 addition & 0 deletions src/Configuration/Schema/SchemaConfigurationFactory.php
Expand Up @@ -82,6 +82,7 @@ private static function createLogs(stdClass $logs): Logs
{
return new Logs(
self::normalizeString($logs->text ?? null),
self::normalizeString($logs->html ?? null),
self::normalizeString($logs->summary ?? null),
self::normalizeString($logs->json ?? null),
self::normalizeString($logs->debug ?? null),
Expand Down
17 changes: 16 additions & 1 deletion src/Container.php
Expand Up @@ -82,6 +82,7 @@
use Infection\Logger\FederatedLogger;
use Infection\Logger\FileLoggerFactory;
use Infection\Logger\GitHub\GitDiffFileProvider;
use Infection\Logger\Html\StrykerHtmlReportBuilder;
use Infection\Logger\MutationTestingResultsLogger;
use Infection\Metrics\FilteringResultsCollectorFactory;
use Infection\Metrics\MetricsCalculator;
Expand Down Expand Up @@ -169,6 +170,7 @@ final class Container
public const DEFAULT_GIT_DIFF_LINES = false;
public const DEFAULT_GIT_DIFF_BASE = null;
public const DEFAULT_USE_GITHUB_LOGGER = false;
public const DEFAULT_HTML_LOGGER_PATH = null;
public const DEFAULT_USE_NOOP_MUTATORS = false;
public const DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES = false;
public const DEFAULT_NO_PROGRESS = false;
Expand Down Expand Up @@ -564,7 +566,8 @@ public static function create(): self
$config->getLogVerbosity(),
$config->isDebugEnabled(),
$config->mutateOnlyCoveredCode(),
$container->getLogger()
$container->getLogger(),
$container->getStrykerHtmlReportBuilder()
);
},
MutationTestingResultsLogger::class => static function (self $container): MutationTestingResultsLogger {
Expand All @@ -577,6 +580,9 @@ public static function create(): self
),
]));
},
StrykerHtmlReportBuilder::class => static function (self $container): StrykerHtmlReportBuilder {
return new StrykerHtmlReportBuilder($container->getMetricsCalculator(), $container->getResultsCollector());
},
TargetDetectionStatusesProvider::class => static function (self $container): TargetDetectionStatusesProvider {
$config = $container->getConfiguration();

Expand Down Expand Up @@ -705,6 +711,7 @@ public static function create(): self
self::DEFAULT_GIT_DIFF_LINES,
self::DEFAULT_GIT_DIFF_BASE,
self::DEFAULT_USE_GITHUB_LOGGER,
self::DEFAULT_HTML_LOGGER_PATH,
self::DEFAULT_USE_NOOP_MUTATORS,
self::DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES
);
Expand Down Expand Up @@ -738,6 +745,7 @@ public function withValues(
bool $isForGitDiffLines,
?string $gitDiffBase,
bool $useGitHubLogger,
?string $htmlLogFilePath,
bool $useNoopMutators,
bool $executeOnlyCoveringTestCases
): self {
Expand Down Expand Up @@ -815,6 +823,7 @@ static function (self $container) use (
$isForGitDiffLines,
$gitDiffBase,
$useGitHubLogger,
$htmlLogFilePath,
$useNoopMutators,
$executeOnlyCoveringTestCases
): Configuration {
Expand Down Expand Up @@ -842,6 +851,7 @@ static function (self $container) use (
$isForGitDiffLines,
$gitDiffBase,
$useGitHubLogger,
$htmlLogFilePath,
$useNoopMutators,
$executeOnlyCoveringTestCases
);
Expand Down Expand Up @@ -1298,6 +1308,11 @@ public function getGitDiffFileProvider(): GitDiffFileProvider
return $this->get(GitDiffFileProvider::class);
}

public function getStrykerHtmlReportBuilder(): StrykerHtmlReportBuilder
{
return $this->get(StrykerHtmlReportBuilder::class);
}

/**
* @param class-string<object> $id
* @param Closure(self): object $value
Expand Down
16 changes: 15 additions & 1 deletion src/Logger/FileLoggerFactory.php
Expand Up @@ -37,6 +37,8 @@

use Infection\Configuration\Entry\Logs;
use Infection\Console\LogVerbosity;
use Infection\Logger\Html\HtmlFileLogger;
use Infection\Logger\Html\StrykerHtmlReportBuilder;
use Infection\Metrics\MetricsCalculator;
use Infection\Metrics\ResultsCollector;
use Psr\Log\LoggerInterface;
Expand All @@ -56,6 +58,7 @@ class FileLoggerFactory
private bool $debugMode;
private bool $onlyCoveredCode;
private LoggerInterface $logger;
private StrykerHtmlReportBuilder $strykerHtmlReportBuilder;

public function __construct(
MetricsCalculator $metricsCalculator,
Expand All @@ -64,7 +67,8 @@ public function __construct(
string $logVerbosity,
bool $debugMode,
bool $onlyCoveredCode,
LoggerInterface $logger
LoggerInterface $logger,
StrykerHtmlReportBuilder $strykerHtmlReportBuilder
) {
$this->metricsCalculator = $metricsCalculator;
$this->resultsCollector = $resultsCollector;
Expand All @@ -73,6 +77,7 @@ public function __construct(
$this->debugMode = $debugMode;
$this->onlyCoveredCode = $onlyCoveredCode;
$this->logger = $logger;
$this->strykerHtmlReportBuilder = $strykerHtmlReportBuilder;
}

public function createFromLogEntries(Logs $logConfig): MutationTestingResultsLogger
Expand Down Expand Up @@ -101,6 +106,8 @@ private function createLineLoggers(Logs $logConfig): iterable

yield $logConfig->getTextLogFilePath() => $this->createTextLogger();

yield $logConfig->getHtmlLogFilePath() => $this->createHtmlLogger();

yield $logConfig->getSummaryLogFilePath() => $this->createSummaryLogger();

yield $logConfig->getJsonLogFilePath() => $this->createJsonLogger();
Expand Down Expand Up @@ -134,6 +141,13 @@ private function createTextLogger(): LineMutationTestingResultsLogger
);
}

private function createHtmlLogger(): LineMutationTestingResultsLogger
{
return new HtmlFileLogger(
$this->strykerHtmlReportBuilder,
);
}

private function createSummaryLogger(): LineMutationTestingResultsLogger
{
return new SummaryFileLogger($this->metricsCalculator);
Expand Down
85 changes: 85 additions & 0 deletions src/Logger/Html/HtmlFileLogger.php
@@ -0,0 +1,85 @@
<?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\Logger\Html;

use Infection\Logger\LineMutationTestingResultsLogger;
use function Safe\json_encode;

/**
* @internal
*/
final class HtmlFileLogger implements LineMutationTestingResultsLogger
{
private StrykerHtmlReportBuilder $strykerHtmlReportBuilder;

public function __construct(
StrykerHtmlReportBuilder $strykerHtmlReportBuilder
) {
$this->strykerHtmlReportBuilder = $strykerHtmlReportBuilder;
}

public function getLogLines(): array
{
return [
<<<"HTML"
<!DOCTYPE html>
<html>
<body>
<a href="/">Back</a>
<mutation-test-report-app title-postfix="Infection"></mutation-test-report-app>
<script defer src="https://www.unpkg.com/mutation-testing-elements"></script>
<script>
const app = document.getElementsByTagName('mutation-test-report-app').item(0);
function updateTheme() {
document.body.style.backgroundColor = app.themeBackgroundColor;
}
app.addEventListener('theme-changed', updateTheme);
updateTheme();

document.getElementsByTagName('mutation-test-report-app').item(0).report = {$this->getMutationTestingReport()}
;
</script>
</body>
</html>
HTML
];
}

private function getMutationTestingReport(): string
{
return json_encode($this->strykerHtmlReportBuilder->build());
}
}