-
-
Notifications
You must be signed in to change notification settings - Fork 385
Description
| Q | A |
|---|---|
| PHPUnit version | 12.5.3 |
| PHP version | 8.4.15 |
| Installation Method | Composer |
Summary
When using #[UsesClassesThatExtendClass(BaseHelper::class)], PHPUnit caches coverage data (\SebastianBergmann\CodeCoverage\StaticAnalysis\AnalysisResult). This includes \SebastianBergmann\CodeCoverage\StaticAnalysis\Class_ that contains absolute file path in $file.
Sometimes tests may be run in multiple environments but in the same filesystem (e.g., against locally installed PHP and inside Docker container from compose.yaml). This is common in developer environments. In this case first run (that creates cache) stores absolute paths from one environment, and subsequent run reads cache and expects absolute paths from another environment.
This affects specifically UsesClassesThatExtendClass, as this attribute is effectively not taken into account in one of the environments.
How to reproduce
Minimal project I managed to achieve is below.
Requirement:
- PHP 8.4 available on local machine. (Because we need to run tests twice: in Docker and locally.)
composer.json
{
"name": "bug/repro",
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^12.5.3"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}phpunit.dist.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://docs.phpunit.de/en -->
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
failOnAllIssues="true"
bootstrap="vendor/autoload.php"
cacheDirectory="var/cache/phpunit"
requireCoverageMetadata="true"
beStrictAboutCoverageMetadata="true"
>
<testsuites>
<testsuite name="full">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
</source>
<coverage>
<report>
<text outputFile="php://stdout" showOnlySummary="true"/>
</report>
</coverage>
</phpunit>compose.yaml
services:
app:
build:
context: .
dockerfile_inline: |
FROM php:8.4-fpm-bullseye
# Install git and unzip (required for Composer)
RUN apt-get update && apt-get install -y \
git \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Install PCOV extension
RUN pecl install pcov \
&& docker-php-ext-enable pcov
# Install Composer globally
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Configure PCOV for optimal performance
# Using "all" includes all files by default which is typical for small test setups
RUN echo "pcov.enabled=1" >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini \
&& echo "pcov.directory=." >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini
WORKDIR /app
volumes:
- .:/appsrc/BaseHelper.php
<?php
namespace App;
abstract class BaseHelper
{
abstract public function getConcreteValue(): string;
public function getBaseValue(): string
{
return 'base';
}
}src/ConcreteHelper.php
<?php
namespace App;
class ConcreteHelper extends BaseHelper
{
public function getConcreteValue(): string
{
return $this->getBaseValue() . '_concrete';
}
}src/ClassUnderTest.php
<?php
declare(strict_types=1);
namespace App;
class ClassUnderTest
{
public function action(): string
{
$concreteHelper = new ConcreteHelper();
return $concreteHelper->getConcreteValue();
}
}tests/ReproTest.php
<?php
namespace App\Tests;
use App\BaseHelper;
use App\ClassUnderTest;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\Attributes\UsesClassesThatExtendClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(ClassUnderTest::class)]
#[UsesClass(BaseHelper::class)]
#[UsesClassesThatExtendClass(BaseHelper::class)]
class ReproTest extends TestCase
{
public function testValue(): void
{
$classUnderTest = new ClassUnderTest();
self::assertSame('base_concrete', $classUnderTest->action());
}
}Then:
docker compose updocker compose exec app composer install- Run tests twice (order doesn’t matter):
docker compose exec app vendor/bin/phpunitvendor/bin/phpunit
- First run succeeds without issues.
- Second run reports:
1) App\Tests\ReproTest::testValue
This test executed code that is not listed as code to be covered or used:
- App\ConcreteHelper
This is because PHPUnit caches absolute file paths:
- Inside the container:
/app/src/BaseHelper.php - Outside the container:
/Users/username/Developer/phpunit-bug/src/BaseHelper.php
When cached and real paths mismatch, UsesClassesThatExtendClass is not taken into account.
vendor/bin/phpunit --check-php-configuration
PHPUnit 12.5.3 by Sebastian Bergmann and contributors.
Checking whether PHP is configured according to https://docs.phpunit.de/en/12.5/installation.html#configuring-php-for-development
display_errors = On ... ok
display_startup_errors = On ... ok
error_reporting = -1 ... ok
xdebug.show_exception_trace = 0 ... ok
zend.assertions = 1 ... ok
assert.exception = 1 ... ok
memory_limit = -1 ... ok
composer info | sort
myclabs/deep-copy 1.13.4 Create deep copies (clones) of your objects
nikic/php-parser 5.7.0 A PHP parser written in PHP
phar-io/manifest 2.0.4 Component for reading phar.io manifest information from a PHP Archive (PHAR)
phar-io/version 3.2.1 Library for handling version information and constraints
phpunit/php-code-coverage 12.5.1 Library that provides collection, processing, and rendering functionality for PHP code coverage information.
phpunit/php-file-iterator 6.0.0 FilterIterator implementation that filters files based on a list of suffixes.
phpunit/php-invoker 6.0.0 Invoke callables with a timeout
phpunit/php-text-template 5.0.0 Simple template engine.
phpunit/php-timer 8.0.0 Utility class for timing
phpunit/phpunit 12.5.3 The PHP Unit Testing framework.
sebastian/cli-parser 4.2.0 Library for parsing CLI options
sebastian/comparator 7.1.3 Provides the functionality to compare PHP values for equality
sebastian/complexity 5.0.0 Library for calculating the complexity of PHP code units
sebastian/diff 7.0.0 Diff implementation
sebastian/environment 8.0.3 Provides functionality to handle HHVM/PHP environments
sebastian/exporter 7.0.2 Provides the functionality to export PHP variables for visualization
sebastian/global-state 8.0.2 Snapshotting of global state
sebastian/lines-of-code 4.0.0 Library for counting the lines of code in PHP source code
sebastian/object-enumerator 7.0.0 Traverses array structures and object graphs to enumerate all referenced objects
sebastian/object-reflector 5.0.0 Allows reflection of object attributes, including inherited and non-public ones
sebastian/recursion-context 7.0.1 Provides functionality to recursively process PHP variables
sebastian/type 6.0.3 Collection of value objects that represent the types of the PHP type system
sebastian/version 6.0.0 Library that helps with managing the version number of Git-hosted PHP projects
staabm/side-effects-detector 1.0.5 A static analysis tool to detect side effects in PHP code
theseer/tokenizer 2.0.1 A small library for converting tokenized PHP source code into XML and potentially other formats