Skip to content

Commit

Permalink
Implement generation of XDebug filter script as described in #3272.
Browse files Browse the repository at this point in the history
This adds the new CLI option --dump-xdebug-filter which will generate a PHP
script that can be used to set a whitelist filter for XDebug's code coverage
collector in order to speed up test runs.

The whitelist is based on the filter configuration in PHPUnit's XML
configuration. If the configuration only contains includes for files and
directories without prefixes and suffixes other than '.php', the XDebug script
will contain the same whitelist items.

If, however, the filter configuration is more complex, the XDebug script will
contain the resolved list of files, which will have a negative impact on
performance.
  • Loading branch information
sebastianheuer authored and sebastianbergmann committed Sep 8, 2018
1 parent aa44032 commit df69c95
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 1 deletion.
7 changes: 7 additions & 0 deletions src/TextUI/Command.php
Expand Up @@ -140,6 +140,7 @@ class Command
'verbose' => null,
'version' => null,
'whitelist=' => null,
'dump-xdebug-filter=' => null,
];

/**
Expand Down Expand Up @@ -740,6 +741,11 @@ protected function handleArguments(array $argv): void

break;

case '--dump-xdebug-filter':
$this->arguments['xdebugFilterFile'] = $option[1];

break;

default:
$optionName = \str_replace('--', '', $option[0]);

Expand Down Expand Up @@ -1081,6 +1087,7 @@ protected function showHelp(): void
--whitelist <dir> Whitelist <dir> for code coverage analysis
--disable-coverage-ignore Disable annotations for ignoring code coverage
--no-coverage Ignore code coverage configuration
--dump-xdebug-filter <file> Generate script to set Xdebug code coverage filter
Logging Options:
Expand Down
14 changes: 13 additions & 1 deletion src/TextUI/TestRunner.php
Expand Up @@ -41,6 +41,7 @@
use PHPUnit\Util\TestDox\HtmlResultPrinter;
use PHPUnit\Util\TestDox\TextResultPrinter;
use PHPUnit\Util\TestDox\XmlResultPrinter;
use PHPUnit\Util\XDebugFilterScriptGenerator;
use ReflectionClass;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException;
Expand Down Expand Up @@ -455,7 +456,7 @@ public function doRun(Test $suite, array $arguments = [], bool $exit = true): Te
$codeCoverageReports = 0;
}

if ($codeCoverageReports > 0) {
if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) {
$codeCoverage = new CodeCoverage(
null,
$this->codeCoverageFilter
Expand Down Expand Up @@ -540,6 +541,17 @@ public function doRun(Test $suite, array $arguments = [], bool $exit = true): Te
}
}

if (isset($arguments['xdebugFilterFile'], $filterConfiguration)) {
$filterScriptGenerator = new XDebugFilterScriptGenerator();
$script = $filterScriptGenerator->generate(
$filterConfiguration['whitelist'],
$this->codeCoverageFilter->getWhitelist()
);
\file_put_contents($arguments['xdebugFilterFile'], $script);

exit(self::SUCCESS_EXIT);
}

if (!$this->codeCoverageFilter->hasWhitelist()) {
if (!$whitelistFromConfigurationFile && !$whitelistFromOption) {
$this->writeMessage('Error', 'No whitelist is configured, no code coverage will be generated.');
Expand Down
82 changes: 82 additions & 0 deletions src/Util/XDebugFilterScriptGenerator.php
@@ -0,0 +1,82 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;

class XDebugFilterScriptGenerator
{
public function generate(array $filterData, array $whitelistedFiles): string
{
$items = $this->getItems($filterData, $whitelistedFiles);

$files = \array_map(function ($item) {
return \sprintf(" '%s'", $item);
}, $items);
$files = \implode(",\n", $files);

return <<<EOF
<?php
if (!\\function_exists('xdebug_set_filter')) {
return;
}
xdebug_set_filter(
XDEBUG_FILTER_CODE_COVERAGE,
XDEBUG_PATH_WHITELIST,
[
$files
]
);
EOF;
}

private function getItems(array $filterData, array $whitelistedFiles): array
{
if ($this->canUseRawFilterData($filterData)) {
return $this->getItemsFromRawFilterData($filterData);
}

return $whitelistedFiles;
}

private function getItemsFromRawFilterData(array $filterData): array
{
$files = [];

if (isset($filterData['include']['directory'])) {
foreach ($filterData['include']['directory'] as $directory) {
$files[] = $directory['path'];
}
}

if (isset($filterData['include']['directory'])) {
foreach ($filterData['include']['file'] as $file) {
$files[] = $file;
}
}

return $files;
}

private function canUseRawFilterData(array $filterData): bool
{
if (\count($filterData['exclude']['directory']) > 0 || \count($filterData['exclude']['file'])) {
return false;
}

foreach ($filterData['include']['directory'] as $directory) {
if (($directory['suffix'] !== '' && $directory['suffix'] !== '.php') || $directory['prefix'] !== '') {
return false;
}
}

return true;
}
}
12 changes: 12 additions & 0 deletions tests/_files/configuration_whitelist.xml
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>

<phpunit>

<filter>
<whitelist>
<file>AbstractTest.php</file>
</whitelist>
</filter>

</phpunit>

30 changes: 30 additions & 0 deletions tests/end-to-end/dump-xdebug-filter.phpt
@@ -0,0 +1,30 @@
--TEST--
phpunit -c ../_files/configuration_whitelist.xml --dump-xdebug-filter 'php://stdout'
--SKIPIF--
<?php
if (!extension_loaded('xdebug')) {
print 'skip: xdebug not loaded';
}
--FILE--
<?php
$_SERVER['argv'][1] = '-c';
$_SERVER['argv'][2] = __DIR__ . '/../_files/configuration_whitelist.xml';
$_SERVER['argv'][3] = '--dump-xdebug-filter';
$_SERVER['argv'][4] = 'php://stdout';

require __DIR__ . '/../bootstrap.php';
PHPUnit\TextUI\Command::main();
--EXPECTF--
PHPUnit %s by Sebastian Bergmann and contributors.
<?php
if (!\function_exists('xdebug_set_filter')) {
return;
}

xdebug_set_filter(
XDEBUG_FILTER_CODE_COVERAGE,
XDEBUG_PATH_WHITELIST,
[
%s
]
);
1 change: 1 addition & 0 deletions tests/end-to-end/help.phpt
Expand Up @@ -24,6 +24,7 @@ Code Coverage Options:
--whitelist <dir> Whitelist <dir> for code coverage analysis
--disable-coverage-ignore Disable annotations for ignoring code coverage
--no-coverage Ignore code coverage configuration
--dump-xdebug-filter <file> Generate script to set Xdebug code coverage filter

Logging Options:

Expand Down
1 change: 1 addition & 0 deletions tests/end-to-end/help2.phpt
Expand Up @@ -25,6 +25,7 @@ Code Coverage Options:
--whitelist <dir> Whitelist <dir> for code coverage analysis
--disable-coverage-ignore Disable annotations for ignoring code coverage
--no-coverage Ignore code coverage configuration
--dump-xdebug-filter <file> Generate script to set Xdebug code coverage filter

Logging Options:

Expand Down
75 changes: 75 additions & 0 deletions tests/unit/Util/XDebugFilterScriptGeneratorTest.php
@@ -0,0 +1,75 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;

use PHPUnit\Framework\TestCase;

class XDebugFilterScriptGeneratorTest extends TestCase
{
/**
* @covers \PHPUnit\Util\XDebugFilterScriptGenerator::generate
*
* @dataProvider scriptGeneratorTestDataProvider
*/
public function testReturnsExpectedScript(array $filterConfiguration, array $resolvedWhitelist)
{
$writer = new XDebugFilterScriptGenerator();
$actual = $writer->generate($filterConfiguration, $resolvedWhitelist);

$this->assertStringEqualsFile(__DIR__ . '/_files/expectedXDebugFilterScript.txt', $actual);
}

public function scriptGeneratorTestDataProvider(): array
{
return [
[
[
'include' => [
'directory' => [
[
'path' => 'src/somePath',
'suffix' => '.php',
'prefix' => '',
],
],
'file' => [
'src/foo.php',
'src/bar.php',
],
],
'exclude' => [
'directory' => [],
'file' => [],
],
],
[],
__DIR__ . '/_files/expectedXDebugFilterScript.php',
],
[
[
'include' => [
'directory' => ['src/'],
'file' => ['src/foo.php'],
],
'exclude' => [
'directory' => [],
'file' => ['src/baz.php'],
],
],
[
'src/somePath',
'src/foo.php',
'src/bar.php',
],
__DIR__ . '/_files/expectedXDebugFilterScript.php',
],
];
}
}
14 changes: 14 additions & 0 deletions tests/unit/Util/_files/expectedXDebugFilterScript.txt
@@ -0,0 +1,14 @@
<?php
if (!\function_exists('xdebug_set_filter')) {
return;
}

xdebug_set_filter(
XDEBUG_FILTER_CODE_COVERAGE,
XDEBUG_PATH_WHITELIST,
[
'src/somePath',
'src/foo.php',
'src/bar.php'
]
);

0 comments on commit df69c95

Please sign in to comment.