Skip to content

Commit 84abc8a

Browse files
committed
[PhpUnitBridge] Enable configuring mock ns with attributes
1 parent 2d32721 commit 84abc8a

16 files changed

+536
-10
lines changed

Attribute/DnsSensitive.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\PhpUnit\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
15+
final class DnsSensitive
16+
{
17+
public function __construct(
18+
public readonly ?string $class = null,
19+
) {
20+
}
21+
}

Attribute/TimeSensitive.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\PhpUnit\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
15+
final class TimeSensitive
16+
{
17+
public function __construct(
18+
public readonly ?string $class = null,
19+
) {
20+
}
21+
}

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.3
5+
---
6+
7+
* Enable configuring clock and DNS mock namespaces with attributes
8+
49
7.2
510
---
611

Extension/DisableClockMockSubscriber.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515
use PHPUnit\Event\Test\Finished;
1616
use PHPUnit\Event\Test\FinishedSubscriber;
1717
use PHPUnit\Metadata\Group;
18+
use Symfony\Bridge\PhpUnit\Attribute\TimeSensitive;
1819
use Symfony\Bridge\PhpUnit\ClockMock;
20+
use Symfony\Bridge\PhpUnit\Metadata\AttributeReader;
1921

2022
/**
2123
* @internal
2224
*/
2325
class DisableClockMockSubscriber implements FinishedSubscriber
2426
{
27+
public function __construct(
28+
private AttributeReader $reader,
29+
) {
30+
}
31+
2532
public function notify(Finished $event): void
2633
{
2734
$test = $event->test();
@@ -33,7 +40,12 @@ public function notify(Finished $event): void
3340
foreach ($test->metadata() as $metadata) {
3441
if ($metadata instanceof Group && 'time-sensitive' === $metadata->groupName()) {
3542
ClockMock::withClockMock(false);
43+
break;
3644
}
3745
}
46+
47+
if ($this->reader->forClassAndMethod($test->className(), $test->methodName(), TimeSensitive::class)) {
48+
ClockMock::withClockMock(false);
49+
}
3850
}
3951
}

Extension/DisableDnsMockSubscriber.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515
use PHPUnit\Event\Test\Finished;
1616
use PHPUnit\Event\Test\FinishedSubscriber;
1717
use PHPUnit\Metadata\Group;
18+
use Symfony\Bridge\PhpUnit\Attribute\DnsSensitive;
1819
use Symfony\Bridge\PhpUnit\DnsMock;
20+
use Symfony\Bridge\PhpUnit\Metadata\AttributeReader;
1921

2022
/**
2123
* @internal
2224
*/
2325
class DisableDnsMockSubscriber implements FinishedSubscriber
2426
{
27+
public function __construct(
28+
private AttributeReader $reader,
29+
) {
30+
}
31+
2532
public function notify(Finished $event): void
2633
{
2734
$test = $event->test();
@@ -33,7 +40,12 @@ public function notify(Finished $event): void
3340
foreach ($test->metadata() as $metadata) {
3441
if ($metadata instanceof Group && 'dns-sensitive' === $metadata->groupName()) {
3542
DnsMock::withMockedHosts([]);
43+
break;
3644
}
3745
}
46+
47+
if ($this->reader->forClassAndMethod($test->className(), $test->methodName(), DnsSensitive::class)) {
48+
DnsMock::withMockedHosts([]);
49+
}
3850
}
3951
}

Extension/EnableClockMockSubscriber.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515
use PHPUnit\Event\Test\PreparationStarted;
1616
use PHPUnit\Event\Test\PreparationStartedSubscriber;
1717
use PHPUnit\Metadata\Group;
18+
use Symfony\Bridge\PhpUnit\Attribute\TimeSensitive;
1819
use Symfony\Bridge\PhpUnit\ClockMock;
20+
use Symfony\Bridge\PhpUnit\Metadata\AttributeReader;
1921

2022
/**
2123
* @internal
2224
*/
2325
class EnableClockMockSubscriber implements PreparationStartedSubscriber
2426
{
27+
public function __construct(
28+
private AttributeReader $reader,
29+
) {
30+
}
31+
2532
public function notify(PreparationStarted $event): void
2633
{
2734
$test = $event->test();
@@ -33,7 +40,12 @@ public function notify(PreparationStarted $event): void
3340
foreach ($test->metadata() as $metadata) {
3441
if ($metadata instanceof Group && 'time-sensitive' === $metadata->groupName()) {
3542
ClockMock::withClockMock(true);
43+
break;
3644
}
3745
}
46+
47+
if ($this->reader->forClassAndMethod($test->className(), $test->methodName(), TimeSensitive::class)) {
48+
ClockMock::withClockMock(true);
49+
}
3850
}
3951
}

Extension/RegisterClockMockSubscriber.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515
use PHPUnit\Event\TestSuite\Loaded;
1616
use PHPUnit\Event\TestSuite\LoadedSubscriber;
1717
use PHPUnit\Metadata\Group;
18+
use Symfony\Bridge\PhpUnit\Attribute\TimeSensitive;
1819
use Symfony\Bridge\PhpUnit\ClockMock;
20+
use Symfony\Bridge\PhpUnit\Metadata\AttributeReader;
1921

2022
/**
2123
* @internal
2224
*/
2325
class RegisterClockMockSubscriber implements LoadedSubscriber
2426
{
27+
public function __construct(
28+
private AttributeReader $reader,
29+
) {
30+
}
31+
2532
public function notify(Loaded $event): void
2633
{
2734
foreach ($event->testSuite()->tests() as $test) {
@@ -34,6 +41,10 @@ public function notify(Loaded $event): void
3441
ClockMock::register($test->className());
3542
}
3643
}
44+
45+
foreach ($this->reader->forClassAndMethod($test->className(), $test->methodName(), TimeSensitive::class) as $attribute) {
46+
ClockMock::register($attribute->class ?? $test->className());
47+
}
3748
}
3849
}
3950
}

Extension/RegisterDnsMockSubscriber.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515
use PHPUnit\Event\TestSuite\Loaded;
1616
use PHPUnit\Event\TestSuite\LoadedSubscriber;
1717
use PHPUnit\Metadata\Group;
18+
use Symfony\Bridge\PhpUnit\Attribute\DnsSensitive;
1819
use Symfony\Bridge\PhpUnit\DnsMock;
20+
use Symfony\Bridge\PhpUnit\Metadata\AttributeReader;
1921

2022
/**
2123
* @internal
2224
*/
2325
class RegisterDnsMockSubscriber implements LoadedSubscriber
2426
{
27+
public function __construct(
28+
private AttributeReader $reader,
29+
) {
30+
}
31+
2532
public function notify(Loaded $event): void
2633
{
2734
foreach ($event->testSuite()->tests() as $test) {
@@ -34,6 +41,10 @@ public function notify(Loaded $event): void
3441
DnsMock::register($test->className());
3542
}
3643
}
44+
45+
foreach ($this->reader->forClassAndMethod($test->className(), $test->methodName(), DnsSensitive::class) as $attribute) {
46+
DnsMock::register($attribute->class ?? $test->className());
47+
}
3748
}
3849
}
3950
}

Metadata/AttributeReader.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\PhpUnit\Metadata;
13+
14+
/**
15+
* @template T of object
16+
*/
17+
final class AttributeReader
18+
{
19+
/**
20+
* @var array<string, array<class-string<T>, list<T>>>
21+
*/
22+
private array $cache = [];
23+
24+
/**
25+
* @param class-string $className
26+
* @param class-string<T> $name
27+
*
28+
* @return list<T>
29+
*/
30+
public function forClass(string $className, string $name): array
31+
{
32+
$attributes = $this->cache[$className] ??= $this->readAttributes(new \ReflectionClass($className));
33+
34+
return $attributes[$name] ?? [];
35+
}
36+
37+
/**
38+
* @param class-string $className
39+
* @param class-string<T> $name
40+
*
41+
* @return list<T>
42+
*/
43+
public function forMethod(string $className, string $methodName, string $name): array
44+
{
45+
$attributes = $this->cache[$className.'::'.$methodName] ??= $this->readAttributes(new \ReflectionMethod($className, $methodName));
46+
47+
return $attributes[$name] ?? [];
48+
}
49+
50+
/**
51+
* @param class-string $className
52+
* @param class-string<T> $name
53+
*
54+
* @return list<T>
55+
*/
56+
public function forClassAndMethod(string $className, string $methodName, string $name): array
57+
{
58+
return [
59+
...$this->forClass($className, $name),
60+
...$this->forMethod($className, $methodName, $name),
61+
];
62+
}
63+
64+
private function readAttributes(\ReflectionClass|\ReflectionMethod $reflection): array
65+
{
66+
$attributeInstances = [];
67+
68+
foreach ($reflection->getAttributes() as $attribute) {
69+
if (!str_starts_with($name = $attribute->getName(), 'Symfony\\Bridge\\PhpUnit\\Attribute\\')) {
70+
continue;
71+
}
72+
73+
$attributeInstances[$name][] = $attribute->newInstance();
74+
}
75+
76+
return $attributeInstances;
77+
}
78+
}

SymfonyExtension.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Bridge\PhpUnit\Extension\EnableClockMockSubscriber;
2121
use Symfony\Bridge\PhpUnit\Extension\RegisterClockMockSubscriber;
2222
use Symfony\Bridge\PhpUnit\Extension\RegisterDnsMockSubscriber;
23+
use Symfony\Bridge\PhpUnit\Metadata\AttributeReader;
2324
use Symfony\Component\ErrorHandler\DebugClassLoader;
2425

2526
class SymfonyExtension implements Extension
@@ -30,23 +31,25 @@ public function bootstrap(Configuration $configuration, Facade $facade, Paramete
3031
DebugClassLoader::enable();
3132
}
3233

34+
$reader = new AttributeReader();
35+
3336
if ($parameters->has('clock-mock-namespaces')) {
3437
foreach (explode(',', $parameters->get('clock-mock-namespaces')) as $namespace) {
3538
ClockMock::register($namespace.'\DummyClass');
3639
}
3740
}
3841

39-
$facade->registerSubscriber(new RegisterClockMockSubscriber());
40-
$facade->registerSubscriber(new EnableClockMockSubscriber());
41-
$facade->registerSubscriber(new DisableClockMockSubscriber());
42+
$facade->registerSubscriber(new RegisterClockMockSubscriber($reader));
43+
$facade->registerSubscriber(new EnableClockMockSubscriber($reader));
44+
$facade->registerSubscriber(new DisableClockMockSubscriber($reader));
4245

4346
if ($parameters->has('dns-mock-namespaces')) {
4447
foreach (explode(',', $parameters->get('dns-mock-namespaces')) as $namespace) {
4548
DnsMock::register($namespace.'\DummyClass');
4649
}
4750
}
4851

49-
$facade->registerSubscriber(new RegisterDnsMockSubscriber());
50-
$facade->registerSubscriber(new DisableDnsMockSubscriber());
52+
$facade->registerSubscriber(new RegisterDnsMockSubscriber($reader));
53+
$facade->registerSubscriber(new DisableDnsMockSubscriber($reader));
5154
}
5255
}

Tests/Fixtures/symfonyextension/tests/bootstrap.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
});
2222

2323
require __DIR__.'/../../../../SymfonyExtension.php';
24+
require __DIR__.'/../../../../Attribute/DnsSensitive.php';
25+
require __DIR__.'/../../../../Attribute/TimeSensitive.php';
2426
require __DIR__.'/../../../../Extension/DisableClockMockSubscriber.php';
2527
require __DIR__.'/../../../../Extension/DisableDnsMockSubscriber.php';
2628
require __DIR__.'/../../../../Extension/EnableClockMockSubscriber.php';
2729
require __DIR__.'/../../../../Extension/RegisterClockMockSubscriber.php';
2830
require __DIR__.'/../../../../Extension/RegisterDnsMockSubscriber.php';
31+
require __DIR__.'/../../../../Metadata/AttributeReader.php';
2932

3033
if (file_exists(__DIR__.'/../../../../vendor/autoload.php')) {
3134
require __DIR__.'/../../../../vendor/autoload.php';

0 commit comments

Comments
 (0)