-
-
Notifications
You must be signed in to change notification settings - Fork 376
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial work on mapping code coverage targets to source locations
- Loading branch information
1 parent
519ec4d
commit 02c166f
Showing
3 changed files
with
222 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php declare(strict_types=1); | ||
/* | ||
* This file is part of phpunit/php-code-coverage. | ||
* | ||
* (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 SebastianBergmann\CodeCoverage; | ||
|
||
use function sprintf; | ||
use RuntimeException; | ||
|
||
final class InvalidCodeCoverageTargetException extends RuntimeException implements Exception | ||
{ | ||
/** | ||
* @param non-empty-string $target | ||
*/ | ||
public function __construct(string $target) | ||
{ | ||
parent::__construct( | ||
sprintf( | ||
'%s is not a valid target for code coverage', | ||
$target, | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php declare(strict_types=1); | ||
/* | ||
* This file is part of phpunit/php-code-coverage. | ||
* | ||
* (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 SebastianBergmann\CodeCoverage\Test\Target; | ||
|
||
use function array_merge; | ||
use function array_unique; | ||
use function assert; | ||
use function sort; | ||
use SebastianBergmann\CodeCoverage\InvalidCodeCoverageTargetException; | ||
|
||
/** | ||
* @immutable | ||
* | ||
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage | ||
* | ||
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage | ||
*/ | ||
final readonly class Mapper | ||
{ | ||
/** | ||
* @var array{namespaces: array<non-empty-string, list<positive-int>>, classes: array<non-empty-string, list<positive-int>>, classesThatExtendClass: array<non-empty-string, list<positive-int>>, classesThatImplementInterface: array<non-empty-string, list<positive-int>>, traits: array<non-empty-string, list<positive-int>>, methods: array<non-empty-string, list<positive-int>>, functions: array<non-empty-string, list<positive-int>>} | ||
*/ | ||
private array $map; | ||
|
||
/** | ||
* @param array{namespaces: array<non-empty-string, list<positive-int>>, classes: array<non-empty-string, list<positive-int>>, classesThatExtendClass: array<non-empty-string, list<positive-int>>, classesThatImplementInterface: array<non-empty-string, list<positive-int>>, traits: array<non-empty-string, list<positive-int>>, methods: array<non-empty-string, list<positive-int>>, functions: array<non-empty-string, list<positive-int>>} $map | ||
*/ | ||
public function __construct(array $map) | ||
{ | ||
$this->map = $map; | ||
} | ||
|
||
/** | ||
* @return array<non-empty-string, list<positive-int>> | ||
*/ | ||
public function map(TargetCollection $targets): array | ||
{ | ||
$result = []; | ||
|
||
foreach ($targets as $target) { | ||
foreach ($this->mapTarget($target) as $file => $lines) { | ||
if (!isset($result[$file])) { | ||
$result[$file] = $lines; | ||
|
||
continue; | ||
} | ||
|
||
$result[$file] = array_unique(array_merge($result[$file], $lines)); | ||
|
||
sort($result[$file]); | ||
} | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* @throws InvalidCodeCoverageTargetException | ||
* | ||
* @return array<non-empty-string, list<positive-int>> | ||
*/ | ||
private function mapTarget(Target $target): array | ||
{ | ||
if ($target->isClass()) { | ||
assert($target instanceof Class_); | ||
|
||
if (!isset($this->map['classes'][$target->className()])) { | ||
throw new InvalidCodeCoverageTargetException('Class ' . $target->className()); | ||
} | ||
|
||
return $this->map['classes'][$target->className()]; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
<?php declare(strict_types=1); | ||
/* | ||
* This file is part of phpunit/php-code-coverage. | ||
* | ||
* (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 SebastianBergmann\CodeCoverage\Test\Target; | ||
|
||
use function array_keys; | ||
use function range; | ||
use function realpath; | ||
use PHPUnit\Framework\Attributes\CoversClass; | ||
use PHPUnit\Framework\Attributes\DataProvider; | ||
use PHPUnit\Framework\Attributes\Small; | ||
use PHPUnit\Framework\Attributes\TestDox; | ||
use PHPUnit\Framework\TestCase; | ||
use SebastianBergmann\CodeCoverage\Filter; | ||
use SebastianBergmann\CodeCoverage\InvalidCodeCoverageTargetException; | ||
use SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingFileAnalyser; | ||
|
||
#[CoversClass(Mapper::class)] | ||
#[Small] | ||
final class MapperTest extends TestCase | ||
{ | ||
/** | ||
* @return non-empty-list<array{0: non-empty-string, 1: array<non-empty-string, non-empty-list<positive-int>>, 2: TargetCollection}> | ||
*/ | ||
public static function provider(): array | ||
{ | ||
$file = realpath(__DIR__ . '/../../_files/source_with_interfaces_classes_traits_functions.php'); | ||
|
||
return [ | ||
[ | ||
'single class', | ||
[ | ||
$file => range(33, 52), | ||
], | ||
TargetCollection::fromArray( | ||
[ | ||
Target::forClass('SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ChildClass'), | ||
], | ||
), | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @return non-empty-list<array{0: non-empty-string, 1: non-empty-string, 2: TargetCollection}> | ||
*/ | ||
public static function invalidProvider(): array | ||
{ | ||
return [ | ||
[ | ||
'single class', | ||
'Class SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass is not a valid target for code coverage', | ||
TargetCollection::fromArray( | ||
[ | ||
Target::forClass('SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ChildClass'), | ||
], | ||
), | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @param array<non-empty-string, non-empty-list<positive-int>> $expected | ||
*/ | ||
#[DataProvider('provider')] | ||
#[TestDox('Maps TargetCollection with $description to source locations')] | ||
public function testMapsTargetValueObjectsToSourceLocations(string $description, array $expected, TargetCollection $targets): void | ||
{ | ||
$this->assertSame( | ||
$expected, | ||
$this->mapper(array_keys($expected))->map($targets), | ||
); | ||
} | ||
|
||
#[DataProvider('invalidProvider')] | ||
#[TestDox('Cannot map $description that does not exist to source locations')] | ||
public function testCannotMapInvalidTargets(string $description, string $exceptionMessage, TargetCollection $targets): void | ||
{ | ||
$this->expectException(InvalidCodeCoverageTargetException::class); | ||
$this->expectExceptionMessage($exceptionMessage); | ||
|
||
$this->mapper([])->map($targets); | ||
} | ||
|
||
/** | ||
* @param list<non-empty-string> $files | ||
*/ | ||
private function mapper(array $files): Mapper | ||
{ | ||
return new Mapper($this->map($files)); | ||
} | ||
|
||
/** | ||
* @param list<non-empty-string> $files | ||
* | ||
* @return array{namespaces: array<non-empty-string, list<positive-int>>, classes: array<non-empty-string, list<positive-int>>, classesThatExtendClass: array<non-empty-string, list<positive-int>>, classesThatImplementInterface: array<non-empty-string, list<positive-int>>, traits: array<non-empty-string, list<positive-int>>, methods: array<non-empty-string, list<positive-int>>, functions: array<non-empty-string, list<positive-int>>} | ||
*/ | ||
private function map(array $files): array | ||
{ | ||
$filter = new Filter; | ||
|
||
$filter->includeFiles($files); | ||
|
||
return (new MapBuilder)->build($filter, new ParsingFileAnalyser(false, false)); | ||
} | ||
} |