Filesystem-based code coverage targeting · Issue #3794 · sebastianbergmann/phpunit · GitHub
Skip to content

Filesystem-based code coverage targeting #3794

Closed

Description

@sebastianbergmann

This could be as simple as

diff --git a/src/Util/Test.php b/src/Util/Test.php
index 5586c3fd4..605dbebc8 100644
--- a/src/Util/Test.php
+++ b/src/Util/Test.php
@@ -889,8 +889,15 @@ private static function getLinesToBeCoveredOrUsed(string $className, string $met
         }
 
         $codeList = [];
+        $files    = [];
 
         foreach (\array_unique($list) as $element) {
+            if (\file_exists($element)) {
+                $files[] = $element;
+
+                continue;
+            }
+
             if ($classShortcut && \strncmp($element, '::', 2) === 0) {
                 $element = $classShortcut . $element;
             }
@@ -914,7 +921,13 @@ private static function getLinesToBeCoveredOrUsed(string $className, string $met
             );
         }
 
-        return self::resolveReflectionObjectsToLines($codeList);
+        $lines = self::resolveReflectionObjectsToLines($codeList);
+
+        foreach ($files as $file) {
+            $lines[$file] = \range(1, \count(\file($file)));
+        }
+
+        return $lines;
     }
 
     /**

Activity

  1. added this to the PHPUnit 8.4 milestone on Aug 20, 2019
  2. changed the title [-]Support `@covers /path/to/file.php`[/-] [+]Support "@covers /path/to/file.php"[/+] on Aug 20, 2019
  3. removed this from the PHPUnit 9.0 milestone on Jan 17, 2020
  4. sebastianbergmann commented on Jan 17, 2020

    @sebastianbergmann
    OwnerAuthor

    The above would only work for absolute paths. We need to support relative paths, too, but we do not know what the base is.

  5. bancer commented on Dec 17, 2020

    @bancer

    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.:

    <filter>
      <whitelist>
        <directory suffix=".php" is_base="true">src</directory>
        <directory suffix=".ctp">src/Template</directory>
      </whitelist>
    </filter>
    
  6. SamMousa commented on Feb 18, 2021

    @SamMousa
    Contributor

    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:

    1. 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.

    2. 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()?

    3. 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?

    4. 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.

    5. 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.

  7. riabiy-denis commented on Jun 18, 2021

    @riabiy-denis

    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.

  8. 2 remaining items

  9. sebastianbergmann commented on Oct 10, 2024

    @sebastianbergmann
    OwnerAuthor
  10. removed this from the PHPUnit 12.0 milestone on Jan 15, 2025
  11. Wirone commented on Jun 12, 2025

    @Wirone

    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 🙂.

  12. sebastianbergmann commented on May 28, 2026

    @sebastianbergmann
    OwnerAuthor

    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:

    1. New attribute classes under src/Framework/Attributes/.
    2. Matching Metadata value objects under src/Metadata/, with isCoversFile() / isCoversDirectory() / isCoversDirectoryRecursively() predicates (and the Uses… equivalents) added to the abstract Metadata base.
    3. Six new factory methods on Metadata and six new filter methods on MetadataCollection.
    4. New cases in AttributeParser (class-level switch only — same as CoversNamespace).
    5. 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.

  13. changed the title [-]`#[CoversDirectory]`, `#[UsesDirectory]`, `#[CoversFile]`, and `#[UsesFile]`[/-] [+]Filesystem-based code coverage targeting[/+] on May 28, 2026
  14. added a commit that references this issue on Jul 11, 2026
    1303144
  15. added a commit that references this issue on Jul 19, 2026
    2da5433
  16. locked as resolved and limited conversation to collaborators on Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions