Skip to content

Commit bf3d4a4

Browse files
nikophilsebastianbergmann
authored andcommitted
#[RequiresPhpunitExtension] attribute
1 parent 93961d3 commit bf3d4a4

13 files changed

+437
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\Framework\Attributes;
11+
12+
use Attribute;
13+
use PHPUnit\Runner\Extension\Extension;
14+
15+
/**
16+
* @immutable
17+
*
18+
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
19+
*/
20+
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
21+
final readonly class RequiresPhpunitExtension
22+
{
23+
/**
24+
* @var class-string<Extension>
25+
*/
26+
private string $extensionClasses;
27+
28+
/**
29+
* @param class-string<Extension> $extensionClas
30+
*/
31+
public function __construct(string $extensionClas)
32+
{
33+
$this->extensionClasses = $extensionClas;
34+
}
35+
36+
/**
37+
* @return class-string<Extension>
38+
*/
39+
public function extensionClass(): string
40+
{
41+
return $this->extensionClasses;
42+
}
43+
}

src/Metadata/Api/Requirements.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
use const PHP_OS_FAMILY;
1414
use const PHP_VERSION;
1515
use function addcslashes;
16+
use function array_column;
1617
use function assert;
1718
use function extension_loaded;
1819
use function function_exists;
20+
use function in_array;
1921
use function ini_get;
2022
use function method_exists;
2123
use function phpversion;
@@ -29,8 +31,10 @@
2931
use PHPUnit\Metadata\RequiresPhp;
3032
use PHPUnit\Metadata\RequiresPhpExtension;
3133
use PHPUnit\Metadata\RequiresPhpunit;
34+
use PHPUnit\Metadata\RequiresPhpunitExtension;
3235
use PHPUnit\Metadata\RequiresSetting;
3336
use PHPUnit\Runner\Version;
37+
use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry;
3438

3539
/**
3640
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
@@ -86,6 +90,21 @@ public function requirementsNotSatisfiedFor(string $className, string $methodNam
8690
}
8791
}
8892

93+
if ($metadata->isRequiresPhpunitExtension()) {
94+
assert($metadata instanceof RequiresPhpunitExtension);
95+
96+
$configuration = ConfigurationRegistry::get();
97+
98+
$extensionBootstrappers = array_column($configuration->extensionBootstrappers(), 'className');
99+
100+
if ($configuration->noExtensions() || !in_array($metadata->extensionClass(), $extensionBootstrappers, true)) {
101+
$notSatisfied[] = sprintf(
102+
'PHPUnit extension "%s" is required.',
103+
$metadata->extensionClass(),
104+
);
105+
}
106+
}
107+
89108
if ($metadata->isRequiresOperatingSystemFamily()) {
90109
assert($metadata instanceof RequiresOperatingSystemFamily);
91110

src/Metadata/Metadata.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace PHPUnit\Metadata;
1111

1212
use PHPUnit\Metadata\Version\Requirement;
13+
use PHPUnit\Runner\Extension\Extension;
1314

1415
/**
1516
* @immutable
@@ -390,6 +391,22 @@ public static function requiresPhpunitOnMethod(Requirement $versionRequirement):
390391
return new RequiresPhpunit(self::METHOD_LEVEL, $versionRequirement);
391392
}
392393

394+
/**
395+
* @param class-string<Extension> $extensionClass
396+
*/
397+
public static function requiresPhpunitExtensionOnClass(string $extensionClass): RequiresPhpunitExtension
398+
{
399+
return new RequiresPhpunitExtension(self::CLASS_LEVEL, $extensionClass);
400+
}
401+
402+
/**
403+
* @param class-string<Extension> $extensionClass
404+
*/
405+
public static function requiresPhpunitExtensionOnMethod(string $extensionClass): RequiresPhpunitExtension
406+
{
407+
return new RequiresPhpunitExtension(self::METHOD_LEVEL, $extensionClass);
408+
}
409+
393410
/**
394411
* @param non-empty-string $setting
395412
* @param non-empty-string $value
@@ -831,6 +848,14 @@ public function isRequiresPhpunit(): bool
831848
return false;
832849
}
833850

851+
/**
852+
* @phpstan-assert-if-true RequiresPhpunitExtension $this
853+
*/
854+
public function isRequiresPhpunitExtension(): bool
855+
{
856+
return false;
857+
}
858+
834859
/**
835860
* @phpstan-assert-if-true RequiresSetting $this
836861
*/

src/Metadata/MetadataCollection.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,16 @@ public function isRequiresPhpunit(): self
483483
);
484484
}
485485

486+
public function isRequiresPhpunitExtension(): self
487+
{
488+
return new self(
489+
...array_filter(
490+
$this->metadata,
491+
static fn (Metadata $metadata): bool => $metadata->isRequiresPhpunitExtension(),
492+
),
493+
);
494+
}
495+
486496
public function isRequiresSetting(): self
487497
{
488498
return new self(

src/Metadata/Parser/AttributeParser.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
use PHPUnit\Framework\Attributes\RequiresPhp;
5959
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
6060
use PHPUnit\Framework\Attributes\RequiresPhpunit;
61+
use PHPUnit\Framework\Attributes\RequiresPhpunitExtension;
6162
use PHPUnit\Framework\Attributes\RequiresSetting;
6263
use PHPUnit\Framework\Attributes\RunClassInSeparateProcess;
6364
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
@@ -288,6 +289,15 @@ public function forClass(string $className): MetadataCollection
288289

289290
break;
290291

292+
case RequiresPhpunitExtension::class:
293+
assert($attributeInstance instanceof RequiresPhpunitExtension);
294+
295+
$result[] = Metadata::requiresPhpunitExtensionOnClass(
296+
$attributeInstance->extensionClass(),
297+
);
298+
299+
break;
300+
291301
case RequiresSetting::class:
292302
assert($attributeInstance instanceof RequiresSetting);
293303

@@ -641,6 +651,15 @@ public function forMethod(string $className, string $methodName): MetadataCollec
641651

642652
break;
643653

654+
case RequiresPhpunitExtension::class:
655+
assert($attributeInstance instanceof RequiresPhpunitExtension);
656+
657+
$result[] = Metadata::requiresPhpunitExtensionOnMethod(
658+
$attributeInstance->extensionClass(),
659+
);
660+
661+
break;
662+
644663
case RequiresSetting::class:
645664
assert($attributeInstance instanceof RequiresSetting);
646665

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\Metadata;
11+
12+
use PHPUnit\Runner\Extension\Extension;
13+
14+
/**
15+
* @immutable
16+
*
17+
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
18+
*/
19+
final readonly class RequiresPhpunitExtension extends Metadata
20+
{
21+
/**
22+
* @var class-string<Extension>
23+
*/
24+
private string $extensionClass;
25+
26+
/**
27+
* @param class-string<Extension> $extensionClass
28+
*/
29+
public function __construct(int $level, string $extensionClass)
30+
{
31+
parent::__construct($level);
32+
33+
$this->extensionClass = $extensionClass;
34+
}
35+
36+
public function isRequiresPhpunitExtension(): true
37+
{
38+
return true;
39+
}
40+
41+
/**
42+
* @return class-string<Extension>
43+
*/
44+
public function extensionClass(): string
45+
{
46+
return $this->extensionClass;
47+
}
48+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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\TestFixture\Metadata\Attribute;
11+
12+
use PHPUnit\Framework\Attributes\RequiresPhpunitExtension;
13+
use PHPUnit\Framework\TestCase;
14+
15+
#[RequiresPhpunitExtension(SomeExtension::class)]
16+
final class RequiresPhpunitExtensionTest extends TestCase
17+
{
18+
#[RequiresPhpunitExtension(SomeExtension::class)]
19+
#[RequiresPhpunitExtension(SomeOtherExtension::class)]
20+
public function testOne(): void
21+
{
22+
}
23+
}

tests/_files/RequirementsTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPUnit\Framework\Attributes\RequiresPhp;
1717
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
1818
use PHPUnit\Framework\Attributes\RequiresPhpunit;
19+
use PHPUnit\Framework\Attributes\RequiresPhpunitExtension;
1920
use PHPUnit\Framework\Attributes\RequiresSetting;
2021
use PHPUnit\Framework\Attributes\TestDox;
2122
use PHPUnit\Framework\Attributes\Ticket;
@@ -363,4 +364,10 @@ public function testVersionConstraintRegexpIgnoresWhitespace(): void
363364
public function testSettingDisplayErrorsOn(): void
364365
{
365366
}
367+
368+
#[RequiresPhpunitExtension(SomeExtension::class)]
369+
#[RequiresPhpunitExtension(SomeOtherExtension::class)]
370+
public function testPHPUnitExtensionRequired(): void
371+
{
372+
}
366373
}

tests/unit/Metadata/Api/RequirementsTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ public static function missingRequirementsProvider(): array
123123
'PHP ^1.0 is required.',
124124
'PHPUnit ^2.0 is required.',
125125
]],
126+
['testPHPUnitExtensionRequired', [
127+
'PHPUnit extension "PHPUnit\TestFixture\SomeExtension" is required.',
128+
'PHPUnit extension "PHPUnit\TestFixture\SomeOtherExtension" is required.',
129+
]],
126130
];
127131
}
128132

tests/unit/Metadata/MetadataCollectionTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPUnit\Framework\TestCase;
1717
use PHPUnit\Metadata\Version\ComparisonRequirement;
1818
use PHPUnit\Util\VersionComparisonOperator;
19+
use stdClass;
1920

2021
#[CoversClass(MetadataCollection::class)]
2122
#[CoversClass(MetadataCollectionIterator::class)]
@@ -48,6 +49,7 @@
4849
#[UsesClass(RequiresPhp::class)]
4950
#[UsesClass(RequiresPhpExtension::class)]
5051
#[UsesClass(RequiresPhpunit::class)]
52+
#[UsesClass(RequiresPhpunitExtension::class)]
5153
#[UsesClass(RequiresSetting::class)]
5254
#[UsesClass(RunClassInSeparateProcess::class)]
5355
#[UsesClass(RunInSeparateProcess::class)]
@@ -404,6 +406,14 @@ public function test_Can_be_filtered_for_RequiresPhpunit(): void
404406
$this->assertTrue($collection->asArray()[0]->isRequiresPhpunit());
405407
}
406408

409+
public function test_Can_be_filtered_for_RequiresPhpunitExtension(): void
410+
{
411+
$collection = $this->collectionWithOneOfEach()->isRequiresPhpunitExtension();
412+
413+
$this->assertCount(1, $collection);
414+
$this->assertTrue($collection->asArray()[0]->isRequiresPhpunitExtension());
415+
}
416+
407417
public function test_Can_be_filtered_for_RequiresSetting(): void
408418
{
409419
$collection = $this->collectionWithOneOfEach()->isRequiresSetting();
@@ -563,6 +573,7 @@ private function collectionWithOneOfEach(): MetadataCollection
563573
new VersionComparisonOperator('>='),
564574
),
565575
),
576+
Metadata::requiresPhpunitExtensionOnClass(stdClass::class),
566577
Metadata::requiresSettingOnClass('foo', 'bar'),
567578
Metadata::runClassInSeparateProcess(),
568579
Metadata::runInSeparateProcess(),

0 commit comments

Comments
 (0)