sebastianbergmann / phpcov

TextUI frontend for php-code-coverage
BSD 3-Clause "New" or "Revised" License
223 stars 58 forks source link

Deal with merging coverage files, generated with different base paths #115

Open ipeevski opened 2 years ago

ipeevski commented 2 years ago

There are a number of use cases, when test coverage can be generated in different places (with different base paths).

Here are some ways to run tests, where each might result in a different path: a. Run tests locally b. Run inside docker c. Run on a remote machine d. Run inside CI pipeline runners e. Run distributed across different machines

Our use case in particular was combinging unit tests running locally and acceptance tests running inside a docker container, accessed via selenium.

Right now those test coverages cannot be merged, since it requires the base paths to be the same. I'm proposing a proof of concept of an utility to align base paths, so phpcov can be used to merge those properly. (This can be added to phpcov or the parent CodeCoverage package).

Here is a script that would map all the paths in a given .cov file from one path to another:

<?php
// Relies on SebastianBergmann\CodeCoverage being available via composer
include 'vendor/autoload.php';

use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Driver\PcovDriver;
use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData;
use SebastianBergmann\CodeCoverage\Report\PHP as PHPReport;

// The following three parameters are specifying what to be processed.
// If it's implemented as a function, those would be the input parameters.

// The file name to be processed
$filename = 'unit.cov';
// Replace the remote path
$from = '/app/';
// With the local path (where phpcov is running from)
$to = '/home/user/project/';

// Get the existing .cov file as a CodeCoverage class
$codeCoverage = include $filename;

$data = new ProcessedCodeCoverageData();

// Process line coverage
$lines = $codeCoverage->getData()->lineCoverage();
$newLines = [];
foreach ($lines as $file => $lineData) {
    $file = str_replace($from, $to, $file);
    $newLines[$file] = $lineData;
}
$data->setLineCoverage($newLines);

// Process function coverage
$functions = $codeCoverage->getData()->functionCoverage();
$newFunctions = [];
foreach ($functions as $file => $functionData) {
    $file = str_replace($from, $to, $file);
    $newFunctions[$file] = $functionData;
}
$data->setFunctionCoverage($newFunctions);

// Process filters
$filter = $codeCoverage->filter();
$files = $filter->files();
foreach ($files as $file) {
    $filter->excludeFile($file);
    $file = str_replace($from, $to, $file);
    $filter->includeFile($file);
}

// Create the new CodeCoverage class
$replacedCodeCoverage = new CodeCoverage(new PcovDriver($filter), $filter);
$replacedCodeCoverage->setTests($codeCoverage->getTests());
$replacedCodeCoverage->setData($data);
$replacedCodeCoverage->getReport();

// Save the new .cov file with paths replaced
(new PHPReport)->process($replacedCodeCoverage, $filename);

Note: Because there are some sorts being applied when fetching the files, the order inside the .cov file will differ if it's run with the the to/from being the same. If it's run a second time though, they are an exact match.

Note: I might have missed other parts that need to be populated in the new CodeCoverage class, but this worked for me.

Feel free to use the code included here anyway you see fit. If you want me to create a patch, please let me know what would be your preferred place for this (static method in a class in CodeCoverage or phpcov?).

Diane-Rose22 commented 2 years ago

Thank you, I also have a strong need for this and would love it as a built in to CodeCoverage or phpcov. Our use case is creating the code coverage in our CI pipeline, but wanting to be able to process & view it locally.

One note for your script: to make it work I had to process filters a bit different

// Process filters
        $filter = $codeCoverage->filter();
        $newFilter = new Filter();
        $files = $filter->files();

        foreach ($files as $file) {
            $file = str_replace($from, $to, $file);
            $newFilter->includeFile($file);
        }

// Create the new CodeCoverage class
        $replacedCodeCoverage = new \SebastianBergmann\CodeCoverage\CodeCoverage(
            new \SebastianBergmann\CodeCoverage\Driver\Xdebug3Driver($newFilter),
            $newFilter
        );

because the source directory does not exist on my local machine.

hsegnitz commented 1 year ago

I have a similar use case (a unit test and an integration test suite running in parallel, one in a container the other one natively, on varying machines, gitlab runner "random" directory snippets causing different paths. This is partially because of a really old legacy application running tests on a real database server requiring mutex. So far I've used an old phpunit and phpcov version where I would just use sed to harmonize the paths before running phpcov, but with the newer serialised format this is not an option anymore.

I thought about trying to solve and then contribute this, but I am stuck thinking about it at the moment. This needs either a big interface change here or a smaller one on phpunit side, if it would be up to me, I'd prefer the latter.

To solve this in phpcov, I see three options: a) adding a command that can rewrite the paths for a single coverage file and write changes back to disk b) adding command line switches that allow adding cov files one by one, each in combination with a "base path" that is then stripped in the resulting report c) make b) into some form of config file

These all break with at least one usage pattern of the tool, I guess, so I thought about this some more, and now I think adding a parameter/config option to phpunit that allows to strip a base path from the report, of the test run. That should be a smaller and entirely optional change there - no need for multiple paths etc. as it just affects a single run.

tvbeek commented 1 year ago

I think that https://github.com/sebastianbergmann/php-code-coverage/issues/925 can be a good part for solving this problem.