diff --git a/.github/workflows/mt-annotations.yaml b/.github/workflows/mt-annotations.yaml index 9958b3609..96fd35d64 100644 --- a/.github/workflows/mt-annotations.yaml +++ b/.github/workflows/mt-annotations.yaml @@ -49,4 +49,4 @@ jobs: - name: Run Infection for added files only run: | git fetch origin $GITHUB_BASE_REF - php bin/infection -j2 --git-diff-lines --git-diff-base=origin/$GITHUB_BASE_REF --logger-github --ignore-msi-with-no-mutations --only-covered + php bin/infection -j2 --git-diff-lines --git-diff-base=origin/$GITHUB_BASE_REF --ignore-msi-with-no-mutations --only-covered diff --git a/.github/workflows/mt.yaml b/.github/workflows/mt.yaml index cd235fd2d..a3c1726ff 100644 --- a/.github/workflows/mt.yaml +++ b/.github/workflows/mt.yaml @@ -11,7 +11,7 @@ on: - master env: - MIN_MSI: 71.05 + MIN_MSI: 71.39 MIN_COVERED_MSI: 86.78 jobs: diff --git a/src/Command/RunCommand.php b/src/Command/RunCommand.php index 3f2ec9481..260130b32 100644 --- a/src/Command/RunCommand.php +++ b/src/Command/RunCommand.php @@ -37,6 +37,7 @@ use function extension_loaded; use function file_exists; +use function getenv; use function implode; use Infection\Configuration\Configuration; use Infection\Configuration\Schema\SchemaConfigurationLoader; @@ -61,8 +62,9 @@ use InvalidArgumentException; use const PHP_SAPI; use Psr\Log\LoggerInterface; -use function Safe\sprintf; +use function sprintf; use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use function trim; @@ -264,8 +266,9 @@ protected function configure(): void ->addOption( self::OPTION_LOGGER_GITHUB, null, - InputOption::VALUE_NONE, - 'Log escaped Mutants as GitHub Annotations.', + InputOption::VALUE_OPTIONAL, + '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_HTML, @@ -482,7 +485,7 @@ private function createContainer(IO $io, LoggerInterface $logger): Container $gitDiffFilter, $isForGitDiffLines, $gitDiffBase, - (bool) $input->getOption(self::OPTION_LOGGER_GITHUB), + $this->getUseGitHubLogger($input), $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) @@ -606,4 +609,38 @@ private function logRunningWithDebugger(ConsoleOutput $consoleOutput): void $consoleOutput->logRunningWithDebugger('PCOV'); } } + + private function getUseGitHubLogger(InputInterface $input): ?bool + { + // on e2e environment, we don't need github logger + if (getenv('INFECTION_E2E_TESTS_ENV') !== false) { + return false; + } + + $useGitHubLogger = $input->getOption(self::OPTION_LOGGER_GITHUB); + // `false` means the option was not provided at all -> user does not care and it will be auto-detected + // `null` means the option was provided without any argument -> user wants to enable it + // any string: the argument provided, but only `'true'` and `'false` are supported + if ($useGitHubLogger === false) { + return null; + } + + if ($useGitHubLogger === null) { + return true; + } + + if ($useGitHubLogger === 'true') { + return true; + } + + if ($useGitHubLogger === 'false') { + return false; + } + + throw new InvalidArgumentException(sprintf( + 'Cannot pass "%s" to "--%s": only "true", "false" or no argument is supported', + $useGitHubLogger, + self::OPTION_LOGGER_GITHUB + )); + } } diff --git a/src/Configuration/ConfigurationFactory.php b/src/Configuration/ConfigurationFactory.php index a6909d71d..c5ce31b28 100644 --- a/src/Configuration/ConfigurationFactory.php +++ b/src/Configuration/ConfigurationFactory.php @@ -52,7 +52,9 @@ use Infection\Mutator\MutatorParser; use Infection\Mutator\MutatorResolver; use Infection\TestFramework\TestFrameworkTypes; +use OndraM\CiDetector\CiDetector; use OndraM\CiDetector\CiDetectorInterface; +use OndraM\CiDetector\Exception\CiNotDetectedException; use function Safe\sprintf; use function sys_get_temp_dir; use Webmozart\Assert\Assert; @@ -118,7 +120,7 @@ public function create( ?string $gitDiffFilter, bool $isForGitDiffLines, ?string $gitDiffBase, - bool $useGitHubLogger, + ?bool $useGitHubLogger, ?string $htmlLogFilePath, bool $useNoopMutators, bool $executeOnlyCoveringTestCases @@ -316,8 +318,12 @@ private function retrieveFilter(string $filter, ?string $gitDiffFilter, bool $is return $this->gitDiffFileProvider->provide($gitDiffFilter, $baseBranch); } - private function retrieveLogs(Logs $logs, bool $useGitHubLogger, ?string $htmlLogFilePath): Logs + private function retrieveLogs(Logs $logs, ?bool $useGitHubLogger, ?string $htmlLogFilePath): Logs { + if ($useGitHubLogger === null) { + $useGitHubLogger = $this->detectCiGithubActions(); + } + if ($useGitHubLogger) { $logs->setUseGitHubAnnotationsLogger($useGitHubLogger); } @@ -328,4 +334,15 @@ private function retrieveLogs(Logs $logs, bool $useGitHubLogger, ?string $htmlLo return $logs; } + + private function detectCiGithubActions(): bool + { + try { + $ci = $this->ciDetector->detect(); + } catch (CiNotDetectedException $e) { + return false; + } + + return $ci->getCiName() === CiDetector::CI_GITHUB_ACTIONS; + } } diff --git a/src/Container.php b/src/Container.php index f4d4ab69c..541a9a0cd 100644 --- a/src/Container.php +++ b/src/Container.php @@ -169,7 +169,7 @@ final class Container public const DEFAULT_GIT_DIFF_FILTER = null; public const DEFAULT_GIT_DIFF_LINES = false; public const DEFAULT_GIT_DIFF_BASE = null; - public const DEFAULT_USE_GITHUB_LOGGER = false; + public const DEFAULT_USE_GITHUB_LOGGER = null; public const DEFAULT_HTML_LOGGER_PATH = null; public const DEFAULT_USE_NOOP_MUTATORS = false; public const DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES = false; @@ -610,7 +610,7 @@ public function withValues( ?string $gitDiffFilter, bool $isForGitDiffLines, ?string $gitDiffBase, - bool $useGitHubLogger, + ?bool $useGitHubLogger, ?string $htmlLogFilePath, bool $useNoopMutators, bool $executeOnlyCoveringTestCases diff --git a/tests/e2e_tests b/tests/e2e_tests index a92a13178..f30ec3c9e 100755 --- a/tests/e2e_tests +++ b/tests/e2e_tests @@ -23,9 +23,9 @@ do if [ -f "run_tests.bash" ] then - output="$(bash run_tests.bash)" + output="$(INFECTION_E2E_TESTS_ENV=1 bash run_tests.bash)" else - output="$(bash ../standard_script.bash ${1:-bin/infection})" + output="$(INFECTION_E2E_TESTS_ENV=1 bash ../standard_script.bash ${1:-bin/infection})" fi if [ $? != 0 ] diff --git a/tests/phpunit/Configuration/ConfigurationFactoryTest.php b/tests/phpunit/Configuration/ConfigurationFactoryTest.php index a5810cf38..44869725d 100644 --- a/tests/phpunit/Configuration/ConfigurationFactoryTest.php +++ b/tests/phpunit/Configuration/ConfigurationFactoryTest.php @@ -91,6 +91,7 @@ public static function tearDownAfterClass(): void */ public function test_it_can_create_a_configuration( bool $ciDetected, + bool $githubActionsDetected, SchemaConfiguration $schema, ?string $inputExistingCoveragePath, ?string $inputInitialTestsPhpOptions, @@ -112,7 +113,7 @@ public function test_it_can_create_a_configuration( ?string $inputGitDiffFilter, bool $inputIsForGitDiffLines, string $inputGitDiffBase, - bool $inputUseGitHubAnnotationsLogger, + ?bool $inputUseGitHubAnnotationsLogger, ?string $inputHtmlLogFilePath, bool $inputUseNoopMutators, int $inputMsiPrecision, @@ -144,7 +145,7 @@ public function test_it_can_create_a_configuration( bool $inputExecuteOnlyCoveringTestCases ): void { $config = $this - ->createConfigurationFactory($ciDetected) + ->createConfigurationFactory($ciDetected, $githubActionsDetected) ->create( $schema, $inputExistingCoveragePath, @@ -217,6 +218,7 @@ public function valueProvider(): iterable $expectedLogs->setUseGitHubAnnotationsLogger(true); yield 'minimal' => [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -397,12 +399,42 @@ public function valueProvider(): iterable true ); - yield 'ignoreMsiWithNoMutations not specified in schema and not specified in input' => self::createValueForIgnoreMsiWithNoMutations( - null, + yield 'Github Actions annotation disabled, not logged in non-Github Actions environment' => self::createValueForGithubActionsDetected( + false, + false, + false + ); + + yield 'Github Actions annotation disabled, not logged in Github Actions environment' => self::createValueForGithubActionsDetected( + false, + true, + false + ); + + yield 'Github Actions annotation not provided, not logged in non-Github Actions environment' => self::createValueForGithubActionsDetected( null, + false, false ); + yield 'Github Actions annotation not provided, logged in Github Actions environment' => self::createValueForGithubActionsDetected( + null, + true, + true + ); + + yield 'Github Actions annotation enabled, logged in non-Github Actions environment' => self::createValueForGithubActionsDetected( + true, + false, + true + ); + + yield 'Github Actions annotation enabled, logged in Github Actions environment' => self::createValueForGithubActionsDetected( + true, + true, + true + ); + yield 'ignoreMsiWithNoMutations not specified in schema and true in input' => self::createValueForIgnoreMsiWithNoMutations( null, true, @@ -686,6 +718,7 @@ public function valueProvider(): iterable ); yield 'with source files' => [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -759,6 +792,7 @@ public function valueProvider(): iterable ]; yield 'complete' => [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -865,6 +899,7 @@ private static function createValueForTimeout( int $expectedTimeOut ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -940,6 +975,7 @@ private static function createValueForTmpDir( ?string $expectedTmpDir ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1016,6 +1052,7 @@ private static function createValueForCoveragePath( string $expectedCoveragePath ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1091,6 +1128,7 @@ private static function createValueForPhpUnitConfigDir( ?string $expectedPhpUnitConfigDir ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1168,6 +1206,7 @@ private static function createValueForNoProgress( ): array { return [ $ciDetected, + false, new SchemaConfiguration( '/path/to/infection.json', null, @@ -1237,12 +1276,101 @@ private static function createValueForNoProgress( ]; } + private static function createValueForGithubActionsDetected( + ?bool $inputUseGitHubAnnotationsLogger, + bool $githubActionsDetected, + bool $useGitHubAnnotationsLogger + ): array { + $expectedLogs = new Logs( + null, + null, + null, + null, + null, + null, + $useGitHubAnnotationsLogger, + null, + ); + + return [ + false, + $githubActionsDetected, + new SchemaConfiguration( + '/path/to/infection.json', + null, + new Source([], []), + Logs::createEmpty(), + '', + 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, + null, + false, + 'master', + $inputUseGitHubAnnotationsLogger, + null, + false, + 2, + 10, + [], + [], + '', + [], + $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, + [], + false, + ]; + } + private static function createValueForIgnoreMsiWithNoMutations( ?bool $ignoreMsiWithNoMutationsFromSchemaConfiguration, ?bool $ignoreMsiWithNoMutationsFromInput, ?bool $expectedIgnoreMsiWithNoMutations ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1319,6 +1447,7 @@ private static function createValueForMinMsi( ?float $expectedMinMsi ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1395,6 +1524,7 @@ private static function createValueForMinCoveredMsi( ?float $expectedMinCoveredMsi ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1472,6 +1602,7 @@ private static function createValueForTestFramework( string $expectedTestFrameworkExtraOptions ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1548,6 +1679,7 @@ private static function createValueForInitialTestsPhpOptions( ?string $expectedInitialTestPhpOptions ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1625,6 +1757,7 @@ private static function createValueForTestFrameworkExtraOptions( string $expectedTestFrameworkExtraOptions ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1701,6 +1834,7 @@ private static function createValueForTestFrameworkKey( string $expectedTestFrameworkExtraOptions ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1781,6 +1915,7 @@ private static function createValueForMutators( array $expectedMutators ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1860,6 +1995,7 @@ private static function createValueForIgnoreSourceCodeByRegex( array $expectedIgnoreSourceCodeMutatorsMap ): array { return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -1946,6 +2082,7 @@ private static function createValueForHtmlLogFilePath(?string $htmlFileLogPathIn ); return [ + false, false, new SchemaConfiguration( '/path/to/infection.json', @@ -2045,7 +2182,7 @@ private static function getDefaultMutators(): array return self::$mutators; } - private function createConfigurationFactory(bool $ciDetected): ConfigurationFactory + private function createConfigurationFactory(bool $ciDetected, bool $githubActionsDetected): ConfigurationFactory { /** @var SourceFileCollector&ObjectProphecy $sourceFilesCollectorProphecy */ $sourceFilesCollectorProphecy = $this->prophesize(SourceFileCollector::class); @@ -2071,7 +2208,7 @@ private function createConfigurationFactory(bool $ciDetected): ConfigurationFact SingletonContainer::getContainer()->getMutatorFactory(), new MutatorParser(), $sourceFilesCollectorProphecy->reveal(), - new DummyCiDetector($ciDetected), + new DummyCiDetector($ciDetected, $githubActionsDetected), $gitDiffFilesProviderMock ); } diff --git a/tests/phpunit/Configuration/Entry/LogsAssertions.php b/tests/phpunit/Configuration/Entry/LogsAssertions.php index c4ccd7b70..549cfe05b 100644 --- a/tests/phpunit/Configuration/Entry/LogsAssertions.php +++ b/tests/phpunit/Configuration/Entry/LogsAssertions.php @@ -57,7 +57,7 @@ private function assertLogsStateIs( $this->assertSame($expectedJsonLogFilePath, $logs->getJsonLogFilePath()); $this->assertSame($expectedDebugLogFilePath, $logs->getDebugLogFilePath()); $this->assertSame($expectedPerMutatorFilePath, $logs->getPerMutatorFilePath()); - $this->assertSame($expectedUseGitHubAnnotationsLogger, $logs->getUseGitHubAnnotationsLogger()); + $this->assertSame($expectedUseGitHubAnnotationsLogger, $logs->getUseGitHubAnnotationsLogger(), 'Use GithubAnnotationLogger is incorrect'); $strykerConfig = $logs->getStrykerConfig(); diff --git a/tests/phpunit/Console/E2ETest.php b/tests/phpunit/Console/E2ETest.php index 1a5b1e2bf..8ecdd681d 100644 --- a/tests/phpunit/Console/E2ETest.php +++ b/tests/phpunit/Console/E2ETest.php @@ -349,6 +349,7 @@ private function runInfection(int $expectedExitCode, array $argvExtra = []): str 'run', '--verbose', '--no-interaction', + '--logger-github=false', ], $argvExtra)); $output = new BufferedOutput(); diff --git a/tests/phpunit/Fixtures/DummyCiDetector.php b/tests/phpunit/Fixtures/DummyCiDetector.php index 6f21edcab..603d29219 100644 --- a/tests/phpunit/Fixtures/DummyCiDetector.php +++ b/tests/phpunit/Fixtures/DummyCiDetector.php @@ -6,20 +6,23 @@ use Infection\Tests\UnsupportedMethod; use OndraM\CiDetector\Ci\CiInterface; -use OndraM\CiDetector\CiDetector; +use OndraM\CiDetector\Ci\GitHubActions; use OndraM\CiDetector\CiDetectorInterface; use OndraM\CiDetector\Env; +use OndraM\CiDetector\Exception\CiNotDetectedException; final class DummyCiDetector implements CiDetectorInterface { private bool $ciDetected; + private bool $githubActionsDetected; - public function __construct(bool $ciDetected) + public function __construct(bool $ciDetected, bool $githubActionsDetected = false) { $this->ciDetected = $ciDetected; + $this->githubActionsDetected = $githubActionsDetected; } - public static function fromEnvironment(Env $environment): CiDetector + public static function fromEnvironment(Env $environment): CiDetectorInterface { throw UnsupportedMethod::method(__CLASS__, __FUNCTION__); } @@ -31,6 +34,10 @@ public function isCiDetected(): bool public function detect(): CiInterface { - throw UnsupportedMethod::method(__CLASS__, __FUNCTION__); + if ($this->githubActionsDetected) { + return new GitHubActions(new Env()); + } + + throw new CiNotDetectedException('No CI server detected in current environment'); } }