diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 56d6af2da..61b13f1e3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,7 +37,7 @@ jobs: with: php-version: ${{ matrix.php-version }} coverage: ${{ matrix.coverage-driver }} - ini-values: memory_limit=512M + ini-values: memory_limit=512M, xdebug.mode=off tools: composer:v2 - name: Get composer cache directory diff --git a/devTools/Dockerfile b/devTools/Dockerfile index c2d44757f..c940361ef 100644 --- a/devTools/Dockerfile +++ b/devTools/Dockerfile @@ -30,7 +30,7 @@ RUN apk add --no-cache \ zip COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -COPY memory-limit.ini xdebug-coverage.ini ${PHP_INI_DIR}/conf.d/ +COPY memory-limit.ini xdebug.ini ${PHP_INI_DIR}/conf.d/ RUN adduser -h /opt/infection -s /bin/bash -D infection diff --git a/devTools/phpstan-src.neon b/devTools/phpstan-src.neon index e501eee69..7a709b716 100644 --- a/devTools/phpstan-src.neon +++ b/devTools/phpstan-src.neon @@ -28,6 +28,12 @@ parameters: path: '../src/Container.php' message: '#^Method Infection\\Container::get.*\(\) should return .* but returns object\.$#' count: 1 + - + path: '../src/Process/OriginalPhpProcess.php' + message: '#Function ini_get is unsafe to use#' + - + path: '../src/TestFramework/Coverage/CoverageChecker.php' + message: '#Function ini_get is unsafe to use#' level: max paths: - ../src diff --git a/devTools/phpstan-tests.neon b/devTools/phpstan-tests.neon index a5a2ae67f..1a1ffef74 100644 --- a/devTools/phpstan-tests.neon +++ b/devTools/phpstan-tests.neon @@ -26,6 +26,9 @@ parameters: message: "#^Variable method call on Infection\\\\Tests\\\\FileSystem\\\\Finder\\\\MockVendor\\.$#" count: 1 path: ../tests/phpunit/FileSystem/Finder/TestFrameworkFinderTest.php + - + message: '#Function ini_get is unsafe to use#' + path: ../tests/phpunit/Process/OriginalPhpProcessTest.php level: 4 paths: - ../tests/phpunit diff --git a/devTools/xdebug-coverage.ini b/devTools/xdebug-coverage.ini deleted file mode 100644 index cea362e77..000000000 --- a/devTools/xdebug-coverage.ini +++ /dev/null @@ -1 +0,0 @@ -xdebug.mode=coverage diff --git a/devTools/xdebug.ini b/devTools/xdebug.ini new file mode 100644 index 000000000..9d4987171 --- /dev/null +++ b/devTools/xdebug.ini @@ -0,0 +1 @@ +xdebug.mode=off diff --git a/src/Process/OriginalPhpProcess.php b/src/Process/OriginalPhpProcess.php index b110563b2..09b62f855 100644 --- a/src/Process/OriginalPhpProcess.php +++ b/src/Process/OriginalPhpProcess.php @@ -35,7 +35,12 @@ namespace Infection\Process; +use function array_merge; use Composer\XdebugHandler\PhpConfig; +use Composer\XdebugHandler\XdebugHandler; +use function extension_loaded; +use function ini_get as ini_get_unsafe; +use const PHP_SAPI; use Symfony\Component\Process\Process; /** @@ -57,8 +62,37 @@ public function start(?callable $callback = null, ?array $env = null): void $phpConfig = new PhpConfig(); $phpConfig->useOriginal(); + if (self::shallExtendEnvironmentWithXdebugMode()) { + $env = array_merge($env ?? [], [ + 'XDEBUG_MODE' => 'coverage', + ]); + } + parent::start($callback, $env ?? []); $phpConfig->usePersistent(); } + + private static function shallExtendEnvironmentWithXdebugMode(): bool + { + // Most obvious cases when we don't want to add XDEBUG_MODE=coverage: + // - PCOV is loaded + // - PHPDBG is in use + if (extension_loaded('pcov') || PHP_SAPI === 'phpdbg') { + return false; + } + + // We also do not need to add XDEBUG_MODE for Xdebug <=3: + // it had coverage enabled at all times and it didn't have `xdebug.mode`. + if (ini_get_unsafe('xdebug.mode') === false) { + return false; + } + + // The last case: Xdebug 3+ running inactive. + return ini_get_unsafe('xdebug.mode') === ''; + + // Why going through all the trouble above? We don't want to enable + // Xdebug when there are more compelling choices. In the end the user is + // still in control: they can provide XDEBUG_MODE=coverage on their own. + } } diff --git a/src/TestFramework/Coverage/CoverageChecker.php b/src/TestFramework/Coverage/CoverageChecker.php index b2fb2cf62..7266d9bca 100644 --- a/src/TestFramework/Coverage/CoverageChecker.php +++ b/src/TestFramework/Coverage/CoverageChecker.php @@ -42,6 +42,7 @@ use Infection\FileSystem\Locator\FileNotFound; use Infection\TestFramework\Coverage\JUnit\JUnitReportLocator; use Infection\TestFramework\Coverage\XmlReport\IndexXmlCoverageLocator; +use function ini_get as ini_get_unsafe; use const PHP_EOL; use const PHP_SAPI; use function Safe\preg_match; @@ -175,6 +176,7 @@ private function hasCoverageGeneratorEnabled(): bool || XdebugHandler::isXdebugActive() || extension_loaded('pcov') || XdebugHandler::getSkippedVersion() !== '' + || ini_get_unsafe('xdebug.mode') !== false || $this->isXdebugIncludedInInitialTestPhpOptions() || $this->isPcovIncludedInInitialTestPhpOptions(); } diff --git a/tests/e2e/Exec_Path/run_tests.bash b/tests/e2e/Exec_Path/run_tests.bash index 6d3ad8355..9524dc1d8 100755 --- a/tests/e2e/Exec_Path/run_tests.bash +++ b/tests/e2e/Exec_Path/run_tests.bash @@ -35,6 +35,7 @@ if [ "$DRIVER" = "phpdbg" ] then PATH=$PATH:bin phpdbg -qrr vendor/bin/phpunit --coverage-xml=coverage/coverage-xml --log-junit=coverage/junit.xml else + export XDEBUG_MODE=coverage PATH=$PATH:bin php vendor/bin/phpunit --coverage-xml=coverage/coverage-xml --log-junit=coverage/junit.xml fi diff --git a/tests/e2e/Provide_Existing_Coverage/run_tests.bash b/tests/e2e/Provide_Existing_Coverage/run_tests.bash index 44679065f..37394ee54 100755 --- a/tests/e2e/Provide_Existing_Coverage/run_tests.bash +++ b/tests/e2e/Provide_Existing_Coverage/run_tests.bash @@ -9,6 +9,7 @@ if [ "$DRIVER" = "phpdbg" ] then phpdbg -qrr $PHPUNIT else + export XDEBUG_MODE=coverage php $PHPUNIT fi diff --git a/tests/e2e/Skip_Initial_Tests/run_tests.bash b/tests/e2e/Skip_Initial_Tests/run_tests.bash index 035817778..f86032e24 100755 --- a/tests/e2e/Skip_Initial_Tests/run_tests.bash +++ b/tests/e2e/Skip_Initial_Tests/run_tests.bash @@ -9,6 +9,7 @@ if [ "$DRIVER" = "phpdbg" ] then phpdbg -qrr $PHPUNIT else + export XDEBUG_MODE=coverage php $PHPUNIT fi diff --git a/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php b/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php index fac494948..62b91da45 100644 --- a/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php +++ b/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php @@ -64,7 +64,6 @@ use Infection\Mutant\DetectionStatus; use Infection\Mutation\MutationAttributeKeys; use Infection\Mutator\NodeMutationGenerator; -use Infection\Process\OriginalPhpProcess; use Infection\Process\Runner\IndexedProcessBearer; use Infection\Process\ShellCommandLineExecutor; use Infection\TestFramework\AdapterInstaller; @@ -100,7 +99,6 @@ final class ProjectCodeProvider RunCommand::class, Application::class, ProgressFormatter::class, - OriginalPhpProcess::class, ComposerExecutableFinder::class, StrykerCurlClient::class, MutationGeneratingConsoleLoggerSubscriber::class, diff --git a/tests/phpunit/Console/E2ETest.php b/tests/phpunit/Console/E2ETest.php index 71179698d..393e5ac46 100644 --- a/tests/phpunit/Console/E2ETest.php +++ b/tests/phpunit/Console/E2ETest.php @@ -145,7 +145,7 @@ public function test_it_runs_on_itself(): void } $output = $this->runInfection(self::EXPECT_SUCCESS, [ - '--test-framework-options="--exclude-group=' . self::EXCLUDED_GROUP . '"', + '--test-framework-options=--exclude-group=' . self::EXCLUDED_GROUP, ]); $this->assertMatchesRegularExpression('/\d+ mutations were generated/', $output); @@ -331,6 +331,10 @@ private function runInfection(int $expectedExitCode, array $argvExtra = []): str $this->markTestSkipped("Infection from within PHPUnit won't run without Xdebug or PHPDBG"); } + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test can be unstable on Windows'); + } + /* * @see https://github.com/sebastianbergmann/php-code-coverage/blob/7743bbcfff2a907e9ee4a25be13d0f8ec5e73800/src/Driver/PHPDBG.php#L24 */ diff --git a/tests/phpunit/Process/OriginalPhpProcessTest.php b/tests/phpunit/Process/OriginalPhpProcessTest.php new file mode 100644 index 000000000..6a7077e5c --- /dev/null +++ b/tests/phpunit/Process/OriginalPhpProcessTest.php @@ -0,0 +1,83 @@ +assertInstanceOf(Process::class, $process); + } + + public function test_it_takes_command_line(): void + { + $process = new OriginalPhpProcess(['foo']); + $this->assertStringContainsString('foo', $process->getCommandLine()); + } + + /** + * @group integration + */ + public function test_it_injects_xdebug_env_vars(): void + { + $process = new OriginalPhpProcess(['env']); + $process->run(null, ['TESTING' => 'test']); + + if ( + !extension_loaded('pcov') && + PHP_SAPI !== 'phpdbg' && + ( + ini_get_unsafe('xdebug.mode') === false || + ini_get_unsafe('xdebug.mode') === '' + ) + ) { + $this->assertStringContainsString('XDEBUG_MODE=coverage', $process->getOutput()); + } else { + $this->assertStringNotContainsString('XDEBUG_MODE=coverage', $process->getOutput()); + } + + $this->assertStringContainsString('TESTING=test', $process->getOutput()); + } +}