mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 07:47:22 +01:00
Merge pull request #43 from fetzi/plugins
Integrate pest-plugins with 2 interfaces
This commit is contained in:
@ -19,6 +19,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^7.3",
|
"php": "^7.3",
|
||||||
"nunomaduro/collision": "^5.0",
|
"nunomaduro/collision": "^5.0",
|
||||||
|
"pestphp/pest-plugin": "dev-master",
|
||||||
"phpunit/phpunit": "^9.1.4",
|
"phpunit/phpunit": "^9.1.4",
|
||||||
"sebastian/environment": "^5.1"
|
"sebastian/environment": "^5.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,79 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Pest\Actions;
|
|
||||||
|
|
||||||
use Pest\Console\Coverage;
|
|
||||||
use Pest\Support\Str;
|
|
||||||
use Pest\TestSuite;
|
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
|
||||||
use Symfony\Component\Console\Input\InputDefinition;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
final class AddsCoverage
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private const COVERAGE_OPTION = 'coverage';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private const MIN_OPTION = 'min';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the coverage related options.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
|
||||||
*/
|
|
||||||
private const OPTIONS = [self::COVERAGE_OPTION, self::MIN_OPTION];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If any, adds the coverage params to the given original arguments.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $originals
|
|
||||||
*
|
|
||||||
* @return array<int, string>
|
|
||||||
*/
|
|
||||||
public static function from(TestSuite $testSuite, array $originals): array
|
|
||||||
{
|
|
||||||
$arguments = array_merge([''], array_values(array_filter($originals, function ($original): bool {
|
|
||||||
foreach (self::OPTIONS as $option) {
|
|
||||||
if ($original === sprintf('--%s', $option) || Str::startsWith($original, sprintf('--%s=', $option))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
})));
|
|
||||||
|
|
||||||
$originals = array_flip($originals);
|
|
||||||
foreach ($arguments as $argument) {
|
|
||||||
unset($originals[$argument]);
|
|
||||||
}
|
|
||||||
$originals = array_flip($originals);
|
|
||||||
|
|
||||||
$inputs = [];
|
|
||||||
$inputs[] = new InputOption(self::COVERAGE_OPTION, null, InputOption::VALUE_NONE);
|
|
||||||
$inputs[] = new InputOption(self::MIN_OPTION, null, InputOption::VALUE_REQUIRED);
|
|
||||||
|
|
||||||
$input = new ArgvInput($arguments, new InputDefinition($inputs));
|
|
||||||
if ((bool) $input->getOption(self::COVERAGE_OPTION)) {
|
|
||||||
$testSuite->coverage = true;
|
|
||||||
$originals[] = '--coverage-php';
|
|
||||||
$originals[] = Coverage::getPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input->getOption(self::MIN_OPTION) !== null) {
|
|
||||||
/* @phpstan-ignore-next-line */
|
|
||||||
$testSuite->coverageMin = (float) $input->getOption(self::MIN_OPTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $originals;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,11 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Console;
|
namespace Pest\Console;
|
||||||
|
|
||||||
use Pest\Actions\AddsCoverage;
|
|
||||||
use Pest\Actions\AddsDefaults;
|
use Pest\Actions\AddsDefaults;
|
||||||
use Pest\Actions\AddsTests;
|
use Pest\Actions\AddsTests;
|
||||||
use Pest\Actions\LoadStructure;
|
use Pest\Actions\LoadStructure;
|
||||||
use Pest\Actions\ValidatesConfiguration;
|
use Pest\Actions\ValidatesConfiguration;
|
||||||
|
use Pest\Contracts\Plugins\AddsOutput;
|
||||||
|
use Pest\Contracts\Plugins\HandlesArguments;
|
||||||
|
use Pest\Plugin\Loader;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use PHPUnit\Framework\TestSuite as BaseTestSuite;
|
use PHPUnit\Framework\TestSuite as BaseTestSuite;
|
||||||
use PHPUnit\TextUI\Command as BaseCommand;
|
use PHPUnit\TextUI\Command as BaseCommand;
|
||||||
@ -54,9 +56,14 @@ final class Command extends BaseCommand
|
|||||||
protected function handleArguments(array $argv): void
|
protected function handleArguments(array $argv): void
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* First, let's handle pest is own `--coverage` param.
|
* First, let's call all plugins that want to handle arguments
|
||||||
*/
|
*/
|
||||||
$argv = AddsCoverage::from($this->testSuite, $argv);
|
$plugins = Loader::getPlugins(HandlesArguments::class);
|
||||||
|
|
||||||
|
/** @var HandlesArguments $plugin */
|
||||||
|
foreach ($plugins as $plugin) {
|
||||||
|
$argv = $plugin->handleArguments($this->testSuite, $argv);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Next, as usual, let's send the console arguments to PHPUnit.
|
* Next, as usual, let's send the console arguments to PHPUnit.
|
||||||
@ -119,25 +126,14 @@ final class Command extends BaseCommand
|
|||||||
{
|
{
|
||||||
$result = parent::run($argv, false);
|
$result = parent::run($argv, false);
|
||||||
|
|
||||||
if ($result === 0 && $this->testSuite->coverage) {
|
/*
|
||||||
if (!Coverage::isAvailable()) {
|
* Let's call all plugins that want to add output after test execution
|
||||||
$this->output->writeln(
|
*/
|
||||||
"\n <fg=white;bg=red;options=bold> ERROR </> No code coverage driver is available.</>",
|
$plugins = Loader::getPlugins(AddsOutput::class);
|
||||||
);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$coverage = Coverage::report($this->output);
|
/** @var AddsOutput $plugin */
|
||||||
|
foreach ($plugins as $plugin) {
|
||||||
$result = (int) ($coverage < $this->testSuite->coverageMin);
|
$plugin->addOutput($this->testSuite, $this->output, $result);
|
||||||
|
|
||||||
if ($result === 1) {
|
|
||||||
$this->output->writeln(sprintf(
|
|
||||||
"\n <fg=white;bg=red;options=bold> FAIL </> Code coverage below expected:<fg=red;options=bold> %s %%</>. Minimum:<fg=white;options=bold> %s %%</>.",
|
|
||||||
number_format($coverage, 1),
|
|
||||||
number_format($this->testSuite->coverageMin, 1)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exit($result);
|
exit($result);
|
||||||
|
|||||||
@ -1,167 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Pest\Console;
|
|
||||||
|
|
||||||
use Pest\Exceptions\ShouldNotHappen;
|
|
||||||
use SebastianBergmann\CodeCoverage\Node\Directory;
|
|
||||||
use SebastianBergmann\CodeCoverage\Node\File;
|
|
||||||
use SebastianBergmann\Environment\Runtime;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Terminal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
final class Coverage
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Returns the coverage path.
|
|
||||||
*/
|
|
||||||
public static function getPath(): string
|
|
||||||
{
|
|
||||||
return implode(DIRECTORY_SEPARATOR, [
|
|
||||||
dirname(__DIR__, 2),
|
|
||||||
'.temp',
|
|
||||||
'coverage.php',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs true there is any code
|
|
||||||
* coverage driver available.
|
|
||||||
*/
|
|
||||||
public static function isAvailable(): bool
|
|
||||||
{
|
|
||||||
return (new Runtime())->canCollectCodeCoverage();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports the code coverage report to the
|
|
||||||
* console and returns the result in float.
|
|
||||||
*/
|
|
||||||
public static function report(OutputInterface $output): float
|
|
||||||
{
|
|
||||||
if (!file_exists($reportPath = self::getPath())) {
|
|
||||||
throw ShouldNotHappen::fromMessage(sprintf('Coverage not found in path: %s.', $reportPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var \SebastianBergmann\CodeCoverage\CodeCoverage $codeCoverage */
|
|
||||||
$codeCoverage = require $reportPath;
|
|
||||||
unlink($reportPath);
|
|
||||||
|
|
||||||
$totalWidth = (new Terminal())->getWidth();
|
|
||||||
|
|
||||||
$dottedLineLength = $totalWidth <= 70 ? $totalWidth : 70;
|
|
||||||
|
|
||||||
$totalCoverage = $codeCoverage->getReport()->getLineExecutedPercent();
|
|
||||||
|
|
||||||
$output->writeln(
|
|
||||||
sprintf(
|
|
||||||
' <fg=white;options=bold>Cov: </><fg=default>%s</>',
|
|
||||||
$totalCoverage
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$output->writeln('');
|
|
||||||
|
|
||||||
/** @var Directory<File|Directory> $report */
|
|
||||||
$report = $codeCoverage->getReport();
|
|
||||||
|
|
||||||
foreach ($report->getIterator() as $file) {
|
|
||||||
if (!$file instanceof File) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$dirname = dirname($file->getId());
|
|
||||||
$basename = basename($file->getId(), '.php');
|
|
||||||
|
|
||||||
$name = $dirname === '.' ? $basename : implode(DIRECTORY_SEPARATOR, [
|
|
||||||
$dirname,
|
|
||||||
$basename,
|
|
||||||
]);
|
|
||||||
$rawName = $dirname === '.' ? $basename : implode(DIRECTORY_SEPARATOR, [
|
|
||||||
$dirname,
|
|
||||||
$basename,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$linesExecutedTakenSize = 0;
|
|
||||||
|
|
||||||
if ($file->getLineExecutedPercent() != '0.00%') {
|
|
||||||
$linesExecutedTakenSize = strlen($uncoveredLines = trim(implode(', ', self::getMissingCoverage($file)))) + 1;
|
|
||||||
$name .= sprintf(' <fg=red>%s</>', $uncoveredLines);
|
|
||||||
}
|
|
||||||
|
|
||||||
$percentage = $file->getNumExecutableLines() === 0
|
|
||||||
? '100.0'
|
|
||||||
: number_format((float) $file->getLineExecutedPercent(), 1, '.', '');
|
|
||||||
|
|
||||||
$takenSize = strlen($rawName . $percentage) + 4 + $linesExecutedTakenSize; // adding 3 space and percent sign
|
|
||||||
|
|
||||||
$percentage = sprintf(
|
|
||||||
'<fg=%s>%s</>',
|
|
||||||
$percentage === '100.0' ? 'green' : ($percentage === '0.0' ? 'red' : 'yellow'),
|
|
||||||
$percentage
|
|
||||||
);
|
|
||||||
|
|
||||||
$output->writeln(sprintf(' %s %s %s %%',
|
|
||||||
$name,
|
|
||||||
str_repeat('.', max($dottedLineLength - $takenSize, 1)),
|
|
||||||
$percentage
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (float) $totalCoverage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates an array of missing coverage on the following format:.
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* ['11', '20..25', '50', '60...80'];
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param File $file
|
|
||||||
*
|
|
||||||
* @return array<int, string>
|
|
||||||
*/
|
|
||||||
public static function getMissingCoverage($file): array
|
|
||||||
{
|
|
||||||
$shouldBeNewLine = true;
|
|
||||||
|
|
||||||
$eachLine = function (array $array, array $tests, int $line) use (&$shouldBeNewLine): array {
|
|
||||||
if (count($tests) > 0) {
|
|
||||||
$shouldBeNewLine = true;
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($shouldBeNewLine) {
|
|
||||||
$array[] = (string) $line;
|
|
||||||
$shouldBeNewLine = false;
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
|
|
||||||
$lastKey = count($array) - 1;
|
|
||||||
|
|
||||||
if (array_key_exists($lastKey, $array) && strpos($array[$lastKey], '..') !== false) {
|
|
||||||
[$from] = explode('..', $array[$lastKey]);
|
|
||||||
$array[$lastKey] = sprintf('%s..%s', $from, $line);
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
|
|
||||||
$array[$lastKey] = sprintf('%s..%s', $array[$lastKey], $line);
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
};
|
|
||||||
|
|
||||||
$array = [];
|
|
||||||
foreach (array_filter($file->getCoverageData(), 'is_array') as $line => $tests) {
|
|
||||||
$array = $eachLine($array, $tests, $line);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
19
src/Contracts/Plugins/AddsOutput.php
Normal file
19
src/Contracts/Plugins/AddsOutput.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Contracts\Plugins;
|
||||||
|
|
||||||
|
use Pest\TestSuite;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface AddsOutput
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Allows to add custom output after the test suite was executed.
|
||||||
|
*/
|
||||||
|
public function addOutput(TestSuite $testSuite, OutputInterface $output, int $testReturnCode): void;
|
||||||
|
}
|
||||||
25
src/Contracts/Plugins/HandlesArguments.php
Normal file
25
src/Contracts/Plugins/HandlesArguments.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Contracts\Plugins;
|
||||||
|
|
||||||
|
use Pest\TestSuite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface HandlesArguments
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Allows to handle custom command line arguments.
|
||||||
|
*
|
||||||
|
* PLEASE NOTE: it is necessary to remove any custom argument from the array
|
||||||
|
* because otherwise the application will complain about them
|
||||||
|
*
|
||||||
|
* @param array<int, string> $arguments
|
||||||
|
*
|
||||||
|
* @return array<int, string> the updated list of arguments
|
||||||
|
*/
|
||||||
|
public function handleArguments(TestSuite $testSuite, array $arguments): array;
|
||||||
|
}
|
||||||
@ -98,10 +98,6 @@
|
|||||||
PASS Tests\Plugins\Traits
|
PASS Tests\Plugins\Traits
|
||||||
✓ it allows global uses
|
✓ it allows global uses
|
||||||
|
|
||||||
PASS Tests\Unit\Actions\AddsCoverage
|
|
||||||
✓ it adds coverage if --coverage exist
|
|
||||||
✓ it adds coverage if --min exist
|
|
||||||
|
|
||||||
PASS Tests\Unit\Actions\AddsDefaults
|
PASS Tests\Unit\Actions\AddsDefaults
|
||||||
✓ it sets defaults
|
✓ it sets defaults
|
||||||
✓ it does not override options
|
✓ it does not override options
|
||||||
@ -115,9 +111,6 @@
|
|||||||
✓ it throws exception when `process isolation` is true
|
✓ it throws exception when `process isolation` is true
|
||||||
✓ it do not throws exception when `process isolation` is false
|
✓ it do not throws exception when `process isolation` is false
|
||||||
|
|
||||||
PASS Tests\Unit\Console\Coverage
|
|
||||||
✓ it generates coverage based on file input
|
|
||||||
|
|
||||||
PASS Tests\Unit\Support\Backtrace
|
PASS Tests\Unit\Support\Backtrace
|
||||||
✓ it gets file name from called file
|
✓ it gets file name from called file
|
||||||
|
|
||||||
@ -135,5 +128,5 @@
|
|||||||
WARN Tests\Visual\Success
|
WARN Tests\Visual\Success
|
||||||
s visual snapshot of test suite on success
|
s visual snapshot of test suite on success
|
||||||
|
|
||||||
Tests: 6 skipped, 70 passed
|
Tests: 6 skipped, 67 passed
|
||||||
Time: 2.68s
|
Time: 2.67s
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Pest\Actions\AddsCoverage;
|
|
||||||
use Pest\TestSuite;
|
|
||||||
|
|
||||||
it('adds coverage if --coverage exist', function () {
|
|
||||||
$testSuite = new TestSuite(getcwd());
|
|
||||||
assertFalse($testSuite->coverage);
|
|
||||||
|
|
||||||
$arguments = AddsCoverage::from($testSuite, []);
|
|
||||||
assertEquals([], $arguments);
|
|
||||||
assertFalse($testSuite->coverage);
|
|
||||||
|
|
||||||
$arguments = AddsCoverage::from($testSuite, ['--coverage']);
|
|
||||||
assertEquals(['--coverage-php', \Pest\Console\Coverage::getPath()], $arguments);
|
|
||||||
assertTrue($testSuite->coverage);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds coverage if --min exist', function () {
|
|
||||||
$testSuite = new TestSuite(getcwd());
|
|
||||||
assertEquals($testSuite->coverageMin, 0.0);
|
|
||||||
|
|
||||||
assertFalse($testSuite->coverage);
|
|
||||||
AddsCoverage::from($testSuite, []);
|
|
||||||
assertEquals($testSuite->coverageMin, 0.0);
|
|
||||||
|
|
||||||
AddsCoverage::from($testSuite, ['--min=2']);
|
|
||||||
assertEquals($testSuite->coverageMin, 2.0);
|
|
||||||
|
|
||||||
AddsCoverage::from($testSuite, ['--min=2.4']);
|
|
||||||
assertEquals($testSuite->coverageMin, 2.4);
|
|
||||||
});
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Pest\Console\Coverage;
|
|
||||||
|
|
||||||
it('generates coverage based on file input', function () {
|
|
||||||
assertEquals([
|
|
||||||
'4..6', '102',
|
|
||||||
], Coverage::getMissingCoverage(new class() {
|
|
||||||
public function getCoverageData(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
1 => ['foo'],
|
|
||||||
2 => ['bar'],
|
|
||||||
4 => [],
|
|
||||||
5 => [],
|
|
||||||
6 => [],
|
|
||||||
7 => null,
|
|
||||||
100 => null,
|
|
||||||
101 => ['foo'],
|
|
||||||
102 => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user