Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianbergmann committed Feb 27, 2024
1 parent 90fb197 commit 3efde16
Show file tree
Hide file tree
Showing 18 changed files with 683 additions and 12 deletions.
1 change: 1 addition & 0 deletions ChangeLog-11.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes of the PHPUnit 11.1 release series are documented in this fi

### Added

* [#5175](https://github.com/sebastianbergmann/phpunit/issues/5175): `#[CoversMethod]` and `#[UsesMethod]` attributes for more fine-grained code coverage targeting
* [#5696](https://github.com/sebastianbergmann/phpunit/pull/5696): `#[DisableReturnValueGenerationForTestDoubles]` attribute for disabling return value generation for test doubles created using `createMock()`, `createMockForIntersectionOfInterfaces()`, `createPartialMock()`, `createStub()`, and `createStubForIntersectionOfInterfaces()`

### Changed
Expand Down
57 changes: 57 additions & 0 deletions src/Framework/Attributes/CoversMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <[email protected]>
*
* 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;
}
}
57 changes: 57 additions & 0 deletions src/Framework/Attributes/UsesMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <[email protected]>
*
* 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 UsesMethod
{
/**
* @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;
}
}
16 changes: 9 additions & 7 deletions src/Metadata/Api/CodeCoverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
use PHPUnit\Metadata\CoversClass;
use PHPUnit\Metadata\CoversDefaultClass;
use PHPUnit\Metadata\CoversFunction;
use PHPUnit\Metadata\CoversMethod;
use PHPUnit\Metadata\Parser\Registry;
use PHPUnit\Metadata\Uses;
use PHPUnit\Metadata\UsesClass;
use PHPUnit\Metadata\UsesDefaultClass;
use PHPUnit\Metadata\UsesFunction;
use PHPUnit\Metadata\UsesMethod;
use SebastianBergmann\CodeUnit\CodeUnitCollection;
use SebastianBergmann\CodeUnit\Exception as CodeUnitException;
use SebastianBergmann\CodeUnit\InvalidCodeUnitException;
Expand Down Expand Up @@ -73,13 +75,13 @@ public function linesToBeCovered(string $className, string $methodName): array|f
$mapper = new Mapper;

foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) {
if (!$metadata->isCoversClass() && !$metadata->isCoversFunction() && !$metadata->isCovers()) {
if (!$metadata->isCoversClass() && !$metadata->isCoversMethod() && !$metadata->isCoversFunction() && !$metadata->isCovers()) {
continue;
}

assert($metadata instanceof CoversClass || $metadata instanceof CoversFunction || $metadata instanceof Covers);
assert($metadata instanceof CoversClass || $metadata instanceof CoversMethod || $metadata instanceof CoversFunction || $metadata instanceof Covers);

if ($metadata->isCoversClass() || $metadata->isCoversFunction()) {
if ($metadata->isCoversClass() || $metadata->isCoversMethod() || $metadata->isCoversFunction()) {
$codeUnits = $codeUnits->mergeWith($this->mapToCodeUnits($metadata));
} elseif ($metadata->isCovers()) {
assert($metadata instanceof Covers);
Expand Down Expand Up @@ -151,13 +153,13 @@ public function linesToBeUsed(string $className, string $methodName): array
$mapper = new Mapper;

foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) {
if (!$metadata->isUsesClass() && !$metadata->isUsesFunction() && !$metadata->isUses()) {
if (!$metadata->isUsesClass() && !$metadata->isUsesMethod() && !$metadata->isUsesFunction() && !$metadata->isUses()) {
continue;
}

assert($metadata instanceof UsesClass || $metadata instanceof UsesFunction || $metadata instanceof Uses);
assert($metadata instanceof UsesClass || $metadata instanceof UsesMethod || $metadata instanceof UsesFunction || $metadata instanceof Uses);

if ($metadata->isUsesClass() || $metadata->isUsesFunction()) {
if ($metadata->isUsesClass() || $metadata->isUsesMethod() || $metadata->isUsesFunction()) {
$codeUnits = $codeUnits->mergeWith($this->mapToCodeUnits($metadata));
} elseif ($metadata->isUses()) {
assert($metadata instanceof Uses);
Expand Down Expand Up @@ -215,7 +217,7 @@ public function shouldCodeCoverageBeCollectedFor(string $className, string $meth
/**
* @throws InvalidCoversTargetException
*/
private function mapToCodeUnits(CoversClass|CoversFunction|UsesClass|UsesFunction $metadata): CodeUnitCollection
private function mapToCodeUnits(CoversClass|CoversFunction|CoversMethod|UsesClass|UsesFunction|UsesMethod $metadata): CodeUnitCollection
{
$mapper = new Mapper;

Expand Down
75 changes: 75 additions & 0 deletions src/Metadata/CoversMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <[email protected]>
*
* 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;
}
}
34 changes: 34 additions & 0 deletions src/Metadata/Metadata.php
Original file line number Diff line number Diff line change
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 @@ -433,6 +442,15 @@ public static function usesFunction(string $functionName): UsesFunction
return new UsesFunction(self::CLASS_LEVEL, $functionName);
}

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

/**
* @psalm-param non-empty-string $target
*/
Expand Down Expand Up @@ -560,6 +578,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 Expand Up @@ -818,6 +844,14 @@ public function isUsesFunction(): bool
return false;
}

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

/**
* @psalm-assert-if-true WithoutErrorHandler $this
*/
Expand Down
20 changes: 20 additions & 0 deletions src/Metadata/MetadataCollection.php
Original file line number Diff line number Diff line change
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 Expand Up @@ -533,6 +543,16 @@ public function isUsesFunction(): self
);
}

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

public function isWithoutErrorHandler(): self
{
return new self(
Expand Down
Loading

0 comments on commit 3efde16

Please sign in to comment.