Skip to content

Commit e2a78f9

Browse files
Closes #6119
1 parent 73fc38e commit e2a78f9

File tree

7 files changed

+139
-6
lines changed

7 files changed

+139
-6
lines changed

ChangeLog-10.5.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable changes of the PHPUnit 10.5 release series are documented in this fi
77
### Changed
88

99
* [#6117](https://github.com/sebastianbergmann/phpunit/issues/6117): Include source location information for issues triggered during test in `--debug` output
10+
* [#6119](https://github.com/sebastianbergmann/phpunit/issues/6119): Improve message for errors that occur while parsing attributes
1011

1112
## [10.5.44] - 2025-01-31
1213

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 const PHP_EOL;
13+
use function sprintf;
14+
use PHPUnit\Exception;
15+
use RuntimeException;
16+
17+
/**
18+
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
19+
*
20+
* @internal This class is not covered by the backward compatibility promise for PHPUnit
21+
*/
22+
final class InvalidAttributeException extends RuntimeException implements Exception
23+
{
24+
/**
25+
* @param non-empty-string $attributeName
26+
* @param non-empty-string $target
27+
* @param non-empty-string $file
28+
* @param positive-int $line
29+
* @param non-empty-string $message
30+
*/
31+
public function __construct(string $attributeName, string $target, string $file, int $line, string $message)
32+
{
33+
parent::__construct(
34+
sprintf(
35+
'Invalid attribute %s for %s in %s:%d%s%s',
36+
$attributeName,
37+
$target,
38+
$file,
39+
$line,
40+
PHP_EOL,
41+
$message,
42+
),
43+
);
44+
}
45+
}

src/Metadata/Parser/AttributeParser.php

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use function json_decode;
1616
use function method_exists;
1717
use function str_starts_with;
18+
use Error;
1819
use PHPUnit\Framework\Attributes\After;
1920
use PHPUnit\Framework\Attributes\AfterClass;
2021
use PHPUnit\Framework\Attributes\BackupGlobals;
@@ -68,6 +69,7 @@
6869
use PHPUnit\Framework\Attributes\UsesClass;
6970
use PHPUnit\Framework\Attributes\UsesFunction;
7071
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
72+
use PHPUnit\Metadata\InvalidAttributeException;
7173
use PHPUnit\Metadata\Metadata;
7274
use PHPUnit\Metadata\MetadataCollection;
7375
use PHPUnit\Metadata\Version\ConstraintRequirement;
@@ -88,9 +90,10 @@ public function forClass(string $className): MetadataCollection
8890
{
8991
assert(class_exists($className));
9092

91-
$result = [];
93+
$reflector = new ReflectionClass($className);
94+
$result = [];
9295

93-
foreach ((new ReflectionClass($className))->getAttributes() as $attribute) {
96+
foreach ($reflector->getAttributes() as $attribute) {
9497
if (!str_starts_with($attribute->getName(), 'PHPUnit\\Framework\\Attributes\\')) {
9598
continue;
9699
}
@@ -99,7 +102,17 @@ public function forClass(string $className): MetadataCollection
99102
continue;
100103
}
101104

102-
$attributeInstance = $attribute->newInstance();
105+
try {
106+
$attributeInstance = $attribute->newInstance();
107+
} catch (Error $e) {
108+
throw new InvalidAttributeException(
109+
$attribute->getName(),
110+
'class ' . $className,
111+
$reflector->getFileName(),
112+
$reflector->getStartLine(),
113+
$e->getMessage(),
114+
);
115+
}
103116

104117
switch ($attribute->getName()) {
105118
case BackupGlobals::class:
@@ -346,9 +359,10 @@ public function forMethod(string $className, string $methodName): MetadataCollec
346359
assert(class_exists($className));
347360
assert(method_exists($className, $methodName));
348361

349-
$result = [];
362+
$reflector = new ReflectionMethod($className, $methodName);
363+
$result = [];
350364

351-
foreach ((new ReflectionMethod($className, $methodName))->getAttributes() as $attribute) {
365+
foreach ($reflector->getAttributes() as $attribute) {
352366
if (!str_starts_with($attribute->getName(), 'PHPUnit\\Framework\\Attributes\\')) {
353367
continue;
354368
}
@@ -357,7 +371,17 @@ public function forMethod(string $className, string $methodName): MetadataCollec
357371
continue;
358372
}
359373

360-
$attributeInstance = $attribute->newInstance();
374+
try {
375+
$attributeInstance = $attribute->newInstance();
376+
} catch (Error $e) {
377+
throw new InvalidAttributeException(
378+
$attribute->getName(),
379+
'method ' . $className . '::' . $methodName . '()',
380+
$reflector->getFileName(),
381+
$reflector->getStartLine(),
382+
$e->getMessage(),
383+
);
384+
}
361385

362386
switch ($attribute->getName()) {
363387
case After::class:
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\Small;
13+
use PHPUnit\Framework\TestCase;
14+
15+
#[Small]
16+
#[Small]
17+
final class DuplicateSmallAttributeTest extends TestCase
18+
{
19+
public function testOne(): void
20+
{
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\Test;
13+
use PHPUnit\Framework\TestCase;
14+
15+
final class DuplicateTestAttributeTest extends TestCase
16+
{
17+
#[Test]
18+
#[Test]
19+
public function testOne(): void
20+
{
21+
}
22+
}

tests/unit/Metadata/Parser/AttributeParserTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
use PHPUnit\Framework\Attributes\UsesClass;
6363
use PHPUnit\Framework\Attributes\UsesFunction;
6464
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
65+
use PHPUnit\Metadata\InvalidAttributeException;
6566

6667
#[CoversClass(AttributeParser::class)]
6768
#[CoversClass(AfterClass::class)]
@@ -92,6 +93,7 @@
9293
#[CoversClass(IgnoreClassForCodeCoverage::class)]
9394
#[CoversClass(IgnoreFunctionForCodeCoverage::class)]
9495
#[CoversClass(IgnoreMethodForCodeCoverage::class)]
96+
#[CoversClass(InvalidAttributeException::class)]
9597
#[CoversClass(Large::class)]
9698
#[CoversClass(Medium::class)]
9799
#[CoversClass(PostCondition::class)]

tests/unit/Metadata/Parser/AttributeParserTestCase.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use PHPUnit\Metadata\DependsOnClass;
1616
use PHPUnit\Metadata\DependsOnMethod;
17+
use PHPUnit\Metadata\InvalidAttributeException;
1718
use PHPUnit\Metadata\RequiresPhp;
1819
use PHPUnit\Metadata\RequiresPhpExtension;
1920
use PHPUnit\Metadata\RequiresPhpunit;
@@ -26,6 +27,8 @@
2627
use PHPUnit\TestFixture\Metadata\Attribute\CoversTest;
2728
use PHPUnit\TestFixture\Metadata\Attribute\DependencyTest;
2829
use PHPUnit\TestFixture\Metadata\Attribute\DoesNotPerformAssertionsTest;
30+
use PHPUnit\TestFixture\Metadata\Attribute\DuplicateSmallAttributeTest;
31+
use PHPUnit\TestFixture\Metadata\Attribute\DuplicateTestAttributeTest;
2932
use PHPUnit\TestFixture\Metadata\Attribute\Example;
3033
use PHPUnit\TestFixture\Metadata\Attribute\GroupTest;
3134
use PHPUnit\TestFixture\Metadata\Attribute\IgnoreCodeCoverageTest;
@@ -934,5 +937,19 @@ public function test_ignores_attributes_in_PHPUnit_namespace_that_do_not_exist()
934937
$this->assertTrue($metadata->isEmpty());
935938
}
936939

940+
public function test_handles_ReflectionException_raised_when_instantiating_attribute_on_class(): void
941+
{
942+
$this->expectException(InvalidAttributeException::class);
943+
944+
$this->parser()->forClass(DuplicateSmallAttributeTest::class);
945+
}
946+
947+
public function test_handles_ReflectionException_raised_when_instantiating_attribute_on_method(): void
948+
{
949+
$this->expectException(InvalidAttributeException::class);
950+
951+
$this->parser()->forMethod(DuplicateTestAttributeTest::class, 'testOne');
952+
}
953+
937954
abstract protected function parser(): Parser;
938955
}

0 commit comments

Comments
 (0)