From f03c47b16af0cc506823b4bec0b5e581dc776a35 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 1 Mar 2026 14:29:24 +0100 Subject: [PATCH] Collect coverage in subprocesses --- .github/workflows/ci.yml | 31 +++++++++++++++++----- .gitignore | 1 + tests/Util/Command/Shutdown.php | 16 +++++++++++ tests/Util/RemoteWorker.php | 30 ++++++++++++++++++++- tests/Util/worker.php | 17 ++++++++++++ tools/merge-coverage.php | 47 +++++++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 tests/Util/Command/Shutdown.php create mode 100644 tools/merge-coverage.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 301f712..7637e5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,7 +96,7 @@ jobs: if: ${{ matrix.php-version != env.HIGHEST_PHP_VERSION }} - name: Run PHPUnit with coverage - run: vendor/bin/phpunit --coverage-clover clover.xml + run: vendor/bin/phpunit --coverage-php coverage.php env: DRIVER: mysql_pdo MYSQL_HOST: 127.0.0.1 @@ -106,6 +106,10 @@ jobs: PDO_ERRMODE: ${{ matrix.pdo-errmode }} if: ${{ matrix.php-version == env.HIGHEST_PHP_VERSION }} + - name: Merge worker coverage + run: php tools/merge-coverage.php coverage.php clover.xml && rm coverage.php + if: ${{ matrix.php-version == env.HIGHEST_PHP_VERSION }} + - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 env: @@ -154,13 +158,16 @@ jobs: dependency-versions: ${{ matrix.deps }} - name: Run PHPUnit with coverage - run: vendor/bin/phpunit --coverage-clover clover.xml + run: vendor/bin/phpunit --coverage-php coverage.php env: DRIVER: mysql_doctrine MYSQL_HOST: 127.0.0.1 MYSQL_USER: root MYSQL_PASSWORD: password + - name: Merge worker coverage + run: php tools/merge-coverage.php coverage.php clover.xml && rm coverage.php + - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 env: @@ -201,13 +208,16 @@ jobs: uses: ramsey/composer-install@v3 - name: Run PHPUnit with coverage - run: vendor/bin/phpunit --coverage-clover clover.xml + run: vendor/bin/phpunit --coverage-php coverage.php env: DRIVER: mariadb_pdo MARIADB_HOST: 127.0.0.1 MARIADB_USER: root MARIADB_PASSWORD: password + - name: Merge worker coverage + run: php tools/merge-coverage.php coverage.php clover.xml && rm coverage.php + - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 env: @@ -248,13 +258,16 @@ jobs: uses: ramsey/composer-install@v3 - name: Run PHPUnit with coverage - run: vendor/bin/phpunit --coverage-clover clover.xml + run: vendor/bin/phpunit --coverage-php coverage.php env: DRIVER: mariadb_doctrine MARIADB_HOST: 127.0.0.1 MARIADB_USER: root MARIADB_PASSWORD: password + - name: Merge worker coverage + run: php tools/merge-coverage.php coverage.php clover.xml && rm coverage.php + - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 env: @@ -293,13 +306,16 @@ jobs: uses: ramsey/composer-install@v3 - name: Run PHPUnit with coverage - run: vendor/bin/phpunit --coverage-clover clover.xml + run: vendor/bin/phpunit --coverage-php coverage.php env: DRIVER: postgres_pdo POSTGRES_HOST: 127.0.0.1 POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres + - name: Merge worker coverage + run: php tools/merge-coverage.php coverage.php clover.xml && rm coverage.php + - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 env: @@ -338,13 +354,16 @@ jobs: uses: ramsey/composer-install@v3 - name: Run PHPUnit with coverage - run: vendor/bin/phpunit --coverage-clover clover.xml + run: vendor/bin/phpunit --coverage-php coverage.php env: DRIVER: postgres_doctrine POSTGRES_HOST: 127.0.0.1 POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres + - name: Merge worker coverage + run: php tools/merge-coverage.php coverage.php clover.xml && rm coverage.php + - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 env: diff --git a/.gitignore b/.gitignore index d9ac457..7930847 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /tools/* !/tools/ecs/composer.json !/tools/ecs/ecs.php +!/tools/merge-coverage.php diff --git a/tests/Util/Command/Shutdown.php b/tests/Util/Command/Shutdown.php new file mode 100644 index 0000000..3383ecb --- /dev/null +++ b/tests/Util/Command/Shutdown.php @@ -0,0 +1,16 @@ +process = new Process(['php', 'worker.php'], __DIR__); + $command = ['php']; + + if (extension_loaded('pcov')) { + // pcov.directory defaults to the process CWD (tests/Util/), which excludes src/. + // Set it explicitly so pcov tracks the library source files. + $command[] = '-d'; + $command[] = 'pcov.directory=' . dirname(__DIR__, 2) . '/src'; + } + + $command[] = 'worker.php'; + + $this->process = new Process($command, __DIR__); $this->input = new InputStream(); $this->process->setInput($this->input); @@ -235,6 +249,20 @@ private function parseDuration(string $duration): float public function __destruct() { + if (! $this->isKilled && $this->process->isRunning()) { + try { + $this->sendCommand(new Command\Shutdown()); + + // Give it enough time to collect coverage. + $deadline = microtime(true) + 1.0; + while ($this->process->isRunning() && microtime(true) < $deadline) { + usleep(100_000); + } + } catch (Throwable) { + // Ignore errors. + } + } + $this->process->stop(0); } } diff --git a/tests/Util/worker.php b/tests/Util/worker.php index 3ba1821..5430c26 100644 --- a/tests/Util/worker.php +++ b/tests/Util/worker.php @@ -14,9 +14,26 @@ use Brick\Lock\Tests\Util\LockDriverFactoryException; use function Opis\Closure\unserialize; +use function pcov\collect; +use function pcov\start; +use function pcov\stop; + +use const pcov\all; require __DIR__ . '/../../vendor/autoload.php'; +if (extension_loaded('pcov')) { + start(); + register_shutdown_function(function (): void { + stop(); + $coverage = collect(all); + file_put_contents( + sys_get_temp_dir() . '/brick-lock-worker-coverage-' . getmypid() . '.bin', + serialize($coverage), + ); + }); +} + try { $lockDriverWithInfo = LockDriverFactory::getDriver(); } catch (LockDriverFactoryException $e) { diff --git a/tools/merge-coverage.php b/tools/merge-coverage.php new file mode 100644 index 0000000..8c0eab1 --- /dev/null +++ b/tools/merge-coverage.php @@ -0,0 +1,47 @@ + + * + * Worker processes (tests/Util/worker.php) save their pcov coverage to temp files + * named brick-lock-worker-coverage-.bin in sys_get_temp_dir(). This script + * loads the serialized CodeCoverage object produced by PHPUnit (--coverage-php), + * appends each worker's raw coverage data, then writes the merged clover.xml. + */ + +declare(strict_types=1); + +require __DIR__ . '/../vendor/autoload.php'; + +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; +use SebastianBergmann\CodeCoverage\Report\Clover; + +$coveragePhpFile = $argv[1] ?? __DIR__ . '/../coverage.php'; +$outputFile = $argv[2] ?? __DIR__ . '/../clover.xml'; + +/** @var CodeCoverage $coverage */ +$coverage = require $coveragePhpFile; + +$workerFiles = glob(sys_get_temp_dir() . '/brick-lock-worker-coverage-*.bin'); +assert($workerFiles !== false); + +foreach ($workerFiles as $file) { + $data = file_get_contents($file); + assert($data !== false); + + $workerData = unserialize($data); + + if (! is_array($workerData) || $workerData === []) { + continue; + } + + $rawData = RawCodeCoverageData::fromXdebugWithoutPathCoverage($workerData); + $coverage->append($rawData, basename($file, '.bin')); + + unlink($file); +} + +(new Clover())->process($coverage, $outputFile);