Skip to content

Commit 9250db6

Browse files
Skip data providers whose method cannot match --filter
1 parent 53effd8 commit 9250db6

9 files changed

Lines changed: 364 additions & 2 deletions

src/Framework/TestBuilder.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use function array_merge;
1313
use function assert;
1414
use function get_parent_class;
15+
use function preg_match;
1516
use PHPUnit\Metadata\Api\DataProvider;
1617
use PHPUnit\Metadata\Api\Groups;
1718
use PHPUnit\Metadata\Api\ProvidedData;
@@ -23,6 +24,7 @@
2324
use PHPUnit\Metadata\Parser\Registry as MetadataRegistry;
2425
use PHPUnit\Metadata\PreserveGlobalState;
2526
use PHPUnit\Runner\ErrorHandler;
27+
use PHPUnit\Runner\Filter\MethodNameFilterCompiler;
2628
use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry;
2729
use ReflectionClass;
2830

@@ -48,7 +50,8 @@ public function build(ReflectionClass $theClass, string $methodName, array $grou
4850

4951
$data = null;
5052

51-
if ($this->requirementsSatisfied($className, $methodName)) {
53+
if ($this->requirementsSatisfied($className, $methodName) &&
54+
!$this->filterExcludesMethod($className, $methodName)) {
5255
try {
5356
ErrorHandler::instance()->enterTestCaseContext($className, $methodName);
5457

@@ -293,4 +296,31 @@ private function requirementsSatisfied(string $className, string $methodName): b
293296
{
294297
return (new Requirements)->requirementsNotSatisfiedFor($className, $methodName) === [];
295298
}
299+
300+
/**
301+
* @param class-string<TestCase> $className
302+
* @param non-empty-string $methodName
303+
*/
304+
private function filterExcludesMethod(string $className, string $methodName): bool
305+
{
306+
$configuration = ConfigurationRegistry::get();
307+
308+
if (!$configuration->hasFilter()) {
309+
return false;
310+
}
311+
312+
$regularExpression = MethodNameFilterCompiler::compile($configuration->filter());
313+
314+
if ($regularExpression === null) {
315+
return false;
316+
}
317+
318+
$result = @preg_match($regularExpression, $className . '::' . $methodName);
319+
320+
if ($result === false) {
321+
return false;
322+
}
323+
324+
return $result === 0;
325+
}
296326
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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\Runner\Filter;
11+
12+
use function preg_match;
13+
use function sprintf;
14+
use function substr;
15+
16+
/**
17+
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
18+
*
19+
* @internal This class is not covered by the backward compatibility promise for PHPUnit
20+
*/
21+
final readonly class MethodNameFilterCompiler
22+
{
23+
/**
24+
* Returns null when the filter does not constrain the method name.
25+
* Callers should treat null as "cannot prove a mismatch, keep the data provider".
26+
*
27+
* Mirrors the parsing in NameFilterIterator::prepareFilter(): the two must
28+
* stay in lockstep so the early skip never disagrees with the final filter.
29+
*
30+
* @return null|non-empty-string
31+
*/
32+
public static function compile(string $filter): ?string
33+
{
34+
if (preg_match('/[a-zA-Z0-9]/', substr($filter, 0, 1)) !== 1 &&
35+
@preg_match($filter, '') !== false) {
36+
return null;
37+
}
38+
39+
$methodNamePortion = $filter;
40+
41+
if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches) === 1) {
42+
$methodNamePortion = $matches[1];
43+
} elseif (preg_match('/^(.*?)#(.+)$/', $filter, $matches) === 1) {
44+
$methodNamePortion = $matches[1];
45+
} elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches) === 1) {
46+
$methodNamePortion = $matches[1];
47+
}
48+
49+
if ($methodNamePortion === '') {
50+
return null;
51+
}
52+
53+
return sprintf('{%s}i', $methodNamePortion);
54+
}
55+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php declare(strict_types=1);
2+
3+
/*
4+
* This file is part of PHPUnit.
5+
*
6+
* (c) Sebastian Bergmann <sebastian@phpunit.de>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
namespace PHPUnit\TestFixture;
12+
13+
use PHPUnit\Framework\Attributes\DataProvider;
14+
use PHPUnit\Framework\TestCase;
15+
16+
final class DataProviderSkipWhenFilteredTest extends TestCase
17+
{
18+
public static function providerForA(): array
19+
{
20+
return [[1], [2]];
21+
}
22+
23+
public static function providerForB(): array
24+
{
25+
return [[1], [2]];
26+
}
27+
28+
#[DataProvider('providerForA')]
29+
public function testA(int $i): void
30+
{
31+
$this->assertGreaterThan(0, $i);
32+
}
33+
34+
#[DataProvider('providerForB')]
35+
public function testB(int $i): void
36+
{
37+
$this->assertGreaterThan(0, $i);
38+
}
39+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
phpunit --filter '#1' keeps all data providers running (no method-name portion to prune by)
3+
--FILE--
4+
<?php declare(strict_types=1);
5+
$_SERVER['argv'][] = '--do-not-cache-result';
6+
$_SERVER['argv'][] = '--no-configuration';
7+
$_SERVER['argv'][] = '--debug';
8+
$_SERVER['argv'][] = '--filter';
9+
$_SERVER['argv'][] = '#1';
10+
$_SERVER['argv'][] = __DIR__ . '/../../_files/DataProviderSkipWhenFilteredTest.php';
11+
12+
require_once __DIR__ . '/../../../bootstrap.php';
13+
14+
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
15+
--EXPECTF--
16+
PHPUnit Started (PHPUnit %s using %s)
17+
Test Runner Configured
18+
Event Facade Sealed
19+
Data Provider Method Called (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForA for test method PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA)
20+
Data Provider Method Finished for PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA:
21+
- PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForA
22+
Data Provider Method Called (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForB for test method PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB)
23+
Data Provider Method Finished for PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB:
24+
- PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForB
25+
Test Suite Loaded (4 tests)
26+
Test Runner Started
27+
Test Suite Sorted
28+
Test Suite Filtered (2 tests)
29+
Test Runner Execution Started (2 tests)
30+
Test Suite Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest, 2 tests)
31+
Test Suite Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA, 1 test)
32+
Test Preparation Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
33+
Test Prepared (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
34+
Test Passed (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
35+
Test Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
36+
Test Suite Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA, 1 test)
37+
Test Suite Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB, 1 test)
38+
Test Preparation Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB#1)
39+
Test Prepared (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB#1)
40+
Test Passed (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB#1)
41+
Test Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB#1)
42+
Test Suite Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB, 1 test)
43+
Test Suite Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest, 2 tests)
44+
Test Runner Execution Finished
45+
Test Runner Finished
46+
PHPUnit Finished (Shell Exit Code: 0)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
phpunit --filter '/.../' (user-supplied regex) keeps all data providers running
3+
--FILE--
4+
<?php declare(strict_types=1);
5+
$_SERVER['argv'][] = '--do-not-cache-result';
6+
$_SERVER['argv'][] = '--no-configuration';
7+
$_SERVER['argv'][] = '--debug';
8+
$_SERVER['argv'][] = '--filter';
9+
$_SERVER['argv'][] = '/testA/';
10+
$_SERVER['argv'][] = __DIR__ . '/../../_files/DataProviderSkipWhenFilteredTest.php';
11+
12+
require_once __DIR__ . '/../../../bootstrap.php';
13+
14+
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
15+
--EXPECTF--
16+
PHPUnit Started (PHPUnit %s using %s)
17+
Test Runner Configured
18+
Event Facade Sealed
19+
Data Provider Method Called (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForA for test method PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA)
20+
Data Provider Method Finished for PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA:
21+
- PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForA
22+
Data Provider Method Called (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForB for test method PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB)
23+
Data Provider Method Finished for PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB:
24+
- PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForB
25+
Test Suite Loaded (4 tests)
26+
Test Runner Started
27+
Test Suite Sorted
28+
Test Suite Filtered (2 tests)
29+
Test Runner Execution Started (2 tests)
30+
Test Suite Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest, 2 tests)
31+
Test Suite Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA, 2 tests)
32+
Test Preparation Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#0)
33+
Test Prepared (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#0)
34+
Test Passed (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#0)
35+
Test Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#0)
36+
Test Preparation Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
37+
Test Prepared (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
38+
Test Passed (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
39+
Test Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
40+
Test Suite Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA, 2 tests)
41+
Test Suite Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest, 2 tests)
42+
Test Runner Execution Finished
43+
Test Runner Finished
44+
PHPUnit Finished (Shell Exit Code: 0)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
phpunit --filter 'foo}' (filter that compiles to an invalid regex) keeps all data providers running
3+
--FILE--
4+
<?php declare(strict_types=1);
5+
$_SERVER['argv'][] = '--do-not-cache-result';
6+
$_SERVER['argv'][] = '--no-configuration';
7+
$_SERVER['argv'][] = '--debug';
8+
$_SERVER['argv'][] = '--filter';
9+
$_SERVER['argv'][] = 'foo}';
10+
$_SERVER['argv'][] = __DIR__ . '/../../_files/DataProviderSkipWhenFilteredTest.php';
11+
12+
require_once __DIR__ . '/../../../bootstrap.php';
13+
14+
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
15+
--EXPECTF--
16+
PHPUnit Started (PHPUnit %s using %s)
17+
Test Runner Configured
18+
Event Facade Sealed
19+
Data Provider Method Called (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForA for test method PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA)
20+
Data Provider Method Finished for PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA:
21+
- PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForA
22+
Data Provider Method Called (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForB for test method PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB)
23+
Data Provider Method Finished for PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testB:
24+
- PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForB
25+
Test Suite Loaded (4 tests)
26+
Test Runner Started
27+
Test Suite Sorted
28+
Test Suite Filtered (0 tests)
29+
Test Runner Execution Started (0 tests)
30+
Test Runner Execution Finished
31+
Test Runner Finished
32+
PHPUnit Finished (Shell Exit Code: 1)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
phpunit --filter testA does not invoke data providers for non-matching methods
3+
--FILE--
4+
<?php declare(strict_types=1);
5+
$_SERVER['argv'][] = '--do-not-cache-result';
6+
$_SERVER['argv'][] = '--no-configuration';
7+
$_SERVER['argv'][] = '--debug';
8+
$_SERVER['argv'][] = '--filter';
9+
$_SERVER['argv'][] = 'testA';
10+
$_SERVER['argv'][] = __DIR__ . '/../../_files/DataProviderSkipWhenFilteredTest.php';
11+
12+
require_once __DIR__ . '/../../../bootstrap.php';
13+
14+
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
15+
--EXPECTF--
16+
PHPUnit Started (PHPUnit %s using %s)
17+
Test Runner Configured
18+
Event Facade Sealed
19+
Data Provider Method Called (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForA for test method PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA)
20+
Data Provider Method Finished for PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA:
21+
- PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::providerForA
22+
Test Suite Loaded (3 tests)
23+
Test Runner Started
24+
Test Suite Sorted
25+
Test Suite Filtered (2 tests)
26+
Test Runner Execution Started (2 tests)
27+
Test Suite Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest, 2 tests)
28+
Test Suite Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA, 2 tests)
29+
Test Preparation Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#0)
30+
Test Prepared (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#0)
31+
Test Passed (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#0)
32+
Test Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#0)
33+
Test Preparation Started (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
34+
Test Prepared (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
35+
Test Passed (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
36+
Test Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA#1)
37+
Test Suite Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest::testA, 2 tests)
38+
Test Suite Finished (PHPUnit\TestFixture\DataProviderSkipWhenFilteredTest, 2 tests)
39+
Test Runner Execution Finished
40+
Test Runner Finished
41+
PHPUnit Finished (Shell Exit Code: 0)

tests/end-to-end/regression/2137-filter.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ $_SERVER['argv'][] = '--do-not-cache-result';
66
$_SERVER['argv'][] = '--no-configuration';
77
$_SERVER['argv'][] = __DIR__ . '/2137/Issue2137Test.php';
88
$_SERVER['argv'][] = '--filter';
9-
$_SERVER['argv'][] = 'BrandService';
9+
$_SERVER['argv'][] = 'test';
1010

1111
require_once __DIR__ . '/../../bootstrap.php';
1212
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);

0 commit comments

Comments
 (0)