Skip to content

Commit f7c6ad1

Browse files
authored
feat: distinguish required and supported groups (#73)
1 parent b5fa1c2 commit f7c6ad1

File tree

4 files changed

+116
-51
lines changed

4 files changed

+116
-51
lines changed

src/TestCheck/EveryTestHasGroup.php

Lines changed: 68 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,36 @@
55
namespace Cdn77\TestUtils\TestCheck;
66

77
use Cdn77\EntityFqnExtractor\ClassExtractor;
8+
use InvalidArgumentException;
89
use PHPUnit\Framework\Attributes\Group;
910
use PHPUnit\Framework\TestCase;
1011
use ReflectionClass;
1112

12-
use function Safe\preg_match;
13+
use function array_intersect;
14+
use function array_map;
15+
use function in_array;
16+
use function Safe\preg_match_all;
1317
use function sprintf;
1418

1519
final class EveryTestHasGroup implements TestCheck
1620
{
1721
/**
1822
* @param iterable<string> $filePathNames
19-
* @param list<string> $allowedGroups
23+
* @param non-empty-list<string>|null $requiredGroups
24+
* @param list<string> $supportedGroups
2025
*/
21-
public function __construct(private iterable $filePathNames, private array $allowedGroups)
22-
{
26+
public function __construct(
27+
private iterable $filePathNames,
28+
private array|null $requiredGroups = null,
29+
private array|null $supportedGroups = null,
30+
) {
31+
if (
32+
$requiredGroups !== null
33+
&& $supportedGroups !== null
34+
&& array_intersect($requiredGroups, $supportedGroups) !== []
35+
) {
36+
throw new InvalidArgumentException('Required groups must not be in supported groups');
37+
}
2338
}
2439

2540
public function run(TestCase $testCaseContext): void
@@ -31,48 +46,63 @@ public function run(TestCase $testCaseContext): void
3146
}
3247

3348
$groupAttributes = $classReflection->getAttributes(Group::class);
34-
foreach ($groupAttributes as $groupAttribute) {
49+
if ($groupAttributes !== []) {
50+
$groups = array_map(
51+
static fn ($groupAttribute) => $groupAttribute->getArguments()[0],
52+
$groupAttributes,
53+
);
54+
} else {
55+
$docComment = $classReflection->getDocComment();
56+
if ($docComment === false) {
57+
$testCaseContext::fail(sprintf('Test "%s" is missing phpdoc comment', $classReflection->getName()));
58+
}
59+
60+
if (preg_match_all('~\* @group +(?<group>\w+)(\n| \*/)~', $docComment, $matches) === 0) {
61+
$testCaseContext::fail(
62+
sprintf('Test "%s" is missing @group annotation', $classReflection->getName()),
63+
);
64+
}
65+
66+
$groups = $matches['group'];
67+
}
68+
69+
$hasRequiredGroup = false;
70+
foreach ($groups as $group) {
71+
if (
72+
$this->requiredGroups !== null
73+
&& in_array($group, $this->requiredGroups, true)
74+
) {
75+
$hasRequiredGroup = true;
76+
77+
continue;
78+
}
79+
80+
if ($this->supportedGroups === null) {
81+
continue;
82+
}
83+
3584
$testCaseContext::assertContains(
36-
$groupAttribute->getArguments()[0],
37-
$this->allowedGroups,
85+
$group,
86+
$this->supportedGroups,
3887
sprintf(
39-
'Test "%s" has invalid @group annotation "%s"',
88+
'Test "%s" has invalid Group attribute "%s"',
4089
$classReflection->getName(),
41-
$groupAttribute->getArguments()[0],
90+
$group,
4291
),
4392
);
4493
}
4594

46-
if ($groupAttributes !== []) {
47-
continue;
95+
if ($this->requiredGroups !== null) {
96+
$testCaseContext::assertTrue(
97+
$hasRequiredGroup,
98+
sprintf(
99+
'Test "%s" does not have required Group attribute',
100+
$classReflection->getName(),
101+
),
102+
);
103+
} else {
104+
$testCaseContext::assertTrue(true);
48105
}
49-
50-
$this->validateDocComment($testCaseContext, $classReflection);
51-
}
52-
}
53-
54-
/** @param ReflectionClass<object> $reflectionClass */
55-
private function validateDocComment(TestCase $testCaseContext, ReflectionClass $reflectionClass): void
56-
{
57-
$docComment = $reflectionClass->getDocComment();
58-
if ($docComment === false) {
59-
$testCaseContext::fail(sprintf('Test "%s" is missing phpdoc comment', $reflectionClass->getName()));
60106
}
61-
62-
if (preg_match('~\* @group +(?<group>\w+)(\n| \*/)~', $docComment, $matches) !== 1) {
63-
$testCaseContext::fail(
64-
sprintf('Test "%s" is missing @group annotation', $reflectionClass->getName()),
65-
);
66-
}
67-
68-
$testCaseContext::assertContains(
69-
$matches['group'],
70-
$this->allowedGroups,
71-
sprintf(
72-
'Test "%s" has invalid @group annotation "%s"',
73-
$reflectionClass->getName(),
74-
$matches['group'],
75-
),
76-
);
77107
}
78108
}

tests/TestCheck/EveryTestHasGroupTest.php

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,47 @@
1111

1212
final class EveryTestHasGroupTest extends BaseTestCase
1313
{
14-
public function testSuccess(): void
15-
{
16-
$check = new EveryTestHasGroup([__DIR__ . '/Fixtures/WithGroup.php'], ['unit']);
14+
/**
15+
* @param non-empty-list<string>|null $requiredGroups
16+
* @param list<string>|null $supportedGroups
17+
*
18+
* @dataProvider providerSuccess
19+
*/
20+
public function testSuccess(
21+
string $filePath,
22+
array|null $requiredGroups,
23+
array|null $supportedGroups,
24+
): void {
25+
$check = new EveryTestHasGroup([__DIR__ . '/Fixtures/' . $filePath], $requiredGroups, $supportedGroups);
1726
$check->run($this);
1827
}
1928

20-
/** @dataProvider providerFail */
21-
public function testFail(string $filePath): void
29+
/** @return Generator<string, array{string, non-empty-list<string>|null, list<string>|null}> */
30+
public static function providerSuccess(): Generator
2231
{
32+
yield 'required, any supported' => ['WithGroups.php', ['a'], null];
33+
yield 'required, supported' => ['WithGroups.php', ['a'], ['b']];
34+
yield 'no required, supported' => ['WithGroups.php', null, ['a', 'b']];
35+
yield 'no required, any supported' => ['WithGroups.php', null, null];
36+
yield 'required, any supported - A' => ['WithGroupsAnnotations.php', ['a'], null];
37+
yield 'required, supported - A' => ['WithGroupsAnnotations.php', ['a'], ['b']];
38+
yield 'no required, supported - A' => ['WithGroupsAnnotations.php', null, ['a', 'b']];
39+
yield 'no required, any supported - A' => ['WithGroupsAnnotations.php', null, null];
40+
}
41+
42+
/**
43+
* @param non-empty-list<string>|null $requiredGroups
44+
* @param list<string>|null $supportedGroups
45+
*
46+
* @dataProvider providerFail
47+
*/
48+
public function testFail(
49+
string $filePath,
50+
array|null $requiredGroups,
51+
array|null $supportedGroups,
52+
): void {
2353
try {
24-
$check = new EveryTestHasGroup([__DIR__ . '/Fixtures/' . $filePath], ['unit']);
54+
$check = new EveryTestHasGroup([__DIR__ . '/Fixtures/' . $filePath], $requiredGroups, $supportedGroups);
2555
$check->run($this);
2656
} catch (AssertionFailedError) {
2757
return;
@@ -30,10 +60,11 @@ public function testFail(string $filePath): void
3060
self::fail('Unexpected check outcome');
3161
}
3262

33-
/** @return Generator<list<string>> */
63+
/** @return Generator<string, array{string, non-empty-list<string>|null, list<string>|null}> */
3464
public static function providerFail(): Generator
3565
{
36-
yield ['WithoutGroup.php'];
37-
yield ['WithUnlistedGroup.php'];
66+
yield 'has unsupported group, required' => ['WithGroups.php', null, []];
67+
yield 'has no group, required' => ['WithoutGroup.php', ['a'], null];
68+
yield 'has unsupported group, required - A' => ['WithGroupsAnnotations.php', null, []];
3869
}
3970
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
use PHPUnit\Framework\Attributes\Group;
88

9-
#[Group('unit')]
10-
final class WithGroup
9+
#[Group('a')]
10+
#[Group('b')]
11+
final class WithGroups
1112
{
1213
}

tests/TestCheck/Fixtures/WithUnlistedGroup.php renamed to tests/TestCheck/Fixtures/WithGroupsAnnotations.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
use PHPUnit\Framework\Attributes\Group;
88

9-
#[Group('Eheu, raptus advena!')]
10-
final class WithUnlistedGroup
9+
/**
10+
* @group a
11+
* @group b
12+
*/
13+
final class WithGroupsAnnotations
1114
{
1215
}

0 commit comments

Comments
 (0)