Skip to content

Commit

Permalink
Set XDEBUG_MODE for processes with coverage (#1518)
Browse files Browse the repository at this point in the history
* Set XDEBUG_MODE for processes with coverage

Fixes #1473
  • Loading branch information
sanmai committed Nov 10, 2021
1 parent 1892158 commit 2da5bcc
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion devTools/Dockerfile
Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions devTools/phpstan-src.neon
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions devTools/phpstan-tests.neon
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion devTools/xdebug-coverage.ini

This file was deleted.

1 change: 1 addition & 0 deletions devTools/xdebug.ini
@@ -0,0 +1 @@
xdebug.mode=off
34 changes: 34 additions & 0 deletions src/Process/OriginalPhpProcess.php
Expand Up @@ -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;

/**
Expand All @@ -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.
}
}
2 changes: 2 additions & 0 deletions src/TestFramework/Coverage/CoverageChecker.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/Exec_Path/run_tests.bash
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions tests/e2e/Provide_Existing_Coverage/run_tests.bash
Expand Up @@ -9,6 +9,7 @@ if [ "$DRIVER" = "phpdbg" ]
then
phpdbg -qrr $PHPUNIT
else
export XDEBUG_MODE=coverage
php $PHPUNIT
fi

Expand Down
1 change: 1 addition & 0 deletions tests/e2e/Skip_Initial_Tests/run_tests.bash
Expand Up @@ -9,6 +9,7 @@ if [ "$DRIVER" = "phpdbg" ]
then
phpdbg -qrr $PHPUNIT
else
export XDEBUG_MODE=coverage
php $PHPUNIT
fi

Expand Down
2 changes: 0 additions & 2 deletions tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php
Expand Up @@ -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;
Expand Down Expand Up @@ -100,7 +99,6 @@ final class ProjectCodeProvider
RunCommand::class,
Application::class,
ProgressFormatter::class,
OriginalPhpProcess::class,
ComposerExecutableFinder::class,
StrykerCurlClient::class,
MutationGeneratingConsoleLoggerSubscriber::class,
Expand Down
6 changes: 5 additions & 1 deletion tests/phpunit/Console/E2ETest.php
Expand Up @@ -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);
Expand Down Expand Up @@ -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
*/
Expand Down
83 changes: 83 additions & 0 deletions tests/phpunit/Process/OriginalPhpProcessTest.php
@@ -0,0 +1,83 @@
<?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\Tests\Process;

use function extension_loaded;
use Infection\Process\OriginalPhpProcess;
use function ini_get as ini_get_unsafe;
use const PHP_SAPI;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Process;

final class OriginalPhpProcessTest extends TestCase
{
public function test_it_extends_symfony_process(): void
{
$process = new OriginalPhpProcess([]);

$this->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());
}
}

0 comments on commit 2da5bcc

Please sign in to comment.