You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Some PHP frameworks include files without PHP classes such as ones where PHP is mixed with HTML. The coverage is generated well without @covers annotation when testing a controller class. But when @covers is used then the coverage for such files is missing. Maybe there is any other way to include them except the fix that is posted above?
In our case @covers improves the performance of generating the coverage report by 25% percent and we would like to use this annotation but the missing coverage for the plain PHP files makes it questionable.
We need to support relative paths, too, but we do not know what the base is.
As a possible solution I would like to suggest to add is_base="true" parameter to the whitelisted directory in the configuration file that would be used as the base (and to allow only one directory to be the base), f.ex.:
I'm open to implementing this, have a local version working based on a more recent version than the example above.
I will need some input on a few points:
Match a new pattern in Mapper::stringToCodeUnits, I'm thinking of a more explicit pattern like file:/src/file.php, but just implicitly based on the presence of an extension could also work.
Every type of code unit has a class and a sort of checker that is never used in the code, like ClassMethodUnit::isClassMethod(), should I add just copy the structure and implement a FileUnit::isFile()?
I implemented it locally by just returning a very large range ie range(1, 100000), this reduces the need to read the file twice. Would this be okay or do you prefer actually parsing the file for number of lines?
Relative paths relative to the file of the annotations can be implemented pretty simply, since we know the class in which @covers is used. I'm not sure this is very valuable though.
Relative paths relative to the some application root will require a way of injecting or retrieving configuration. I propose not implementing this in the first draft. People running in containers will be able to get most of the benefits using absolute paths, and adding support for relative paths later won't break anyone's setup.
Another case, where this would be useful, is testing closure-based routes for some Nova components.
Simple ones are quite easy to define in one file, but then PhpStorm navigation is broken. Having ability to set path would resolve the issue.
When it's implemented and released, would it be possible to mark test classes that run bin/* scripts using exec()? Today I had the need for that and I am looking for possibilities 🙂.
The supporting work on phpunit/php-code-coverage has begun in sebastianbergmann/php-code-coverage#1210. That PR adds three new Target types (File, Directory, and DirectoryRecursively) populates the matching files, directories, and directoriesRecursively buckets in the TargetMap, and adds a realpath() fallback in Mapper::mapTarget() so users can pass either an absolute path or a path relative to the current working directory.
Once that is merged, the PHPUnit side will add six new attributes:
#[CoversFile] / #[UsesFile]
#[CoversDirectory] / #[UsesDirectory] for direct children only (non-recursively)
#[CoversDirectoryRecursively] / #[UsesDirectoryRecursively] every file under the directory (recursively)
The wiring follows the existing CoversNamespace / UsesNamespace pipeline exactly:
New attribute classes under src/Framework/Attributes/.
Matching Metadata value objects under src/Metadata/, with isCoversFile() / isCoversDirectory() / isCoversDirectoryRecursively() predicates (and the Uses… equivalents) added to the abstract Metadata base.
Six new factory methods on Metadata and six new filter methods on MetadataCollection.
New cases in AttributeParser (class-level switch only — same as CoversNamespace).
New branches in Metadata\Api\CodeCoverage::coversTargets() and usesTargets() that translate the metadata into Target::forFile() / forDirectory() / forDirectoryRecursively() instances from Filesystem-based targeting php-code-coverage#1210.
Path semantics: the attribute argument is stored verbatim and normalized with realpath() at the Mapper boundary, so both absolute and relative paths work without plumbing a project root through the attribute parser.
changed the title [-]`#[CoversDirectory]`, `#[UsesDirectory]`, `#[CoversFile]`, and `#[UsesFile]`[/-][+]Filesystem-based code coverage targeting[/+]on May 28, 2026
Activity
[-]Support `@covers /path/to/file.php`[/-][+]Support "@covers /path/to/file.php"[/+]sebastianbergmann commentedon Jan 17, 2020
The above would only work for absolute paths. We need to support relative paths, too, but we do not know what the base is.
bancer commentedon Dec 17, 2020
Some PHP frameworks include files without PHP classes such as ones where PHP is mixed with HTML. The coverage is generated well without
@coversannotation when testing a controller class. But when@coversis used then the coverage for such files is missing. Maybe there is any other way to include them except the fix that is posted above?In our case
@coversimproves the performance of generating the coverage report by 25% percent and we would like to use this annotation but the missing coverage for the plain PHP files makes it questionable.As a possible solution I would like to suggest to add
is_base="true"parameter to the whitelisted directory in the configuration file that would be used as the base (and to allow only one directory to be the base), f.ex.:SamMousa commentedon Feb 18, 2021
I'm open to implementing this, have a local version working based on a more recent version than the example above.
I will need some input on a few points:
Match a new pattern in
Mapper::stringToCodeUnits, I'm thinking of a more explicit pattern likefile:/src/file.php, but just implicitly based on the presence of an extension could also work.Every type of code unit has a class and a sort of checker that is never used in the code, like
ClassMethodUnit::isClassMethod(), should I add just copy the structure and implement aFileUnit::isFile()?I implemented it locally by just returning a very large range ie
range(1, 100000), this reduces the need to read the file twice. Would this be okay or do you prefer actually parsing the file for number of lines?Relative paths relative to the file of the annotations can be implemented pretty simply, since we know the class in which
@coversis used. I'm not sure this is very valuable though.Relative paths relative to the some application root will require a way of injecting or retrieving configuration. I propose not implementing this in the first draft. People running in containers will be able to get most of the benefits using absolute paths, and adding support for relative paths later won't break anyone's setup.
riabiy-denis commentedon Jun 18, 2021
Another case, where this would be useful, is testing closure-based routes for some Nova components.
Simple ones are quite easy to define in one file, but then PhpStorm navigation is broken. Having ability to set path would resolve the issue.
2 remaining items
sebastianbergmann commentedon Oct 10, 2024
Depends on sebastianbergmann/php-code-coverage#519.
Initial work on sebastianbergmann/phpunit#3794
Wirone commentedon Jun 12, 2025
When it's implemented and released, would it be possible to mark test classes that run
bin/*scripts usingexec()? Today I had the need for that and I am looking for possibilities 🙂.sebastianbergmann commentedon May 28, 2026
The supporting work on
phpunit/php-code-coveragehas begun in sebastianbergmann/php-code-coverage#1210. That PR adds three newTargettypes (File,Directory, andDirectoryRecursively) populates the matchingfiles,directories, anddirectoriesRecursivelybuckets in theTargetMap, and adds arealpath()fallback inMapper::mapTarget()so users can pass either an absolute path or a path relative to the current working directory.Once that is merged, the PHPUnit side will add six new attributes:
#[CoversFile]/#[UsesFile]#[CoversDirectory]/#[UsesDirectory]for direct children only (non-recursively)#[CoversDirectoryRecursively]/#[UsesDirectoryRecursively]every file under the directory (recursively)The wiring follows the existing
CoversNamespace/UsesNamespacepipeline exactly:src/Framework/Attributes/.Metadatavalue objects undersrc/Metadata/, withisCoversFile()/isCoversDirectory()/isCoversDirectoryRecursively()predicates (and theUses…equivalents) added to the abstractMetadatabase.Metadataand six new filter methods onMetadataCollection.AttributeParser(class-level switch only — same asCoversNamespace).Metadata\Api\CodeCoverage::coversTargets()andusesTargets()that translate the metadata intoTarget::forFile()/forDirectory()/forDirectoryRecursively()instances from Filesystem-based targeting php-code-coverage#1210.Path semantics: the attribute argument is stored verbatim and normalized with
realpath()at theMapperboundary, so both absolute and relative paths work without plumbing a project root through the attribute parser.[-]`#[CoversDirectory]`, `#[UsesDirectory]`, `#[CoversFile]`, and `#[UsesFile]`[/-][+]Filesystem-based code coverage targeting[/+]Document sebastianbergmann/phpunit#3794
Closes sebastianbergmann#3794
Closes sebastianbergmann#3794