From 9dcb6f7843f5c98c6c9c0c61b50b703167aae656 Mon Sep 17 00:00:00 2001 From: David Parry Date: Sat, 14 Mar 2026 14:58:53 +1100 Subject: [PATCH] feat: Enhance group filtering logic and update help documentation to support AND logic for group combinations --- src/Runner/Filter/GroupFilterIterator.php | 40 +++++++++++++-- .../_files/groups-with-and-logic/phpunit.xml | 10 ++++ .../tests/MultiGroupTest.php | 49 +++++++++++++++++++ .../cli/group/exclude-group-and-argument.phpt | 40 +++++++++++++++ .../group/exclude-group-and-or-argument.phpt | 38 ++++++++++++++ .../exclude-group-and-three-argument.phpt | 44 +++++++++++++++++ .../cli/group/group-and-argument.phpt | 36 ++++++++++++++ .../cli/group/group-and-or-argument.phpt | 42 ++++++++++++++++ .../cli/group/group-and-three-argument.phpt | 32 ++++++++++++ 9 files changed, 328 insertions(+), 3 deletions(-) create mode 100644 tests/end-to-end/_files/groups-with-and-logic/phpunit.xml create mode 100644 tests/end-to-end/_files/groups-with-and-logic/tests/MultiGroupTest.php create mode 100644 tests/end-to-end/cli/group/exclude-group-and-argument.phpt create mode 100644 tests/end-to-end/cli/group/exclude-group-and-or-argument.phpt create mode 100644 tests/end-to-end/cli/group/exclude-group-and-three-argument.phpt create mode 100644 tests/end-to-end/cli/group/group-and-argument.phpt create mode 100644 tests/end-to-end/cli/group/group-and-or-argument.phpt create mode 100644 tests/end-to-end/cli/group/group-and-three-argument.phpt diff --git a/src/Runner/Filter/GroupFilterIterator.php b/src/Runner/Filter/GroupFilterIterator.php index da45211d752..8648803e830 100644 --- a/src/Runner/Filter/GroupFilterIterator.php +++ b/src/Runner/Filter/GroupFilterIterator.php @@ -9,9 +9,13 @@ */ namespace PHPUnit\Runner\Filter; +use function array_intersect; use function array_merge; use function array_push; +use function array_values; +use function explode; use function in_array; +use function str_contains; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestSuite; @@ -39,16 +43,46 @@ public function __construct(RecursiveIterator $iterator, array $groups, TestSuit { parent::__construct($iterator); - $groupTests = []; + $groupTests = []; + $suiteGroups = $suite->groups(); - foreach ($suite->groups() as $group => $tests) { - if (in_array($group, $groups, true)) { + $simpleGroups = []; + $compoundGroups = []; + + foreach ($groups as $group) { + if (str_contains($group, '+')) { + $compoundGroups[] = explode('+', $group); + } else { + $simpleGroups[] = $group; + } + } + + foreach ($suiteGroups as $group => $tests) { + if (in_array($group, $simpleGroups, true)) { $groupTests = array_merge($groupTests, $tests); array_push($groupTests, ...$groupTests); } } + foreach ($compoundGroups as $constituents) { + $testsInAllGroups = null; + + foreach ($constituents as $part) { + $testsInGroup = $suiteGroups[$part] ?? []; + + if ($testsInAllGroups === null) { + $testsInAllGroups = $testsInGroup; + } else { + $testsInAllGroups = array_values(array_intersect($testsInAllGroups, $testsInGroup)); + } + } + + if ($testsInAllGroups !== null && $testsInAllGroups !== []) { + $groupTests = array_merge($groupTests, $testsInAllGroups); + } + } + $this->groupTests = $groupTests; } diff --git a/tests/end-to-end/_files/groups-with-and-logic/phpunit.xml b/tests/end-to-end/_files/groups-with-and-logic/phpunit.xml new file mode 100644 index 00000000000..8b2df97afce --- /dev/null +++ b/tests/end-to-end/_files/groups-with-and-logic/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/_files/groups-with-and-logic/tests/MultiGroupTest.php b/tests/end-to-end/_files/groups-with-and-logic/tests/MultiGroupTest.php new file mode 100644 index 00000000000..0684f79b83c --- /dev/null +++ b/tests/end-to-end/_files/groups-with-and-logic/tests/MultiGroupTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\GroupsWithAndLogic; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; + +final class MultiGroupTest extends TestCase +{ + #[Group('X')] + public function testX(): void + { + $this->assertTrue(true); + } + + #[Group('Y')] + public function testY(): void + { + $this->assertTrue(true); + } + + #[Group('X')] + #[Group('Y')] + public function testXY(): void + { + $this->assertTrue(true); + } + + #[Group('X')] + #[Group('Y')] + #[Group('Z')] + public function testXYZ(): void + { + $this->assertTrue(true); + } + + #[Group('Z')] + public function testZ(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/cli/group/exclude-group-and-argument.phpt b/tests/end-to-end/cli/group/exclude-group-and-argument.phpt new file mode 100644 index 00000000000..5ae3c35f9fd --- /dev/null +++ b/tests/end-to-end/cli/group/exclude-group-and-argument.phpt @@ -0,0 +1,40 @@ +--TEST-- +phpunit --exclude-group X+Y tests/MultiGroupTest.php (AND exclude group filter) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (3 tests) +Test Runner Execution Started (3 tests) +Test Suite Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Suite Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/exclude-group-and-or-argument.phpt b/tests/end-to-end/cli/group/exclude-group-and-or-argument.phpt new file mode 100644 index 00000000000..be2ac4b7f7f --- /dev/null +++ b/tests/end-to-end/cli/group/exclude-group-and-or-argument.phpt @@ -0,0 +1,38 @@ +--TEST-- +phpunit --exclude-group X+Y --exclude-group Z tests/MultiGroupTest.php (AND combined with OR exclude group filter) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (2 tests) +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Suite Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/exclude-group-and-three-argument.phpt b/tests/end-to-end/cli/group/exclude-group-and-three-argument.phpt new file mode 100644 index 00000000000..3be9f93cd59 --- /dev/null +++ b/tests/end-to-end/cli/group/exclude-group-and-three-argument.phpt @@ -0,0 +1,44 @@ +--TEST-- +phpunit --exclude-group X+Y+Z tests/MultiGroupTest.php (AND exclude group filter with three groups) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (4 tests) +Test Runner Execution Started (4 tests) +Test Suite Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 4 tests) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testX) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testY) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Suite Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 4 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/group-and-argument.phpt b/tests/end-to-end/cli/group/group-and-argument.phpt new file mode 100644 index 00000000000..8cd1f4c1eae --- /dev/null +++ b/tests/end-to-end/cli/group/group-and-argument.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit --group X+Y tests/MultiGroupTest.php (AND group filter) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (2 tests) +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Suite Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/group-and-or-argument.phpt b/tests/end-to-end/cli/group/group-and-or-argument.phpt new file mode 100644 index 00000000000..685b3f536bc --- /dev/null +++ b/tests/end-to-end/cli/group/group-and-or-argument.phpt @@ -0,0 +1,42 @@ +--TEST-- +phpunit --group X+Y --group Z tests/MultiGroupTest.php (AND combined with OR) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (3 tests) +Test Runner Execution Started (3 tests) +Test Suite Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXY) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testZ) +Test Suite Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/group-and-three-argument.phpt b/tests/end-to-end/cli/group/group-and-three-argument.phpt new file mode 100644 index 00000000000..4ccf4067262 --- /dev/null +++ b/tests/end-to-end/cli/group/group-and-three-argument.phpt @@ -0,0 +1,32 @@ +--TEST-- +phpunit --group X+Y+Z tests/MultiGroupTest.php (AND group filter with three groups) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Prepared (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Passed (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest::testXYZ) +Test Suite Finished (PHPUnit\TestFixture\GroupsWithAndLogic\MultiGroupTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0)