Skip to content

Commit df69c95

Browse files
sebastianheuersebastianbergmann
authored andcommitted
Implement generation of XDebug filter script as described in #3272.
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.
1 parent aa44032 commit df69c95

File tree

9 files changed

+235
-1
lines changed

9 files changed

+235
-1
lines changed

src/TextUI/Command.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ class Command
140140
'verbose' => null,
141141
'version' => null,
142142
'whitelist=' => null,
143+
'dump-xdebug-filter=' => null,
143144
];
144145

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

741742
break;
742743

744+
case '--dump-xdebug-filter':
745+
$this->arguments['xdebugFilterFile'] = $option[1];
746+
747+
break;
748+
743749
default:
744750
$optionName = \str_replace('--', '', $option[0]);
745751

@@ -1081,6 +1087,7 @@ protected function showHelp(): void
10811087
--whitelist <dir> Whitelist <dir> for code coverage analysis
10821088
--disable-coverage-ignore Disable annotations for ignoring code coverage
10831089
--no-coverage Ignore code coverage configuration
1090+
--dump-xdebug-filter <file> Generate script to set Xdebug code coverage filter
10841091
10851092
Logging Options:
10861093

src/TextUI/TestRunner.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
use PHPUnit\Util\TestDox\HtmlResultPrinter;
4242
use PHPUnit\Util\TestDox\TextResultPrinter;
4343
use PHPUnit\Util\TestDox\XmlResultPrinter;
44+
use PHPUnit\Util\XDebugFilterScriptGenerator;
4445
use ReflectionClass;
4546
use SebastianBergmann\CodeCoverage\CodeCoverage;
4647
use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException;
@@ -455,7 +456,7 @@ public function doRun(Test $suite, array $arguments = [], bool $exit = true): Te
455456
$codeCoverageReports = 0;
456457
}
457458

458-
if ($codeCoverageReports > 0) {
459+
if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) {
459460
$codeCoverage = new CodeCoverage(
460461
null,
461462
$this->codeCoverageFilter
@@ -540,6 +541,17 @@ public function doRun(Test $suite, array $arguments = [], bool $exit = true): Te
540541
}
541542
}
542543

544+
if (isset($arguments['xdebugFilterFile'], $filterConfiguration)) {
545+
$filterScriptGenerator = new XDebugFilterScriptGenerator();
546+
$script = $filterScriptGenerator->generate(
547+
$filterConfiguration['whitelist'],
548+
$this->codeCoverageFilter->getWhitelist()
549+
);
550+
\file_put_contents($arguments['xdebugFilterFile'], $script);
551+
552+
exit(self::SUCCESS_EXIT);
553+
}
554+
543555
if (!$this->codeCoverageFilter->hasWhitelist()) {
544556
if (!$whitelistFromConfigurationFile && !$whitelistFromOption) {
545557
$this->writeMessage('Error', 'No whitelist is configured, no code coverage will be generated.');
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of PHPUnit.
4+
*
5+
* (c) Sebastian Bergmann <sebastian@phpunit.de>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace PHPUnit\Util;
11+
12+
class XDebugFilterScriptGenerator
13+
{
14+
public function generate(array $filterData, array $whitelistedFiles): string
15+
{
16+
$items = $this->getItems($filterData, $whitelistedFiles);
17+
18+
$files = \array_map(function ($item) {
19+
return \sprintf(" '%s'", $item);
20+
}, $items);
21+
$files = \implode(",\n", $files);
22+
23+
return <<<EOF
24+
<?php
25+
if (!\\function_exists('xdebug_set_filter')) {
26+
return;
27+
}
28+
29+
xdebug_set_filter(
30+
XDEBUG_FILTER_CODE_COVERAGE,
31+
XDEBUG_PATH_WHITELIST,
32+
[
33+
$files
34+
]
35+
);
36+
37+
EOF;
38+
}
39+
40+
private function getItems(array $filterData, array $whitelistedFiles): array
41+
{
42+
if ($this->canUseRawFilterData($filterData)) {
43+
return $this->getItemsFromRawFilterData($filterData);
44+
}
45+
46+
return $whitelistedFiles;
47+
}
48+
49+
private function getItemsFromRawFilterData(array $filterData): array
50+
{
51+
$files = [];
52+
53+
if (isset($filterData['include']['directory'])) {
54+
foreach ($filterData['include']['directory'] as $directory) {
55+
$files[] = $directory['path'];
56+
}
57+
}
58+
59+
if (isset($filterData['include']['directory'])) {
60+
foreach ($filterData['include']['file'] as $file) {
61+
$files[] = $file;
62+
}
63+
}
64+
65+
return $files;
66+
}
67+
68+
private function canUseRawFilterData(array $filterData): bool
69+
{
70+
if (\count($filterData['exclude']['directory']) > 0 || \count($filterData['exclude']['file'])) {
71+
return false;
72+
}
73+
74+
foreach ($filterData['include']['directory'] as $directory) {
75+
if (($directory['suffix'] !== '' && $directory['suffix'] !== '.php') || $directory['prefix'] !== '') {
76+
return false;
77+
}
78+
}
79+
80+
return true;
81+
}
82+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
3+
<phpunit>
4+
5+
<filter>
6+
<whitelist>
7+
<file>AbstractTest.php</file>
8+
</whitelist>
9+
</filter>
10+
11+
</phpunit>
12+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
phpunit -c ../_files/configuration_whitelist.xml --dump-xdebug-filter 'php://stdout'
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('xdebug')) {
6+
print 'skip: xdebug not loaded';
7+
}
8+
--FILE--
9+
<?php
10+
$_SERVER['argv'][1] = '-c';
11+
$_SERVER['argv'][2] = __DIR__ . '/../_files/configuration_whitelist.xml';
12+
$_SERVER['argv'][3] = '--dump-xdebug-filter';
13+
$_SERVER['argv'][4] = 'php://stdout';
14+
15+
require __DIR__ . '/../bootstrap.php';
16+
PHPUnit\TextUI\Command::main();
17+
--EXPECTF--
18+
PHPUnit %s by Sebastian Bergmann and contributors.
19+
<?php
20+
if (!\function_exists('xdebug_set_filter')) {
21+
return;
22+
}
23+
24+
xdebug_set_filter(
25+
XDEBUG_FILTER_CODE_COVERAGE,
26+
XDEBUG_PATH_WHITELIST,
27+
[
28+
%s
29+
]
30+
);

tests/end-to-end/help.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Code Coverage Options:
2424
--whitelist <dir> Whitelist <dir> for code coverage analysis
2525
--disable-coverage-ignore Disable annotations for ignoring code coverage
2626
--no-coverage Ignore code coverage configuration
27+
--dump-xdebug-filter <file> Generate script to set Xdebug code coverage filter
2728

2829
Logging Options:
2930

tests/end-to-end/help2.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Code Coverage Options:
2525
--whitelist <dir> Whitelist <dir> for code coverage analysis
2626
--disable-coverage-ignore Disable annotations for ignoring code coverage
2727
--no-coverage Ignore code coverage configuration
28+
--dump-xdebug-filter <file> Generate script to set Xdebug code coverage filter
2829

2930
Logging Options:
3031

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of PHPUnit.
4+
*
5+
* (c) Sebastian Bergmann <sebastian@phpunit.de>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace PHPUnit\Util;
11+
12+
use PHPUnit\Framework\TestCase;
13+
14+
class XDebugFilterScriptGeneratorTest extends TestCase
15+
{
16+
/**
17+
* @covers \PHPUnit\Util\XDebugFilterScriptGenerator::generate
18+
*
19+
* @dataProvider scriptGeneratorTestDataProvider
20+
*/
21+
public function testReturnsExpectedScript(array $filterConfiguration, array $resolvedWhitelist)
22+
{
23+
$writer = new XDebugFilterScriptGenerator();
24+
$actual = $writer->generate($filterConfiguration, $resolvedWhitelist);
25+
26+
$this->assertStringEqualsFile(__DIR__ . '/_files/expectedXDebugFilterScript.txt', $actual);
27+
}
28+
29+
public function scriptGeneratorTestDataProvider(): array
30+
{
31+
return [
32+
[
33+
[
34+
'include' => [
35+
'directory' => [
36+
[
37+
'path' => 'src/somePath',
38+
'suffix' => '.php',
39+
'prefix' => '',
40+
],
41+
],
42+
'file' => [
43+
'src/foo.php',
44+
'src/bar.php',
45+
],
46+
],
47+
'exclude' => [
48+
'directory' => [],
49+
'file' => [],
50+
],
51+
],
52+
[],
53+
__DIR__ . '/_files/expectedXDebugFilterScript.php',
54+
],
55+
[
56+
[
57+
'include' => [
58+
'directory' => ['src/'],
59+
'file' => ['src/foo.php'],
60+
],
61+
'exclude' => [
62+
'directory' => [],
63+
'file' => ['src/baz.php'],
64+
],
65+
],
66+
[
67+
'src/somePath',
68+
'src/foo.php',
69+
'src/bar.php',
70+
],
71+
__DIR__ . '/_files/expectedXDebugFilterScript.php',
72+
],
73+
];
74+
}
75+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
if (!\function_exists('xdebug_set_filter')) {
3+
return;
4+
}
5+
6+
xdebug_set_filter(
7+
XDEBUG_FILTER_CODE_COVERAGE,
8+
XDEBUG_PATH_WHITELIST,
9+
[
10+
'src/somePath',
11+
'src/foo.php',
12+
'src/bar.php'
13+
]
14+
);

0 commit comments

Comments
 (0)