From 4e9ea31ff2ba51777676d24ab4e2c75f1120042a Mon Sep 17 00:00:00 2001 From: Conrad Lara Date: Tue, 26 Sep 2023 19:02:08 -0700 Subject: [PATCH 1/2] Add GitLab code quality reporting. --- resources/schema.json | 4 + src/Configuration/ConfigurationFactory.php | 1 + src/Configuration/Entry/Logs.php | 7 + .../Schema/SchemaConfigurationFactory.php | 1 + src/Logger/FileLoggerFactory.php | 7 + src/Logger/GitLabCodeQualityLogger.php | 80 +++++++++ .../Configuration/ConfigurationAssertions.php | 1 + .../ConfigurationFactoryTest.php | 5 + .../Configuration/ConfigurationTest.php | 1 + .../Configuration/Entry/LogsAssertions.php | 2 + .../phpunit/Configuration/Entry/LogsTest.php | 6 + .../Schema/SchemaConfigurationFactoryTest.php | 46 ++++++ .../Schema/SchemaConfigurationTest.php | 1 + .../phpunit/Logger/FileLoggerFactoryTest.php | 28 ++++ .../Logger/GitLabCodeQualityLoggerTest.php | 153 ++++++++++++++++++ .../Logger/StrykerLoggerFactoryTest.php | 5 + 16 files changed, 348 insertions(+) create mode 100644 src/Logger/GitLabCodeQualityLogger.php create mode 100644 tests/phpunit/Logger/GitLabCodeQualityLoggerTest.php diff --git a/resources/schema.json b/resources/schema.json index 27da9db3c..f8780306d 100644 --- a/resources/schema.json +++ b/resources/schema.json @@ -96,6 +96,10 @@ "type": "boolean", "description": "GitHub Annotations for escaped Mutants in the added/modifies files" }, + "gitlab": { + "type": "string", + "definition": "GitLab(Code Climate) code quality json file." + }, "summaryJson": { "type": "string", "definition": "Summary JSON log file, which contains only the statistics from the complete JSON file." diff --git a/src/Configuration/ConfigurationFactory.php b/src/Configuration/ConfigurationFactory.php index 5869d9298..a1646af4d 100644 --- a/src/Configuration/ConfigurationFactory.php +++ b/src/Configuration/ConfigurationFactory.php @@ -327,6 +327,7 @@ private function retrieveLogs(Logs $logs, string $configDir, ?bool $useGitHubLog self::pathToAbsolute($logs->getHtmlLogFilePath(), $configDir), self::pathToAbsolute($logs->getSummaryLogFilePath(), $configDir), self::pathToAbsolute($logs->getJsonLogFilePath(), $configDir), + self::pathToAbsolute($logs->getGitlabLogFilePath(), $configDir), self::pathToAbsolute($logs->getDebugLogFilePath(), $configDir), self::pathToAbsolute($logs->getPerMutatorFilePath(), $configDir), $logs->getUseGitHubAnnotationsLogger(), diff --git a/src/Configuration/Entry/Logs.php b/src/Configuration/Entry/Logs.php index b1b51c75c..eb883ea05 100644 --- a/src/Configuration/Entry/Logs.php +++ b/src/Configuration/Entry/Logs.php @@ -46,6 +46,7 @@ public function __construct( private ?string $htmlLogFilePath, private readonly ?string $summaryLogFilePath, private readonly ?string $jsonLogFilePath, + private readonly ?string $gitlabLogFilePath, private readonly ?string $debugLogFilePath, private readonly ?string $perMutatorFilePath, private bool $useGitHubAnnotationsLogger, @@ -63,6 +64,7 @@ public static function createEmpty(): self null, null, null, + null, false, null, null @@ -94,6 +96,11 @@ public function getJsonLogFilePath(): ?string return $this->jsonLogFilePath; } + public function getGitlabLogFilePath(): ?string + { + return $this->gitlabLogFilePath; + } + public function getDebugLogFilePath(): ?string { return $this->debugLogFilePath; diff --git a/src/Configuration/Schema/SchemaConfigurationFactory.php b/src/Configuration/Schema/SchemaConfigurationFactory.php index c26c2390f..42b9f155d 100644 --- a/src/Configuration/Schema/SchemaConfigurationFactory.php +++ b/src/Configuration/Schema/SchemaConfigurationFactory.php @@ -85,6 +85,7 @@ private static function createLogs(stdClass $logs): Logs self::normalizeString($logs->html ?? null), self::normalizeString($logs->summary ?? null), self::normalizeString($logs->json ?? null), + self::normalizeString($logs->gitlab ?? null), self::normalizeString($logs->debug ?? null), self::normalizeString($logs->perMutator ?? null), $logs->github ?? false, diff --git a/src/Logger/FileLoggerFactory.php b/src/Logger/FileLoggerFactory.php index 129fe090a..09f6fef92 100644 --- a/src/Logger/FileLoggerFactory.php +++ b/src/Logger/FileLoggerFactory.php @@ -86,6 +86,8 @@ private function createLineLoggers(Logs $logConfig): iterable yield $logConfig->getJsonLogFilePath() => $this->createJsonLogger(); + yield $logConfig->getGitlabLogFilePath() => $this->createGitlabLogger(); + yield $logConfig->getDebugLogFilePath() => $this->createDebugLogger(); yield $logConfig->getPerMutatorFilePath() => $this->createPerMutatorLogger(); @@ -138,6 +140,11 @@ private function createJsonLogger(): LineMutationTestingResultsLogger ); } + private function createGitlabLogger(): LineMutationTestingResultsLogger + { + return new GitLabCodeQualityLogger($this->resultsCollector); + } + private function createGitHubAnnotationsLogger(): LineMutationTestingResultsLogger { return new GitHubAnnotationsLogger($this->resultsCollector); diff --git a/src/Logger/GitLabCodeQualityLogger.php b/src/Logger/GitLabCodeQualityLogger.php new file mode 100644 index 000000000..1e5eaf2c7 --- /dev/null +++ b/src/Logger/GitLabCodeQualityLogger.php @@ -0,0 +1,80 @@ +resultsCollector->getEscapedExecutionResults() as $escapedExecutionResult) { + $lines[] = [ + 'type' => 'issue', + 'fingerprint' => $escapedExecutionResult->getMutantHash(), + 'check_name' => $escapedExecutionResult->getMutatorName(), + 'description' => 'Escaped Mutant for Mutator ' . $escapedExecutionResult->getMutatorName(), + 'content' => Str::convertToUtf8(Str::trimLineReturns($escapedExecutionResult->getMutantDiff())), + 'categories' => ['Escaped Mutant'], + 'location' => [ + 'path' => Path::makeRelative($escapedExecutionResult->getOriginalFilePath(), $projectRootDirectory), + 'lines' => [ + 'begin' => $escapedExecutionResult->getOriginalStartingLine(), + ], + ], + 'severity' => 'major', + ]; + } + + return [json_encode($lines, JSON_THROW_ON_ERROR)]; + } +} diff --git a/tests/phpunit/Configuration/ConfigurationAssertions.php b/tests/phpunit/Configuration/ConfigurationAssertions.php index 6bbbf8d32..026fa08af 100644 --- a/tests/phpunit/Configuration/ConfigurationAssertions.php +++ b/tests/phpunit/Configuration/ConfigurationAssertions.php @@ -102,6 +102,7 @@ private function assertConfigurationStateIs( $expectedLogs->getHtmlLogFilePath(), $expectedLogs->getSummaryLogFilePath(), $expectedLogs->getJsonLogFilePath(), + $expectedLogs->getGitlabLogFilePath(), $expectedLogs->getDebugLogFilePath(), $expectedLogs->getPerMutatorFilePath(), $expectedLogs->getUseGitHubAnnotationsLogger(), diff --git a/tests/phpunit/Configuration/ConfigurationFactoryTest.php b/tests/phpunit/Configuration/ConfigurationFactoryTest.php index c37ae3c81..5e61fdab3 100644 --- a/tests/phpunit/Configuration/ConfigurationFactoryTest.php +++ b/tests/phpunit/Configuration/ConfigurationFactoryTest.php @@ -815,6 +815,7 @@ public function valueProvider(): iterable '/report.html', '/summary.log', '/json.log', + '/gitlab.log', '/debug.log', '/mutator.log', true, @@ -872,6 +873,7 @@ public function valueProvider(): iterable '/report.html', '/summary.log', '/json.log', + '/gitlab.log', '/debug.log', '/mutator.log', true, @@ -1302,6 +1304,7 @@ private static function createValueForGithubActionsDetected( null, null, null, + null, $useGitHubAnnotationsLogger, null, null, @@ -2092,6 +2095,7 @@ private static function createValueForHtmlLogFilePath(?string $htmlFileLogPathIn null, null, null, + null, true, null, null, @@ -2111,6 +2115,7 @@ private static function createValueForHtmlLogFilePath(?string $htmlFileLogPathIn null, null, null, + null, false, null, null, diff --git a/tests/phpunit/Configuration/ConfigurationTest.php b/tests/phpunit/Configuration/ConfigurationTest.php index d726e4ad5..5e303a872 100644 --- a/tests/phpunit/Configuration/ConfigurationTest.php +++ b/tests/phpunit/Configuration/ConfigurationTest.php @@ -212,6 +212,7 @@ public function valueProvider(): iterable 'report.html', 'summary.log', 'json.log', + 'gitlab.log', 'debug.log', 'mutator.log', true, diff --git a/tests/phpunit/Configuration/Entry/LogsAssertions.php b/tests/phpunit/Configuration/Entry/LogsAssertions.php index 27b00a221..e83af12c3 100644 --- a/tests/phpunit/Configuration/Entry/LogsAssertions.php +++ b/tests/phpunit/Configuration/Entry/LogsAssertions.php @@ -46,6 +46,7 @@ private function assertLogsStateIs( ?string $expectedHtmlLogFilePath, ?string $expectedSummaryLogFilePath, ?string $expectedJsonLogFilePath, + ?string $expectedGitlabLogFilePath, ?string $expectedDebugLogFilePath, ?string $expectedPerMutatorFilePath, bool $expectedUseGitHubAnnotationsLogger, @@ -56,6 +57,7 @@ private function assertLogsStateIs( $this->assertSame($expectedHtmlLogFilePath, $logs->getHtmlLogFilePath()); $this->assertSame($expectedSummaryLogFilePath, $logs->getSummaryLogFilePath()); $this->assertSame($expectedJsonLogFilePath, $logs->getJsonLogFilePath()); + $this->assertSame($expectedGitlabLogFilePath, $logs->getGitlabLogFilePath()); $this->assertSame($expectedDebugLogFilePath, $logs->getDebugLogFilePath()); $this->assertSame($expectedPerMutatorFilePath, $logs->getPerMutatorFilePath()); $this->assertSame($expectedUseGitHubAnnotationsLogger, $logs->getUseGitHubAnnotationsLogger(), 'Use GithubAnnotationLogger is incorrect'); diff --git a/tests/phpunit/Configuration/Entry/LogsTest.php b/tests/phpunit/Configuration/Entry/LogsTest.php index 5c61d1f73..5c95d8c9e 100644 --- a/tests/phpunit/Configuration/Entry/LogsTest.php +++ b/tests/phpunit/Configuration/Entry/LogsTest.php @@ -51,6 +51,7 @@ public function test_it_can_be_instantiated( ?string $htmlLogFilePath, ?string $summaryLogFilePath, ?string $jsonLogFilePath, + ?string $gitlabLogFilePath, ?string $debugLogFilePath, ?string $perMutatorFilePath, bool $useGitHubAnnotationsLogger, @@ -62,6 +63,7 @@ public function test_it_can_be_instantiated( $htmlLogFilePath, $summaryLogFilePath, $jsonLogFilePath, + $gitlabLogFilePath, $debugLogFilePath, $perMutatorFilePath, $useGitHubAnnotationsLogger, @@ -75,6 +77,7 @@ public function test_it_can_be_instantiated( $htmlLogFilePath, $summaryLogFilePath, $jsonLogFilePath, + $gitlabLogFilePath, $debugLogFilePath, $perMutatorFilePath, $useGitHubAnnotationsLogger, @@ -95,6 +98,7 @@ public function test_it_can_be_instantiated_without_any_values(): void null, null, null, + null, false, null, null @@ -110,6 +114,7 @@ public function valuesProvider(): iterable null, null, null, + null, false, null, null, @@ -120,6 +125,7 @@ public function valuesProvider(): iterable 'report.html', 'summary.log', 'json.log', + 'gitlab.log', 'debug.log', 'perMutator.log', true, diff --git a/tests/phpunit/Configuration/Schema/SchemaConfigurationFactoryTest.php b/tests/phpunit/Configuration/Schema/SchemaConfigurationFactoryTest.php index 4ac255144..786ddec41 100644 --- a/tests/phpunit/Configuration/Schema/SchemaConfigurationFactoryTest.php +++ b/tests/phpunit/Configuration/Schema/SchemaConfigurationFactoryTest.php @@ -231,6 +231,7 @@ public function provideRawConfig(): iterable null, null, null, + null, false, null, null @@ -259,6 +260,7 @@ public function provideRawConfig(): iterable null, null, null, + null, false, null, null @@ -287,6 +289,7 @@ public function provideRawConfig(): iterable null, null, null, + null, false, null, null @@ -315,6 +318,36 @@ public function provideRawConfig(): iterable 'json.log', null, null, + null, + false, + null, + null + ), + ]), + ]; + + yield '[logs][gitlab] nominal' => [ + <<<'JSON' +{ + "source": { + "directories": ["src"] + }, + "logs": { + "gitlab": "gitlab.log" + } +} +JSON + , + self::createConfig([ + 'source' => new Source(['src'], []), + 'logs' => new Logs( + null, + null, + null, + null, + 'gitlab.log', + null, + null, false, null, null @@ -341,6 +374,7 @@ public function provideRawConfig(): iterable null, null, null, + null, 'debug.log', null, false, @@ -370,6 +404,7 @@ public function provideRawConfig(): iterable null, null, null, + null, 'perMutator.log', false, null, @@ -401,6 +436,7 @@ public function provideRawConfig(): iterable null, null, null, + null, false, StrykerConfig::forBadge('master'), null @@ -431,6 +467,7 @@ public function provideRawConfig(): iterable null, null, null, + null, false, StrykerConfig::forFullReport('master'), null @@ -461,6 +498,7 @@ public function provideRawConfig(): iterable null, null, null, + null, false, StrykerConfig::forBadge('/^foo$/'), null @@ -491,6 +529,7 @@ public function provideRawConfig(): iterable null, null, null, + null, false, StrykerConfig::forFullReport('/^foo$/'), null @@ -519,6 +558,7 @@ public function provideRawConfig(): iterable null, null, null, + null, false, null, 'summary.json' @@ -537,6 +577,7 @@ public function provideRawConfig(): iterable "html": "report.html", "summary": "summary.log", "json": "json.log", + "gitlab": "gitlab.log", "debug": "debug.log", "perMutator": "perMutator.log", "github": true, @@ -555,6 +596,7 @@ public function provideRawConfig(): iterable 'report.html', 'summary.log', 'json.log', + 'gitlab.log', 'debug.log', 'perMutator.log', true, @@ -623,6 +665,7 @@ public function provideRawConfig(): iterable "html": " report.html ", "summary": " summary.log ", "json": " json.log ", + "gitlab": " gitlab.log", "debug": " debug.log ", "perMutator": " perMutator.log ", "github": true , @@ -641,6 +684,7 @@ public function provideRawConfig(): iterable 'report.html', 'summary.log', 'json.log', + 'gitlab.log', 'debug.log', 'perMutator.log', true, @@ -2275,6 +2319,7 @@ public function provideRawConfig(): iterable "html": "report.html", "summary": "summary.log", "json": "json.log", + "gitlab": "gitlab.log", "debug": "debug.log", "perMutator": "perMutator.log", "github": true, @@ -2515,6 +2560,7 @@ public function provideRawConfig(): iterable 'report.html', 'summary.log', 'json.log', + 'gitlab.log', 'debug.log', 'perMutator.log', true, diff --git a/tests/phpunit/Configuration/Schema/SchemaConfigurationTest.php b/tests/phpunit/Configuration/Schema/SchemaConfigurationTest.php index cecc0aa79..5fcc31778 100644 --- a/tests/phpunit/Configuration/Schema/SchemaConfigurationTest.php +++ b/tests/phpunit/Configuration/Schema/SchemaConfigurationTest.php @@ -124,6 +124,7 @@ public function valueProvider(): iterable 'report.html', 'summary.log', 'json.log', + 'gitlab.log', 'debug.log', 'mutator.log', true, diff --git a/tests/phpunit/Logger/FileLoggerFactoryTest.php b/tests/phpunit/Logger/FileLoggerFactoryTest.php index 1a9233b22..0878e45da 100644 --- a/tests/phpunit/Logger/FileLoggerFactoryTest.php +++ b/tests/phpunit/Logger/FileLoggerFactoryTest.php @@ -45,6 +45,7 @@ use Infection\Logger\FileLogger; use Infection\Logger\FileLoggerFactory; use Infection\Logger\GitHubAnnotationsLogger; +use Infection\Logger\GitLabCodeQualityLogger; use Infection\Logger\Html\HtmlFileLogger; use Infection\Logger\Html\StrykerHtmlReportBuilder; use Infection\Logger\JsonLogger; @@ -105,6 +106,7 @@ public function test_it_does_not_create_any_logger_for_no_verbosity_level_and_no '/a/file', '/a/file', '/a/file', + '/a/file', true, null, '/a/file', @@ -147,6 +149,7 @@ public function logsProvider(): iterable null, null, null, + null, false, null, null @@ -162,6 +165,7 @@ public function logsProvider(): iterable null, null, null, + null, false, null, null @@ -177,6 +181,7 @@ public function logsProvider(): iterable null, null, null, + null, false, null, null @@ -190,6 +195,7 @@ public function logsProvider(): iterable null, null, null, + null, 'debug_file', null, false, @@ -207,6 +213,7 @@ public function logsProvider(): iterable 'json_file', null, null, + null, false, null, null @@ -214,6 +221,22 @@ public function logsProvider(): iterable [JsonLogger::class], ]; + yield 'GitLab logger' => [ + new Logs( + null, + null, + null, + null, + 'gitlab.log', + null, + null, + false, + null, + null + ), + [GitLabCodeQualityLogger::class], + ]; + yield 'per mutator logger' => [ new Logs( null, @@ -221,6 +244,7 @@ public function logsProvider(): iterable null, null, null, + null, 'per_muator', false, null, @@ -237,6 +261,7 @@ public function logsProvider(): iterable null, null, null, + null, true, null, null @@ -252,6 +277,7 @@ public function logsProvider(): iterable null, null, null, + null, false, null, 'summary-json' @@ -265,6 +291,7 @@ public function logsProvider(): iterable 'html', 'summary', 'json', + 'gitlab', 'debug', 'per_mutator', true, @@ -276,6 +303,7 @@ public function logsProvider(): iterable HtmlFileLogger::class, SummaryFileLogger::class, JsonLogger::class, + GitLabCodeQualityLogger::class, DebugFileLogger::class, PerMutatorLogger::class, SummaryJsonLogger::class, diff --git a/tests/phpunit/Logger/GitLabCodeQualityLoggerTest.php b/tests/phpunit/Logger/GitLabCodeQualityLoggerTest.php new file mode 100644 index 000000000..2147770b7 --- /dev/null +++ b/tests/phpunit/Logger/GitLabCodeQualityLoggerTest.php @@ -0,0 +1,153 @@ +assertLoggedContentIs($expectedContents, $logger); + } + + public function metricsProvider(): iterable + { + yield 'no mutations; only covered' => [ + new ResultsCollector(), + [], + ]; + + yield 'all mutations; only covered' => [ + $this->createCompleteResultsCollector(), + [ + [ + 'type' => 'issue', + 'fingerprint' => 'a1b2c3', + 'check_name' => 'PregQuote', + 'description' => 'Escaped Mutant for Mutator PregQuote', + 'content' => str_replace("\n", PHP_EOL, "--- Original\n+++ New\n@@ @@\n\n- echo 'original';\n+ echo 'escaped#1';"), + 'categories' => ['Escaped Mutant'], + 'location' => [ + 'path' => 'foo/bar', + 'lines' => [ + 'begin' => 9, + ], + ], + 'severity' => 'major', + ], + [ + 'type' => 'issue', + 'fingerprint' => 'a1b2c3', + 'check_name' => 'For_', + 'description' => 'Escaped Mutant for Mutator For_', + 'content' => str_replace("\n", PHP_EOL, "--- Original\n+++ New\n@@ @@\n\n- echo 'original';\n+ echo 'escaped#0';"), + 'categories' => ['Escaped Mutant'], + 'location' => [ + 'path' => 'foo/bar', + 'lines' => [ + 'begin' => 10, + ], + ], + 'severity' => 'major', + ], + ], + ]; + + yield 'Non UTF-8 characters' => [ + $this->createNonUtf8CharactersCollector(), + [ + [ + 'type' => 'issue', + 'fingerprint' => 'a1b2c3', + 'check_name' => 'For_', + 'description' => 'Escaped Mutant for Mutator For_', + 'content' => str_replace("\n", PHP_EOL, "--- Original\n+++ New\n@@ @@\n\n- echo 'original';\n+ echo 'i?';"), + 'categories' => ['Escaped Mutant'], + 'location' => [ + 'path' => 'foo/bar', + 'lines' => [ + 'begin' => 10, + ], + ], + 'severity' => 'major', + ], + ], + ]; + } + + private function assertLoggedContentIs(array $expectedJson, GitLabCodeQualityLogger $logger): void + { + $this->assertSame($expectedJson, json_decode($logger->getLogLines()[0], true, JSON_THROW_ON_ERROR)); + } + + private function createNonUtf8CharactersCollector(): ResultsCollector + { + $collector = new ResultsCollector(); + + $collector->collect( + $this->createMutantExecutionResult( + 0, + For_::class, + DetectionStatus::ESCAPED, + base64_decode('abc', true) // produces non UTF-8 character + ), + ); + + return $collector; + } +} diff --git a/tests/phpunit/Logger/StrykerLoggerFactoryTest.php b/tests/phpunit/Logger/StrykerLoggerFactoryTest.php index 3cefab1ec..d1f12d89b 100644 --- a/tests/phpunit/Logger/StrykerLoggerFactoryTest.php +++ b/tests/phpunit/Logger/StrykerLoggerFactoryTest.php @@ -63,6 +63,7 @@ public function test_it_does_not_create_any_logger_for_no_verbosity_level_and_no '/a/file', '/a/file', '/a/file', + '/a/file', true, null, '/a/file', @@ -84,6 +85,7 @@ public function test_it_creates_a_stryker_logger_on_no_verbosity(): void null, null, null, + null, false, StrykerConfig::forBadge('master'), null @@ -128,6 +130,7 @@ public function logsProvider(): iterable null, null, null, + null, false, StrykerConfig::forBadge('foo'), null @@ -143,6 +146,7 @@ public function logsProvider(): iterable null, null, null, + null, false, StrykerConfig::forFullReport('foo'), null @@ -156,6 +160,7 @@ public function logsProvider(): iterable 'html', 'summary', 'json', + 'gitlab', 'debug', 'per_mutator', true, From 2fc798d7b2ac150cf35278691f7d3c714f4d8f77 Mon Sep 17 00:00:00 2001 From: Conrad Lara Date: Wed, 27 Sep 2023 19:25:35 -0700 Subject: [PATCH 2/2] Add cli --logger-gitlab options. --- resources/schema.json | 2 +- src/Command/RunCommand.php | 11 ++ src/Configuration/ConfigurationFactory.php | 9 +- src/Configuration/Entry/Logs.php | 7 +- src/Container.php | 4 + .../Tracing/provide-traces-closure.php | 1 + .../ConfigurationFactoryTest.php | 159 ++++++++++++++++++ tests/phpunit/ContainerTest.php | 2 + 8 files changed, 191 insertions(+), 4 deletions(-) diff --git a/resources/schema.json b/resources/schema.json index f8780306d..51f6153bd 100644 --- a/resources/schema.json +++ b/resources/schema.json @@ -98,7 +98,7 @@ }, "gitlab": { "type": "string", - "definition": "GitLab(Code Climate) code quality json file." + "definition": "GitLab (Code Climate) code quality json file." }, "summaryJson": { "type": "string", diff --git a/src/Command/RunCommand.php b/src/Command/RunCommand.php index f0a8b008b..e16d10c2d 100644 --- a/src/Command/RunCommand.php +++ b/src/Command/RunCommand.php @@ -125,6 +125,9 @@ final class RunCommand extends BaseCommand /** @var string */ private const OPTION_LOGGER_GITHUB = 'logger-github'; + /** @var string */ + private const OPTION_LOGGER_GITLAB = 'logger-gitlab'; + private const OPTION_LOGGER_HTML = 'logger-html'; private const OPTION_USE_NOOP_MUTATORS = 'noop'; @@ -274,6 +277,12 @@ protected function configure(): void 'Log escaped Mutants as GitHub Annotations (automatically detected on Github Actions itself, use true to force-enable or false to force-disable it).', false ) + ->addOption( + self::OPTION_LOGGER_GITLAB, + null, + InputOption::VALUE_OPTIONAL, + 'Path to log escaped Mutants in the GitLab (Code Climate) JSON format.', + ) ->addOption( self::OPTION_LOGGER_HTML, null, @@ -404,6 +413,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)); + $gitlabFileLogPath = trim((string) $input->getOption(self::OPTION_LOGGER_GITLAB)); $htmlFileLogPath = trim((string) $input->getOption(self::OPTION_LOGGER_HTML)); /** @var string|null $minMsi */ @@ -489,6 +499,7 @@ private function createContainer(IO $io, LoggerInterface $logger): Container $isForGitDiffLines, $gitDiffBase, $this->getUseGitHubLogger($input), + $gitlabFileLogPath === '' ? Container::DEFAULT_GITLAB_LOGGER_PATH : $gitlabFileLogPath, $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) diff --git a/src/Configuration/ConfigurationFactory.php b/src/Configuration/ConfigurationFactory.php index a1646af4d..2fbaa7e81 100644 --- a/src/Configuration/ConfigurationFactory.php +++ b/src/Configuration/ConfigurationFactory.php @@ -108,6 +108,7 @@ public function create( bool $isForGitDiffLines, ?string $gitDiffBase, ?bool $useGitHubLogger, + ?string $gitlabLogFilePath, ?string $htmlLogFilePath, bool $useNoopMutators, bool $executeOnlyCoveringTestCases @@ -140,7 +141,7 @@ public function create( ), $this->retrieveFilter($filter, $gitDiffFilter, $isForGitDiffLines, $gitDiffBase, $schema->getSource()->getDirectories()), $schema->getSource()->getExcludes(), - $this->retrieveLogs($schema->getLogs(), $configDir, $useGitHubLogger, $htmlLogFilePath), + $this->retrieveLogs($schema->getLogs(), $configDir, $useGitHubLogger, $gitlabLogFilePath, $htmlLogFilePath), $logVerbosity, $namespacedTmpDir, $this->retrievePhpUnit($schema, $configDir), @@ -308,7 +309,7 @@ private function retrieveFilter(string $filter, ?string $gitDiffFilter, bool $is return $this->gitDiffFileProvider->provide($gitDiffFilter, $baseBranch, $sourceDirectories); } - private function retrieveLogs(Logs $logs, string $configDir, ?bool $useGitHubLogger, ?string $htmlLogFilePath): Logs + private function retrieveLogs(Logs $logs, string $configDir, ?bool $useGitHubLogger, ?string $gitlabLogFilePath, ?string $htmlLogFilePath): Logs { if ($useGitHubLogger === null) { $useGitHubLogger = $this->detectCiGithubActions(); @@ -318,6 +319,10 @@ private function retrieveLogs(Logs $logs, string $configDir, ?bool $useGitHubLog $logs->setUseGitHubAnnotationsLogger($useGitHubLogger); } + if ($gitlabLogFilePath !== null) { + $logs->setGitlabLogFilePath($gitlabLogFilePath); + } + if ($htmlLogFilePath !== null) { $logs->setHtmlLogFilePath($htmlLogFilePath); } diff --git a/src/Configuration/Entry/Logs.php b/src/Configuration/Entry/Logs.php index eb883ea05..a3e017275 100644 --- a/src/Configuration/Entry/Logs.php +++ b/src/Configuration/Entry/Logs.php @@ -46,7 +46,7 @@ public function __construct( private ?string $htmlLogFilePath, private readonly ?string $summaryLogFilePath, private readonly ?string $jsonLogFilePath, - private readonly ?string $gitlabLogFilePath, + private ?string $gitlabLogFilePath, private readonly ?string $debugLogFilePath, private readonly ?string $perMutatorFilePath, private bool $useGitHubAnnotationsLogger, @@ -81,6 +81,11 @@ public function getHtmlLogFilePath(): ?string return $this->htmlLogFilePath; } + public function setGitlabLogFilePath(string $gitlabLogFilePath): void + { + $this->gitlabLogFilePath = $gitlabLogFilePath; + } + public function setHtmlLogFilePath(string $htmlLogFilePath): void { $this->htmlLogFilePath = $htmlLogFilePath; diff --git a/src/Container.php b/src/Container.php index ad9439e07..35afc14b0 100644 --- a/src/Container.php +++ b/src/Container.php @@ -173,6 +173,7 @@ final class Container public const DEFAULT_GIT_DIFF_LINES = false; public const DEFAULT_GIT_DIFF_BASE = null; public const DEFAULT_USE_GITHUB_LOGGER = null; + public const DEFAULT_GITLAB_LOGGER_PATH = null; public const DEFAULT_HTML_LOGGER_PATH = null; public const DEFAULT_USE_NOOP_MUTATORS = false; public const DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES = false; @@ -588,6 +589,7 @@ public static function create(): self self::DEFAULT_GIT_DIFF_LINES, self::DEFAULT_GIT_DIFF_BASE, self::DEFAULT_USE_GITHUB_LOGGER, + self::DEFAULT_GITLAB_LOGGER_PATH, self::DEFAULT_HTML_LOGGER_PATH, self::DEFAULT_USE_NOOP_MUTATORS, self::DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES @@ -622,6 +624,7 @@ public function withValues( bool $isForGitDiffLines, ?string $gitDiffBase, ?bool $useGitHubLogger, + ?string $gitlabLogFilePath, ?string $htmlLogFilePath, bool $useNoopMutators, bool $executeOnlyCoveringTestCases @@ -690,6 +693,7 @@ public function withValues( $isForGitDiffLines, $gitDiffBase, $useGitHubLogger, + $gitlabLogFilePath, $htmlLogFilePath, $useNoopMutators, $executeOnlyCoveringTestCases diff --git a/tests/benchmark/Tracing/provide-traces-closure.php b/tests/benchmark/Tracing/provide-traces-closure.php index b022223a0..77bbade0d 100644 --- a/tests/benchmark/Tracing/provide-traces-closure.php +++ b/tests/benchmark/Tracing/provide-traces-closure.php @@ -71,6 +71,7 @@ Container::DEFAULT_GIT_DIFF_LINES, Container::DEFAULT_GIT_DIFF_BASE, Container::DEFAULT_USE_GITHUB_LOGGER, + Container::DEFAULT_GITLAB_LOGGER_PATH, Container::DEFAULT_HTML_LOGGER_PATH, true, Container::DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES diff --git a/tests/phpunit/Configuration/ConfigurationFactoryTest.php b/tests/phpunit/Configuration/ConfigurationFactoryTest.php index 5e61fdab3..5790cbfe3 100644 --- a/tests/phpunit/Configuration/ConfigurationFactoryTest.php +++ b/tests/phpunit/Configuration/ConfigurationFactoryTest.php @@ -114,6 +114,7 @@ public function test_it_can_create_a_configuration( bool $inputIsForGitDiffLines, string $inputGitDiffBase, ?bool $inputUseGitHubAnnotationsLogger, + ?string $inputGitlabLogFilePath, ?string $inputHtmlLogFilePath, bool $inputUseNoopMutators, int $inputMsiPrecision, @@ -170,6 +171,7 @@ public function test_it_can_create_a_configuration( $inputIsForGitDiffLines, $inputGitDiffBase, $inputUseGitHubAnnotationsLogger, + $inputGitlabLogFilePath, $inputHtmlLogFilePath, $inputUseNoopMutators, $inputExecuteOnlyCoveringTestCases @@ -258,6 +260,7 @@ public function valueProvider(): iterable 'master', true, null, + null, false, 2, 10, @@ -447,6 +450,42 @@ public function valueProvider(): iterable true ); + yield 'null GitLab file log path with existing path from config file' => self::createValueForGitlabLogger( + '/from-config.json', + null, + '/from-config.json' + ); + + yield 'absolute GitLab file log path' => self::createValueForGitlabLogger( + '/path/to/from-config.json', + null, + '/path/to/from-config.json', + ); + + yield 'relative GitLab file log path' => self::createValueForGitlabLogger( + 'relative/path/to/from-config.json', + null, + '/path/to/relative/path/to/from-config.json' + ); + + yield 'override GitLab file log path from CLI option with existing path from config file' => self::createValueForGitlabLogger( + '/from-config.json', + '/from-cli.json', + '/from-cli.json' + ); + + yield 'set GitLab file log path from CLI option when config file has no setting' => self::createValueForGitlabLogger( + null, + '/from-cli.json', + '/from-cli.json' + ); + + yield 'null GitLab file log path in config and CLI' => self::createValueForGitlabLogger( + null, + null, + null + ); + yield 'ignoreMsiWithNoMutations not specified in schema and true in input' => self::createValueForIgnoreMsiWithNoMutations( null, true, @@ -770,6 +809,7 @@ public function valueProvider(): iterable 'master', false, null, + null, false, 2, 10, @@ -858,6 +898,7 @@ public function valueProvider(): iterable 'master', false, null, + null, false, 2, 10, @@ -955,6 +996,7 @@ private static function createValueForTimeout( 'master', false, null, + null, false, 2, $expectedTimeOut, @@ -1031,6 +1073,7 @@ private static function createValueForTmpDir( 'master', false, null, + null, false, 2, 10, @@ -1108,6 +1151,7 @@ private static function createValueForCoveragePath( 'master', false, null, + null, false, 2, 10, @@ -1184,6 +1228,7 @@ private static function createValueForPhpUnitConfigDir( 'master', false, null, + null, false, 2, 10, @@ -1261,6 +1306,7 @@ private static function createValueForNoProgress( 'master', false, null, + null, false, 2, 10, @@ -1351,6 +1397,7 @@ private static function createValueForGithubActionsDetected( 'master', $inputUseGitHubAnnotationsLogger, null, + null, false, 2, 10, @@ -1382,6 +1429,108 @@ private static function createValueForGithubActionsDetected( ]; } + private static function createValueForGitlabLogger( + ?string $gitlabFileLogPathInConfig, + ?string $gitlabFileLogPathFromCliOption, + ?string $expectedGitlabFileLogPath + ): array { + $expectedLogs = new Logs( + null, + null, + null, + null, + $expectedGitlabFileLogPath, + null, + null, + false, + null, + null, + ); + + return [ + false, + false, + new SchemaConfiguration( + '/path/to/infection.json', + null, + new Source([], []), + new Logs( + null, + null, + null, + null, + $gitlabFileLogPathInConfig, + null, + null, + false, + null, + null, + ), + '', + new PhpUnit(null, null), + null, + null, + null, + [], + null, + null, + null, + null + ), + null, + null, + false, + 'none', + false, + false, + false, + false, + null, + false, + null, + '', + null, + null, + '', + 0, + false, + 'AM', + false, + 'master', + false, + $gitlabFileLogPathFromCliOption, + null, + false, + 2, + 10, + [], + [], + 'src/a.php,src/b.php', + [], + $expectedLogs, + 'none', + sys_get_temp_dir() . '/infection', + new PhpUnit('/path/to', null), + self::getDefaultMutators(), + 'phpunit', + null, + null, + false, + '', + sys_get_temp_dir() . '/infection', + false, + false, + false, + false, + false, + null, + false, + null, + [], + true, + ]; + } + private static function createValueForIgnoreMsiWithNoMutations( ?bool $ignoreMsiWithNoMutationsFromSchemaConfiguration, ?bool $ignoreMsiWithNoMutationsFromInput, @@ -1428,6 +1577,7 @@ private static function createValueForIgnoreMsiWithNoMutations( 'master', false, null, + null, false, 2, 10, @@ -1505,6 +1655,7 @@ private static function createValueForMinMsi( 'master', false, null, + null, false, 2, 10, @@ -1582,6 +1733,7 @@ private static function createValueForMinCoveredMsi( 'master', false, null, + null, false, 2, 10, @@ -1660,6 +1812,7 @@ private static function createValueForTestFramework( 'master', false, null, + null, false, 2, 10, @@ -1737,6 +1890,7 @@ private static function createValueForInitialTestsPhpOptions( 'master', false, null, + null, false, 2, 10, @@ -1815,6 +1969,7 @@ private static function createValueForTestFrameworkExtraOptions( 'master', false, null, + null, false, 2, 10, @@ -1892,6 +2047,7 @@ private static function createValueForTestFrameworkKey( 'master', false, null, + null, false, 2, 10, @@ -1973,6 +2129,7 @@ private static function createValueForMutators( 'master', false, null, + null, $useNoopMutatos, 2, 10, @@ -2053,6 +2210,7 @@ private static function createValueForIgnoreSourceCodeByRegex( 'master', false, null, + null, false, 2, 10, @@ -2152,6 +2310,7 @@ private static function createValueForHtmlLogFilePath(?string $htmlFileLogPathIn false, 'master', true, + null, $htmlFileLogPathFromCliOption, false, 2, diff --git a/tests/phpunit/ContainerTest.php b/tests/phpunit/ContainerTest.php index 7e3049553..a9966ea3c 100644 --- a/tests/phpunit/ContainerTest.php +++ b/tests/phpunit/ContainerTest.php @@ -96,6 +96,7 @@ public function test_it_can_build_lazy_source_file_data_factory_that_fails_on_us Container::DEFAULT_GIT_DIFF_LINES, Container::DEFAULT_GIT_DIFF_BASE, Container::DEFAULT_USE_GITHUB_LOGGER, + Container::DEFAULT_GITLAB_LOGGER_PATH, Container::DEFAULT_HTML_LOGGER_PATH, Container::DEFAULT_USE_NOOP_MUTATORS, Container::DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES @@ -146,6 +147,7 @@ public function test_it_provides_a_friendly_error_when_attempting_to_configure_i Container::DEFAULT_GIT_DIFF_LINES, Container::DEFAULT_GIT_DIFF_BASE, Container::DEFAULT_USE_GITHUB_LOGGER, + Container::DEFAULT_GITLAB_LOGGER_PATH, Container::DEFAULT_HTML_LOGGER_PATH, Container::DEFAULT_USE_NOOP_MUTATORS, Container::DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES,