Skip to content

Commit

Permalink
Implement #[CoversMethod] attribute for #5175
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianbergmann committed Feb 26, 2024
1 parent 88af11b commit fa8db04
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 0 deletions.
57 changes: 57 additions & 0 deletions src/Framework/Attributes/CoversMethod.php
@@ -0,0 +1,57 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Attributes;

use Attribute;

/**
* @psalm-immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final readonly class CoversMethod
{
/**
* @psalm-var class-string
*/
private string $className;

/**
* @psalm-var non-empty-string
*/
private string $methodName;

/**
* @psalm-param class-string $className
* @psalm-param non-empty-string $methodName
*/
public function __construct(string $className, $methodName)
{
$this->className = $className;
$this->methodName = $methodName;
}

/**
* @psalm-return class-string
*/
public function className(): string
{
return $this->className;
}

/**
* @psalm-return non-empty-string
*/
public function methodName(): string
{
return $this->methodName;
}
}
75 changes: 75 additions & 0 deletions src/Metadata/CoversMethod.php
@@ -0,0 +1,75 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Metadata;

/**
* @psalm-immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*/
final readonly class CoversMethod extends Metadata
{
/**
* @psalm-var class-string
*/
private string $className;

/**
* @psalm-var non-empty-string
*/
private string $methodName;

/**
* @psalm-param 0|1 $level
* @psalm-param class-string $className
* @psalm-param non-empty-string $methodName
*/
protected function __construct(int $level, string $className, string $methodName)
{
parent::__construct($level);

$this->className = $className;
$this->methodName = $methodName;
}

/**
* @psalm-assert-if-true CoversMethod $this
*/
public function isCoversMethod(): bool
{
return true;
}

/**
* @psalm-return class-string
*/
public function className(): string
{
return $this->className;
}

/**
* @psalm-return non-empty-string
*/
public function methodName(): string
{
return $this->methodName;
}

/**
* @psalm-return non-empty-string
*
* @internal This method is not covered by the backward compatibility promise for PHPUnit
*/
public function asStringForCodeUnitMapper(): string
{
return $this->className . '::' . $this->methodName;
}
}
17 changes: 17 additions & 0 deletions src/Metadata/Metadata.php
Expand Up @@ -74,6 +74,15 @@ public static function coversClass(string $className): CoversClass
return new CoversClass(self::CLASS_LEVEL, $className);
}

/**
* @psalm-param class-string $className
* @psalm-param non-empty-string $methodName
*/
public static function coversMethod(string $className, string $methodName): CoversMethod
{
return new CoversMethod(self::CLASS_LEVEL, $className, $methodName);
}

/**
* @psalm-param non-empty-string $functionName
*/
Expand Down Expand Up @@ -560,6 +569,14 @@ public function isCoversFunction(): bool
return false;
}

/**
* @psalm-assert-if-true CoversMethod $this
*/
public function isCoversMethod(): bool
{
return false;
}

/**
* @psalm-assert-if-true CoversNothing $this
*/
Expand Down
10 changes: 10 additions & 0 deletions src/Metadata/MetadataCollection.php
Expand Up @@ -200,6 +200,16 @@ public function isCoversFunction(): self
);
}

public function isCoversMethod(): self
{
return new self(
...array_filter(
$this->metadata,
static fn (Metadata $metadata): bool => $metadata->isCoversMethod(),
),
);
}

public function isExcludeGlobalVariableFromBackup(): self
{
return new self(
Expand Down
11 changes: 11 additions & 0 deletions src/Metadata/Parser/AttributeParser.php
Expand Up @@ -25,6 +25,7 @@
use PHPUnit\Framework\Attributes\BeforeClass;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversFunction;
use PHPUnit\Framework\Attributes\CoversMethod;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\DataProviderExternal;
Expand Down Expand Up @@ -123,6 +124,16 @@ public function forClass(string $className): MetadataCollection

break;

case CoversMethod::class:
assert($attributeInstance instanceof CoversMethod);

$result[] = Metadata::coversMethod(
$attributeInstance->className(),
$attributeInstance->methodName(),
);

break;

case CoversNothing::class:
$result[] = Metadata::coversNothingOnClass();

Expand Down
2 changes: 2 additions & 0 deletions tests/_files/Metadata/Attribute/tests/CoversTest.php
Expand Up @@ -11,10 +11,12 @@

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversFunction;
use PHPUnit\Framework\Attributes\CoversMethod;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\TestCase;

#[CoversClass(Example::class)]
#[CoversMethod(Example::class, 'method')]
#[CoversFunction('f')]
#[CoversNothing]
final class CoversTest extends TestCase
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/Metadata/MetadataCollectionTest.php
Expand Up @@ -29,6 +29,7 @@
#[UsesClass(CoversClass::class)]
#[UsesClass(CoversDefaultClass::class)]
#[UsesClass(CoversFunction::class)]
#[UsesClass(CoversMethod::class)]
#[UsesClass(CoversNothing::class)]
#[UsesClass(DataProvider::class)]
#[UsesClass(DependsOnClass::class)]
Expand Down Expand Up @@ -210,6 +211,14 @@ public function test_Can_be_filtered_for_CoversFunction(): void
$this->assertTrue($collection->asArray()[0]->isCoversFunction());
}

public function test_Can_be_filtered_for_CoversMethod(): void
{
$collection = $this->collectionWithOneOfEach()->isCoversMethod();

$this->assertCount(1, $collection);
$this->assertTrue($collection->asArray()[0]->isCoversMethod());
}

public function test_Can_be_filtered_for_CoversNothing(): void
{
$collection = $this->collectionWithOneOfEach()->isCoversNothing();
Expand Down Expand Up @@ -489,6 +498,7 @@ private function collectionWithOneOfEach(): MetadataCollection
Metadata::coversClass(''),
Metadata::coversDefaultClass(''),
Metadata::coversFunction(''),
Metadata::coversMethod('', ''),
Metadata::coversNothingOnClass(),
Metadata::dataProvider('', ''),
Metadata::dependsOnClass('', false, false),
Expand Down

0 comments on commit fa8db04

Please sign in to comment.