Skip to content

Commit

Permalink
Initial commit of working command line app
Browse files Browse the repository at this point in the history
  • Loading branch information
cooperaj committed Feb 19, 2020
0 parents commit bde983e
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea/
vendor/
composer.lock
covpf.phar
26 changes: 26 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "cooperaj/php-coverage-path-fixer",
"description": "Manipulates .cov files path information when source files have moved or are in a different location",
"minimum-stability": "stable",
"license": "MIT",
"authors": [
{
"name": "Adam Cooper",
"email": "[email protected]"
}
],

"require": {
"phpunit/php-code-coverage": "^8.0.0",
"symfony/console": "^v5.0.4"
},
"require-dev": {
"phpunit/phpunit": "9.0.1"
},
"autoload": {
"psr-4": {
"CoveragePathFixer\\": "src/"
}
},
"bin": [ "covpf" ]
}
11 changes: 11 additions & 0 deletions covpf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

use CoveragePathFixer\Application;

include 'vendor/autoload.php';

$application = new Application();
$application->run();
20 changes: 20 additions & 0 deletions src/Application.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace CoveragePathFixer;

use CoveragePathFixer\Command\ChangePathPrefix;
use Symfony\Component\Console\Application as ConsoleApplication;

class Application extends ConsoleApplication
{
public function __construct(string $name = 'CoveragePathFixer', string $version = 'UNKNOWN')
{
parent::__construct($name, $version);

$command = new ChangePathPrefix();
$this->add($command);
$this->setDefaultCommand($command->getName(), true);
}
}
169 changes: 169 additions & 0 deletions src/Command/ChangePathPrefix.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
<?php

declare(strict_types=1);

namespace CoveragePathFixer\Command;

use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RecursiveRegexIterator;
use RegexIterator;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Filter;
use SebastianBergmann\CodeCoverage\Report\Clover;
use SebastianBergmann\CodeCoverage\Report\PHP;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class ChangePathPrefix extends Command
{
protected static $defaultName = 'fix';

protected function configure(): void
{
$this->setDescription('Swaps a given path prefix with another in your coverage files')
->setHelp(
'This command will recursively search for coverage (.cov) files and alter the code ' .
'paths held within such that the files can be found. \n\n' .
'e.g. "/app/src" can be swapped with e.g. "/home/ci/src"'
)

->addArgument(
'directory_to_search',
InputArgument::REQUIRED,
'The directory to recursively search for ".cov" files',
)

->addArgument(
'original_prefix',
InputArgument::REQUIRED,
'The orginal path prefix e.g. "/app/src"'
)

->addArgument(
'replacement_prefix',
InputArgument::REQUIRED,
'The new path prefix e.g. "/home/ci/src"'
)

->addOption(
'merge',
'm',
InputOption::VALUE_REQUIRED,
'Merge the discovered files into the specified single output coverage (.cov) file'
)

->addOption(
'clover',
'c',
InputOption::VALUE_NONE,
'Output an additional clover format coverage file alongside each .cov file that is processed'
);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
try {
$files = $this->findCoverageFiles($input->getArgument('directory_to_search'));

$files = $this->iterateCoverageFiles(
$files,
$input->getArgument('original_prefix'),
$input->getArgument('replacement_prefix')
);

if ($path = $input->getOption('merge')) {
$files = $this->mergeCoverageFiles($files, $path);
}

$this->outputCoverageFiles($files, $input->getOption('clover'));
} catch (\Exception $ex) {
return $ex->getCode();
}

return 0;
}

private function changePrefix(array $data, string $originalPrefix, string $replacementPrefix): array
{
return array_combine(array_map(function($el) use ($originalPrefix, $replacementPrefix) {
$el = preg_replace('#^' . $originalPrefix . '#', $replacementPrefix, $el);
return $el;
}, array_keys($data)), array_values($data));
}

private function iterateCoverageFiles(array $files, string $originalPrefix, string $replacementPrefix): array
{
return array_map(function(array $file) use ($originalPrefix, $replacementPrefix) {
$coverage = $this->loadCoverageFile($file[0]);

$data = $this->changePrefix($coverage->getData(), $originalPrefix, $replacementPrefix);
$whiteList = $this->changePrefix(
$coverage->filter()->getWhitelistedFiles(),
$originalPrefix,
$replacementPrefix
);

$filter = new Filter();
$filter->setWhitelistedFiles($whiteList);

$coverage = new CodeCoverage(null, $filter);
$coverage->setData($data);

return $coverage;
}, $files);
}

private function findCoverageFiles(string $directory): array
{
$path = realpath($directory);

$directory = new RecursiveDirectoryIterator($path);
$iterator = new RecursiveIteratorIterator($directory);
$filtered = new RegexIterator($iterator, '/^.+\.cov$/i', RecursiveRegexIterator::GET_MATCH);

return iterator_to_array($filtered);
}

private function loadCoverageFile(string $file): CodeCoverage
{
$coverage = include $file;

if (!($coverage instanceof CodeCoverage)) {
unset($coverage);
throw new \Exception('File with coverage extension not resolved to CodeCoverage class');
}

return $coverage;
}

private function mergeCoverageFiles(array $files, string $path): array
{
$coverage = new CodeCoverage();

foreach ($files as $file => $coverage) {
$coverage->merge($coverage);
}

return [$path => $coverage];
}

private function outputCoverageFiles(array $files, bool $asClover = false): void
{
array_walk($files, function($coverage, $path) use ($asClover) {
if ($asClover) {
$filename = basename($path, '.cov');
$directory = dirname($path);

$reportWriter = new Clover();
$reportWriter->process($coverage, $directory . DIRECTORY_SEPARATOR . $filename . '.xml');
}

$reportWriter = new PHP();
$reportWriter->process($coverage, $path);
});
}
}

0 comments on commit bde983e

Please sign in to comment.