mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 07:47:22 +01:00
Fixes and improvements.
This commit is contained in:
committed by
Nuno Maduro
parent
7466667c08
commit
2f519261f5
@ -5,10 +5,36 @@ declare(strict_types=1);
|
|||||||
use ParaTest\WrapperRunner\ApplicationForWrapperWorker;
|
use ParaTest\WrapperRunner\ApplicationForWrapperWorker;
|
||||||
use ParaTest\WrapperRunner\WrapperWorker;
|
use ParaTest\WrapperRunner\WrapperWorker;
|
||||||
use Pest\ConfigLoader;
|
use Pest\ConfigLoader;
|
||||||
|
use Pest\Kernel;
|
||||||
|
use Pest\Plugins\Actions\CallsAddsOutput;
|
||||||
|
use Pest\Plugins\Actions\CallsHandleArguments;
|
||||||
|
use Pest\Support\Container;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
(static function (): void {
|
$bootPest = (static function (): void {
|
||||||
|
$argv = new ArgvInput();
|
||||||
|
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);
|
||||||
|
$testSuite = TestSuite::getInstance(
|
||||||
|
$rootPath,
|
||||||
|
$argv->getParameterOption('--test-directory', (new ConfigLoader($rootPath))->getTestsDirectory()),
|
||||||
|
);
|
||||||
|
|
||||||
|
$output = new ConsoleOutput(OutputInterface::VERBOSITY_NORMAL, true);
|
||||||
|
|
||||||
|
$container = Container::getInstance();
|
||||||
|
$container->add(TestSuite::class, $testSuite);
|
||||||
|
$container->add(OutputInterface::class, $output);
|
||||||
|
$container->add(InputInterface::class, $argv);
|
||||||
|
$container->add(Container::class, $container);
|
||||||
|
|
||||||
|
Kernel::boot();
|
||||||
|
});
|
||||||
|
|
||||||
|
(static function () use ($bootPest): void {
|
||||||
$getopt = getopt('', [
|
$getopt = getopt('', [
|
||||||
'status-file:',
|
'status-file:',
|
||||||
'progress-file:',
|
'progress-file:',
|
||||||
@ -50,16 +76,9 @@ use Symfony\Component\Console\Input\ArgvInput;
|
|||||||
$phpunitArgv = unserialize($getopt['phpunit-argv'], ['allowed_classes' => false]);
|
$phpunitArgv = unserialize($getopt['phpunit-argv'], ['allowed_classes' => false]);
|
||||||
assert(is_array($phpunitArgv));
|
assert(is_array($phpunitArgv));
|
||||||
|
|
||||||
/**
|
$bootPest();
|
||||||
* We need to instantiate the Pest Test suite instance
|
|
||||||
* so that Pest is able to execute correctly.
|
(new CallsHandleArguments())($phpunitArgv);
|
||||||
*/
|
|
||||||
$argv = new ArgvInput();
|
|
||||||
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);
|
|
||||||
TestSuite::getInstance(
|
|
||||||
$rootPath,
|
|
||||||
$argv->getParameterOption('--test-directory', (new ConfigLoader($rootPath))->getTestsDirectory()),
|
|
||||||
);
|
|
||||||
|
|
||||||
$application = new ApplicationForWrapperWorker(
|
$application = new ApplicationForWrapperWorker(
|
||||||
$phpunitArgv,
|
$phpunitArgv,
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"brianium/paratest": "^7.0",
|
"brianium/paratest": "^7.0.4",
|
||||||
"pestphp/pest-dev-tools": "^2.4.0",
|
"pestphp/pest-dev-tools": "^2.4.0",
|
||||||
"pestphp/pest-plugin-arch": "^2.0.0",
|
"pestphp/pest-plugin-arch": "^2.0.0",
|
||||||
"symfony/process": "^6.2.5"
|
"symfony/process": "^6.2.5"
|
||||||
|
|||||||
@ -12,6 +12,10 @@ RUN docker-php-ext-configure intl
|
|||||||
RUN docker-php-ext-install intl
|
RUN docker-php-ext-install intl
|
||||||
RUN docker-php-ext-enable intl
|
RUN docker-php-ext-enable intl
|
||||||
|
|
||||||
|
RUN apk add --no-cache $PHPIZE_DEPS linux-headers
|
||||||
|
RUN pecl install xdebug
|
||||||
|
RUN docker-php-ext-enable xdebug
|
||||||
|
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|||||||
@ -7,6 +7,7 @@ namespace Pest\Logging\TeamCity;
|
|||||||
use NunoMaduro\Collision\Adapters\Phpunit\State;
|
use NunoMaduro\Collision\Adapters\Phpunit\State;
|
||||||
use NunoMaduro\Collision\Adapters\Phpunit\TestResult;
|
use NunoMaduro\Collision\Adapters\Phpunit\TestResult;
|
||||||
use Pest\Exceptions\ShouldNotHappen;
|
use Pest\Exceptions\ShouldNotHappen;
|
||||||
|
use Pest\Support\StateGenerator;
|
||||||
use Pest\Support\Str;
|
use Pest\Support\Str;
|
||||||
use PHPUnit\Event\Code\Test;
|
use PHPUnit\Event\Code\Test;
|
||||||
use PHPUnit\Event\Code\TestDox;
|
use PHPUnit\Event\Code\TestDox;
|
||||||
@ -28,12 +29,15 @@ final class Converter
|
|||||||
{
|
{
|
||||||
private const PREFIX = 'P\\';
|
private const PREFIX = 'P\\';
|
||||||
|
|
||||||
|
private readonly StateGenerator $stateGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the Converter.
|
* Creates a new instance of the Converter.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly string $rootPath,
|
private readonly string $rootPath,
|
||||||
) {
|
) {
|
||||||
|
$this->stateGenerator = new StateGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -183,83 +187,6 @@ final class Converter
|
|||||||
*/
|
*/
|
||||||
public function getStateFromResult(PhpUnitTestResult $result): State
|
public function getStateFromResult(PhpUnitTestResult $result): State
|
||||||
{
|
{
|
||||||
$state = new State();
|
return $this->stateGenerator->fromPhpUnitTestResult($result);
|
||||||
|
|
||||||
foreach ($result->testErroredEvents() as $resultEvent) {
|
|
||||||
assert($resultEvent instanceof Errored);
|
|
||||||
$state->add(TestResult::fromTestCase(
|
|
||||||
$resultEvent->test(),
|
|
||||||
TestResult::FAIL,
|
|
||||||
$resultEvent->throwable()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($result->testFailedEvents() as $resultEvent) {
|
|
||||||
$state->add(TestResult::fromTestCase(
|
|
||||||
$resultEvent->test(),
|
|
||||||
TestResult::FAIL,
|
|
||||||
$resultEvent->throwable()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($result->testMarkedIncompleteEvents() as $resultEvent) {
|
|
||||||
$state->add(TestResult::fromTestCase(
|
|
||||||
$resultEvent->test(),
|
|
||||||
TestResult::INCOMPLETE,
|
|
||||||
$resultEvent->throwable()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($result->testConsideredRiskyEvents() as $riskyEvents) {
|
|
||||||
foreach ($riskyEvents as $riskyEvent) {
|
|
||||||
$state->add(TestResult::fromTestCase(
|
|
||||||
$riskyEvent->test(),
|
|
||||||
TestResult::RISKY,
|
|
||||||
Throwable::from(new IncompleteTestError($riskyEvent->message()))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($result->testSkippedEvents() as $resultEvent) {
|
|
||||||
if ($resultEvent->message() === '__TODO__') {
|
|
||||||
$state->add(TestResult::fromTestCase($resultEvent->test(), TestResult::TODO));
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$state->add(TestResult::fromTestCase(
|
|
||||||
$resultEvent->test(),
|
|
||||||
TestResult::SKIPPED,
|
|
||||||
Throwable::from(new SkippedWithMessageException($resultEvent->message()))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$numberOfPassedTests = $result->numberOfTests()
|
|
||||||
- $result->numberOfTestErroredEvents()
|
|
||||||
- $result->numberOfTestFailedEvents()
|
|
||||||
- $result->numberOfTestSkippedEvents()
|
|
||||||
- $result->numberOfTestsWithTestConsideredRiskyEvents()
|
|
||||||
- $result->numberOfTestMarkedIncompleteEvents();
|
|
||||||
|
|
||||||
for ($i = 0; $i < $numberOfPassedTests; $i++) {
|
|
||||||
$state->add(TestResult::fromTestCase(
|
|
||||||
|
|
||||||
new TestMethod(
|
|
||||||
/** @phpstan-ignore-next-line */
|
|
||||||
"$i",
|
|
||||||
/** @phpstan-ignore-next-line */
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
1,
|
|
||||||
/** @phpstan-ignore-next-line */
|
|
||||||
TestDox::fromClassNameAndMethodName('', ''),
|
|
||||||
MetadataCollection::fromArray([]),
|
|
||||||
TestDataCollection::fromArray([])
|
|
||||||
),
|
|
||||||
TestResult::PASS
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $state;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,14 @@ namespace Pest\Plugins;
|
|||||||
|
|
||||||
use ParaTest\ParaTestCommand;
|
use ParaTest\ParaTestCommand;
|
||||||
use Pest\Contracts\Plugins\HandlesArguments;
|
use Pest\Contracts\Plugins\HandlesArguments;
|
||||||
|
use Pest\Plugins\Actions\CallsAddsOutput;
|
||||||
use Pest\Plugins\Concerns\HandleArguments;
|
use Pest\Plugins\Concerns\HandleArguments;
|
||||||
use Pest\Support\Arr;
|
use Pest\Support\Arr;
|
||||||
use Pest\Support\Container;
|
use Pest\Support\Container;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use function Pest\version;
|
||||||
|
|
||||||
final class Parallel implements HandlesArguments
|
final class Parallel implements HandlesArguments
|
||||||
{
|
{
|
||||||
@ -54,7 +56,14 @@ final class Parallel implements HandlesArguments
|
|||||||
|
|
||||||
$testSuite = TestSuite::getInstance();
|
$testSuite = TestSuite::getInstance();
|
||||||
|
|
||||||
return ParaTestCommand::applicationFactory($testSuite->rootPath)->run(new ArgvInput($filteredArguments));
|
$command = ParaTestCommand::applicationFactory($testSuite->rootPath);
|
||||||
|
$command->setAutoExit(false);
|
||||||
|
$command->setName('Pest');
|
||||||
|
$command->setVersion(version());
|
||||||
|
$exitCode = $command->run(new ArgvInput($filteredArguments));
|
||||||
|
|
||||||
|
$exitCode = (new CallsAddsOutput())($exitCode);
|
||||||
|
exit($exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function markTestSuiteAsParallelSubProcessIfRequired(): void
|
private function markTestSuiteAsParallelSubProcessIfRequired(): void
|
||||||
|
|||||||
@ -5,28 +5,20 @@ declare(strict_types=1);
|
|||||||
namespace Pest\Plugins\Parallel\Paratest;
|
namespace Pest\Plugins\Parallel\Paratest;
|
||||||
|
|
||||||
use NunoMaduro\Collision\Adapters\Phpunit\TestResult as CollisionTestResult;
|
use NunoMaduro\Collision\Adapters\Phpunit\TestResult as CollisionTestResult;
|
||||||
use NunoMaduro\Collision\Exceptions\TestException;
|
|
||||||
use ParaTest\Options;
|
use ParaTest\Options;
|
||||||
use Pest\Plugins\Parallel\Support\CompactPrinter;
|
use Pest\Plugins\Parallel\Support\CompactPrinter;
|
||||||
use PHPUnit\Runner\TestSuiteSorter;
|
use Pest\Support\StateGenerator;
|
||||||
|
use PHPUnit\Event\Test\Errored;
|
||||||
use PHPUnit\TestRunner\TestResult\TestResult;
|
use PHPUnit\TestRunner\TestResult\TestResult;
|
||||||
use PHPUnit\TextUI\Output\Default\ResultPrinter as DefaultResultPrinter;
|
|
||||||
use PHPUnit\TextUI\Output\Printer;
|
use PHPUnit\TextUI\Output\Printer;
|
||||||
use PHPUnit\TextUI\Output\SummaryPrinter;
|
|
||||||
use PHPUnit\Util\Color;
|
|
||||||
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
|
||||||
use SebastianBergmann\CodeCoverage\Filter;
|
|
||||||
use SebastianBergmann\Timer\Duration;
|
use SebastianBergmann\Timer\Duration;
|
||||||
use SebastianBergmann\Timer\ResourceUsageFormatter;
|
|
||||||
use SplFileInfo;
|
use SplFileInfo;
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
use Termwind\Terminal;
|
|
||||||
use function assert;
|
use function assert;
|
||||||
use function fclose;
|
use function fclose;
|
||||||
use function feof;
|
use function feof;
|
||||||
use function floor;
|
|
||||||
use function fopen;
|
use function fopen;
|
||||||
use function fread;
|
use function fread;
|
||||||
use function fseek;
|
use function fseek;
|
||||||
@ -34,26 +26,16 @@ use function ftell;
|
|||||||
use function fwrite;
|
use function fwrite;
|
||||||
use function preg_replace;
|
use function preg_replace;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function str_repeat;
|
|
||||||
use function strlen;
|
use function strlen;
|
||||||
|
|
||||||
use function Termwind\terminal;
|
|
||||||
use const DIRECTORY_SEPARATOR;
|
|
||||||
use const PHP_EOL;
|
|
||||||
use const PHP_VERSION;
|
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
final class ResultPrinter
|
final class ResultPrinter
|
||||||
{
|
{
|
||||||
public readonly Printer $printer;
|
public readonly Printer $printer;
|
||||||
private readonly CompactPrinter $compactPrinter;
|
private readonly CompactPrinter $compactPrinter;
|
||||||
|
|
||||||
private int $numTestsWidth = 0;
|
|
||||||
private int $maxColumn = 0;
|
|
||||||
private int $totalCases = 0;
|
private int $totalCases = 0;
|
||||||
private int $column = 0;
|
|
||||||
private int $casesProcessed = 0;
|
|
||||||
private int $numberOfColumns = 80;
|
|
||||||
/** @var resource|null */
|
/** @var resource|null */
|
||||||
private $teamcityLogFileHandle;
|
private $teamcityLogFileHandle;
|
||||||
/** @var array<non-empty-string, int> */
|
/** @var array<non-empty-string, int> */
|
||||||
@ -98,7 +80,7 @@ final class ResultPrinter
|
|||||||
public function start(): void
|
public function start(): void
|
||||||
{
|
{
|
||||||
$this->compactPrinter->line(sprintf(
|
$this->compactPrinter->line(sprintf(
|
||||||
'Running %d test file%s using %d process%s',
|
'Running %d test%s using %d process%s',
|
||||||
$this->totalCases,
|
$this->totalCases,
|
||||||
$this->totalCases === 1 ? '' : 's',
|
$this->totalCases === 1 ? '' : 's',
|
||||||
$this->options->processes,
|
$this->options->processes,
|
||||||
@ -173,14 +155,10 @@ final class ResultPrinter
|
|||||||
|
|
||||||
$this->compactPrinter->newLine();
|
$this->compactPrinter->newLine();
|
||||||
|
|
||||||
$issues = array_map(fn($event) => CollisionTestResult::fromTestCase(
|
$state = (new StateGenerator())->fromPhpUnitTestResult($testResult);
|
||||||
$event->test(),
|
|
||||||
CollisionTestResult::FAIL,
|
|
||||||
$event->throwable(),
|
|
||||||
), [...$testResult->testFailedEvents(), ...$testResult->testErroredEvents()]);
|
|
||||||
|
|
||||||
$this->compactPrinter->errors($issues);
|
$this->compactPrinter->errors($state);
|
||||||
$this->compactPrinter->recap($testResult, $duration);
|
$this->compactPrinter->recap($state, $testResult, $duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function printFeedbackItem(string $item): void
|
private function printFeedbackItem(string $item): void
|
||||||
|
|||||||
@ -16,6 +16,7 @@ use PHPUnit\Event\Facade as EventFacade;
|
|||||||
use PHPUnit\Runner\CodeCoverage;
|
use PHPUnit\Runner\CodeCoverage;
|
||||||
use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade;
|
use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade;
|
||||||
use PHPUnit\TestRunner\TestResult\TestResult;
|
use PHPUnit\TestRunner\TestResult\TestResult;
|
||||||
|
use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
|
||||||
use PHPUnit\TextUI\ShellExitCodeCalculator;
|
use PHPUnit\TextUI\ShellExitCodeCalculator;
|
||||||
use PHPUnit\Util\ExcludeList;
|
use PHPUnit\Util\ExcludeList;
|
||||||
use SebastianBergmann\Timer\Timer;
|
use SebastianBergmann\Timer\Timer;
|
||||||
@ -67,6 +68,8 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
/** @var non-empty-string[] */
|
/** @var non-empty-string[] */
|
||||||
private readonly array $parameters;
|
private readonly array $parameters;
|
||||||
|
|
||||||
|
private CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Options $options,
|
private readonly Options $options,
|
||||||
private readonly OutputInterface $output
|
private readonly OutputInterface $output
|
||||||
@ -92,6 +95,7 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
$parameters[] = $wrapper;
|
$parameters[] = $wrapper;
|
||||||
|
|
||||||
$this->parameters = $parameters;
|
$this->parameters = $parameters;
|
||||||
|
$this->codeCoverageFilterRegistry = new CodeCoverageFilterRegistry();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run(): int
|
public function run(): int
|
||||||
@ -99,7 +103,7 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
ExcludeList::addDirectory(dirname(__DIR__));
|
ExcludeList::addDirectory(dirname(__DIR__));
|
||||||
TestResultFacade::init();
|
TestResultFacade::init();
|
||||||
EventFacade::seal();
|
EventFacade::seal();
|
||||||
$suiteLoader = new SuiteLoader($this->options, $this->output);
|
$suiteLoader = new SuiteLoader($this->options, $this->output, $this->codeCoverageFilterRegistry,);
|
||||||
$this->pending = $this->getTestFiles($suiteLoader);
|
$this->pending = $this->getTestFiles($suiteLoader);
|
||||||
|
|
||||||
$result = TestResultFacade::result();
|
$result = TestResultFacade::result();
|
||||||
@ -116,11 +120,6 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
return $this->complete($result);
|
return $this->complete($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getExitCode(): int
|
|
||||||
{
|
|
||||||
return $this->exitcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function startWorkers(): void
|
private function startWorkers(): void
|
||||||
{
|
{
|
||||||
for ($token = 1; $token <= $this->options->processes; ++$token) {
|
for ($token = 1; $token <= $this->options->processes; ++$token) {
|
||||||
@ -155,6 +154,8 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
) {
|
) {
|
||||||
$this->pending = [];
|
$this->pending = [];
|
||||||
} elseif (($pending = array_shift($this->pending)) !== null) {
|
} elseif (($pending = array_shift($this->pending)) !== null) {
|
||||||
|
$this->debug(sprintf('Assigning %s to worker %d', $pending, $token));
|
||||||
|
|
||||||
$worker->assign($pending);
|
$worker->assign($pending);
|
||||||
$this->batches[$token]++;
|
$this->batches[$token]++;
|
||||||
}
|
}
|
||||||
@ -312,13 +313,14 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeCoverage::init($this->options->configuration);
|
$coverageManager = new CodeCoverage();
|
||||||
$coverageMerger = new CoverageMerger(CodeCoverage::instance());
|
$coverageManager->init($this->options->configuration, $this->codeCoverageFilterRegistry);
|
||||||
|
$coverageMerger = new CoverageMerger($coverageManager->codeCoverage());
|
||||||
foreach ($this->coverageFiles as $coverageFile) {
|
foreach ($this->coverageFiles as $coverageFile) {
|
||||||
$coverageMerger->addCoverageFromFile($coverageFile);
|
$coverageMerger->addCoverageFromFile($coverageFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeCoverage::generateReports(
|
$coverageManager->generateReports(
|
||||||
$this->printer->printer,
|
$this->printer->printer,
|
||||||
$this->options->configuration,
|
$this->options->configuration,
|
||||||
);
|
);
|
||||||
@ -349,14 +351,13 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We are doing this because the SuiteLoader returns filenames incorrectly
|
||||||
|
* for Pest tests. Ideally we should find a cleaner solution.
|
||||||
|
*/
|
||||||
private function getTestFiles(SuiteLoader $suiteLoader): array
|
private function getTestFiles(SuiteLoader $suiteLoader): array
|
||||||
{
|
{
|
||||||
/**
|
$this->debug(sprintf("Found %d test file%s", count($suiteLoader->files), count($suiteLoader->files) === 1 ? '' : 's'));
|
||||||
* TODO: Clean this up
|
|
||||||
*
|
|
||||||
* We are doing this because the SuiteLoader returns filenames incorrectly
|
|
||||||
* for Pest tests. We need to find a better way to do this.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$tests = array_filter(
|
$tests = array_filter(
|
||||||
$suiteLoader->files,
|
$suiteLoader->files,
|
||||||
@ -365,4 +366,11 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
return [...$tests, ...TestSuite::getInstance()->tests->getFilenames()];
|
return [...$tests, ...TestSuite::getInstance()->tests->getFilenames()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function debug(string $message): void
|
||||||
|
{
|
||||||
|
if ($this->options->verbose) {
|
||||||
|
$this->output->writeln(" <fg=blue>{$message}</> ");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,26 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Plugins\Parallel\Support;
|
namespace Pest\Plugins\Parallel\Support;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Adapters\Phpunit\Printers\DefaultPrinter;
|
||||||
use NunoMaduro\Collision\Adapters\Phpunit\State;
|
use NunoMaduro\Collision\Adapters\Phpunit\State;
|
||||||
use NunoMaduro\Collision\Adapters\Phpunit\Style;
|
use NunoMaduro\Collision\Adapters\Phpunit\Style;
|
||||||
use NunoMaduro\Collision\Adapters\Phpunit\TestResult;
|
use NunoMaduro\Collision\Adapters\Phpunit\TestResult;
|
||||||
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
|
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
|
||||||
|
use Pest\Logging\TeamCity\Converter;
|
||||||
|
use Pest\Support\StateGenerator;
|
||||||
|
use PHPUnit\Event\Code\TestDox;
|
||||||
|
use PHPUnit\Event\Code\TestMethod;
|
||||||
|
use PHPUnit\Event\Event;
|
||||||
|
use PHPUnit\Event\Telemetry\HRTime;
|
||||||
|
use PHPUnit\Event\Telemetry\Info;
|
||||||
|
use PHPUnit\Event\Telemetry\MemoryUsage;
|
||||||
|
use PHPUnit\Event\Telemetry\Snapshot;
|
||||||
|
use PHPUnit\Event\Test\Passed;
|
||||||
|
use PHPUnit\Event\TestData\TestDataCollection;
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
use PHPUnit\Framework\IncompleteTestError;
|
use PHPUnit\Framework\IncompleteTestError;
|
||||||
|
use PHPUnit\Metadata\MetadataCollection;
|
||||||
|
use PHPUnit\TestRunner\TestResult\TestResult as PHPUnitTestResult;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use SebastianBergmann\Timer\Duration;
|
use SebastianBergmann\Timer\Duration;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
@ -49,22 +63,20 @@ final class CompactPrinter
|
|||||||
|
|
||||||
public function descriptionItem(string $item): void
|
public function descriptionItem(string $item): void
|
||||||
{
|
{
|
||||||
// TODO: Support todos
|
// TODO: Support TODOs
|
||||||
|
|
||||||
$icon = match (strtolower($item)) {
|
$lookupTable = [
|
||||||
'f', 'e' => '⨯', // FAILED
|
'.' => ['gray', '.'],
|
||||||
's' => 's', // SKIPPED
|
'S' => ['yellow', 's'],
|
||||||
'w', 'r' => '!', // WARN, RISKY
|
'I' => ['yellow', 'i'],
|
||||||
'i' => '…', // INCOMPLETE
|
'N' => ['yellow', 'i'],
|
||||||
'.' => '.', // PASSED
|
'R' => ['yellow', '!'],
|
||||||
default => $item,
|
'W' => ['yellow', '!'],
|
||||||
};
|
'E' => ['red', '⨯'],
|
||||||
|
'F' => ['red', '⨯'],
|
||||||
|
];
|
||||||
|
|
||||||
$color = match (strtolower($item)) {
|
[$color, $icon] = $lookupTable[$item] ?? $lookupTable['.'];
|
||||||
'f', 'e' => 'red',
|
|
||||||
'd', 's', 'i', 'r', 'w' => 'yellow',
|
|
||||||
default => 'gray',
|
|
||||||
};
|
|
||||||
|
|
||||||
$symbolsOnCurrentLine = $this->compactProcessed % $this->compactSymbolsPerLine;
|
$symbolsOnCurrentLine = $this->compactProcessed % $this->compactSymbolsPerLine;
|
||||||
|
|
||||||
@ -80,102 +92,34 @@ final class CompactPrinter
|
|||||||
$this->output->write(sprintf('<fg=%s;options=bold>%s</>', $color, $icon));
|
$this->output->write(sprintf('<fg=%s;options=bold>%s</>', $color, $icon));
|
||||||
|
|
||||||
$this->compactProcessed++;
|
$this->compactProcessed++;
|
||||||
|
|
||||||
//switch ($item) {
|
|
||||||
// case self::TODO:
|
|
||||||
// return '↓';
|
|
||||||
// case self::RUNS:
|
|
||||||
// return '•';
|
|
||||||
// default:
|
|
||||||
// return '✓';
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function errors(array $errors): void
|
public function errors(State $state): void
|
||||||
{
|
{
|
||||||
array_map(function (TestResult $testResult): void {
|
$this->style->writeErrorsSummary($state, false);
|
||||||
if (! $testResult->throwable instanceof \PHPUnit\Event\Code\Throwable) {
|
|
||||||
throw new ShouldNotHappen();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderUsing($this->output);
|
|
||||||
render(<<<'HTML'
|
|
||||||
<div class="mx-2 text-red">
|
|
||||||
<hr/>
|
|
||||||
</div>
|
|
||||||
HTML);
|
|
||||||
|
|
||||||
$testCaseName = $testResult->testCaseName;
|
|
||||||
$description = $testResult->description;
|
|
||||||
|
|
||||||
/** @var class-string $throwableClassName */
|
|
||||||
$throwableClassName = $testResult->throwable->className();
|
|
||||||
|
|
||||||
$throwableClassName = ! in_array($throwableClassName, [
|
|
||||||
ExpectationFailedException::class,
|
|
||||||
IncompleteTestError::class,
|
|
||||||
], true) ? sprintf('<span class="px-1 bg-red font-bold">%s</span>', (new ReflectionClass($throwableClassName))->getShortName())
|
|
||||||
: '';
|
|
||||||
|
|
||||||
$truncateClasses = $this->output->isVerbose() ? '' : 'flex-1 truncate';
|
|
||||||
|
|
||||||
renderUsing($this->output);
|
|
||||||
render(sprintf(<<<'HTML'
|
|
||||||
<div class="flex justify-between mx-2">
|
|
||||||
<span class="%s">
|
|
||||||
<span class="px-1 bg-%s font-bold uppercase">%s</span> <span class="font-bold">%s</span><span class="text-gray mx-1">></span><span>%s</span>
|
|
||||||
</span>
|
|
||||||
<span class="ml-1">
|
|
||||||
%s
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
HTML, $truncateClasses, $testResult->color, $testResult->type, $testCaseName, $description, $throwableClassName));
|
|
||||||
|
|
||||||
$this->style->writeError($testResult->throwable);
|
|
||||||
}, $errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function recap(\PHPUnit\TestRunner\TestResult\TestResult $testResult, Duration $duration): void
|
public function recap(State $state, PHPUnitTestResult $testResult, Duration $duration): void
|
||||||
{
|
{
|
||||||
$testCounts = [
|
assert($this->output instanceof ConsoleOutput);
|
||||||
'passed' => ['green', $testResult->numberOfTestsRun()],
|
$style = new Style($this->output);
|
||||||
'failed' => ['red', $testResult->numberOfTestFailedEvents()],
|
|
||||||
'errored' => ['red', $testResult->numberOfTestErroredEvents()],
|
|
||||||
'skipped' => ['yellow', $testResult->numberOfTestSkippedEvents()],
|
|
||||||
'incomplete' => ['yellow', $testResult->numberOfTestMarkedIncompleteEvents()],
|
|
||||||
'risky' => ['yellow', $testResult->numberOfTestsWithTestConsideredRiskyEvents()],
|
|
||||||
'warnings' => ['yellow', $testResult->numberOfTestsWithTestTriggeredWarningEvents()],
|
|
||||||
];
|
|
||||||
|
|
||||||
$tests = [];
|
$nanoseconds = $duration->asNanoseconds() % 1000000000;
|
||||||
|
$snapshotDuration = HRTime::fromSecondsAndNanoseconds((int)$duration->asSeconds(), $nanoseconds);
|
||||||
|
$telemetryDuration = \PHPUnit\Event\Telemetry\Duration::fromSecondsAndNanoseconds((int)$duration->asSeconds(), $nanoseconds);
|
||||||
|
|
||||||
foreach ($testCounts as $type => [$color, $count]) {
|
$telemetry = new Info(
|
||||||
if ($count === 0) {
|
new Snapshot(
|
||||||
continue;
|
$snapshotDuration,
|
||||||
}
|
MemoryUsage::fromBytes(0),
|
||||||
|
MemoryUsage::fromBytes(0),
|
||||||
$tests[] = "<fg={$color};options=bold>$count $type</>";
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->output->writeln(['']);
|
|
||||||
|
|
||||||
if (! empty($tests)) {
|
|
||||||
$this->output->writeln([
|
|
||||||
sprintf(
|
|
||||||
' <fg=gray>Tests:</> <fg=default>%s</><fg=gray> (%s assertions)</>',
|
|
||||||
implode('<fg=gray>,</> ', $tests),
|
|
||||||
$testResult->numberOfAssertions()
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->output->writeln([
|
|
||||||
sprintf(
|
|
||||||
' <fg=gray>Duration:</> <fg=default>%ss</>',
|
|
||||||
number_format($duration->asSeconds(), 2, '.', '')
|
|
||||||
),
|
),
|
||||||
]);
|
$telemetryDuration,
|
||||||
|
MemoryUsage::fromBytes(0),
|
||||||
|
\PHPUnit\Event\Telemetry\Duration::fromSecondsAndNanoseconds(0, 0),
|
||||||
|
MemoryUsage::fromBytes(0),
|
||||||
|
);
|
||||||
|
|
||||||
$this->output->writeln('');
|
$style->writeRecap($state, $telemetry, $testResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
src/Support/StateGenerator.php
Normal file
102
src/Support/StateGenerator.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Support;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Adapters\Phpunit\State;
|
||||||
|
use NunoMaduro\Collision\Adapters\Phpunit\TestResult;
|
||||||
|
use PHPUnit\Event\Code\TestDox;
|
||||||
|
use PHPUnit\Event\Code\TestMethod;
|
||||||
|
use PHPUnit\Event\Code\Throwable;
|
||||||
|
use PHPUnit\Event\Test\Errored;
|
||||||
|
use PHPUnit\Event\TestData\TestDataCollection;
|
||||||
|
use PHPUnit\Framework\IncompleteTestError;
|
||||||
|
use PHPUnit\Framework\SkippedWithMessageException;
|
||||||
|
use PHPUnit\Metadata\MetadataCollection;
|
||||||
|
use PHPUnit\TestRunner\TestResult\TestResult as PHPUnitTestResult;
|
||||||
|
|
||||||
|
final class StateGenerator
|
||||||
|
{
|
||||||
|
public function fromPhpUnitTestResult(PHPUnitTestResult $testResult): State
|
||||||
|
{
|
||||||
|
$state = new State();
|
||||||
|
|
||||||
|
foreach ($testResult->testErroredEvents() as $testResultEvent) {
|
||||||
|
assert($testResultEvent instanceof Errored);
|
||||||
|
$state->add(\NunoMaduro\Collision\Adapters\Phpunit\TestResult::fromTestCase(
|
||||||
|
$testResultEvent->test(),
|
||||||
|
TestResult::FAIL,
|
||||||
|
$testResultEvent->throwable()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($testResult->testFailedEvents() as $testResultEvent) {
|
||||||
|
$state->add(TestResult::fromTestCase(
|
||||||
|
$testResultEvent->test(),
|
||||||
|
TestResult::FAIL,
|
||||||
|
$testResultEvent->throwable()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($testResult->testMarkedIncompleteEvents() as $testResultEvent) {
|
||||||
|
$state->add(TestResult::fromTestCase(
|
||||||
|
$testResultEvent->test(),
|
||||||
|
TestResult::INCOMPLETE,
|
||||||
|
$testResultEvent->throwable()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($testResult->testConsideredRiskyEvents() as $riskyEvents) {
|
||||||
|
foreach ($riskyEvents as $riskyEvent) {
|
||||||
|
$state->add(TestResult::fromTestCase(
|
||||||
|
$riskyEvent->test(),
|
||||||
|
TestResult::RISKY,
|
||||||
|
Throwable::from(new IncompleteTestError($riskyEvent->message()))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($testResult->testSkippedEvents() as $testResultEvent) {
|
||||||
|
if ($testResultEvent->message() === '__TODO__') {
|
||||||
|
$state->add(TestResult::fromTestCase($testResultEvent->test(), TestResult::TODO));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$state->add(TestResult::fromTestCase(
|
||||||
|
$testResultEvent->test(),
|
||||||
|
TestResult::SKIPPED,
|
||||||
|
Throwable::from(new SkippedWithMessageException($testResultEvent->message()))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$numberOfPassedTests = $testResult->numberOfTestsRun()
|
||||||
|
- $testResult->numberOfTestErroredEvents()
|
||||||
|
- $testResult->numberOfTestFailedEvents()
|
||||||
|
- $testResult->numberOfTestSkippedEvents()
|
||||||
|
- $testResult->numberOfTestsWithTestConsideredRiskyEvents()
|
||||||
|
- $testResult->numberOfTestMarkedIncompleteEvents();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $numberOfPassedTests; $i++) {
|
||||||
|
$state->add(TestResult::fromTestCase(
|
||||||
|
|
||||||
|
new TestMethod(
|
||||||
|
/** @phpstan-ignore-next-line */
|
||||||
|
"$i",
|
||||||
|
/** @phpstan-ignore-next-line */
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
1,
|
||||||
|
/** @phpstan-ignore-next-line */
|
||||||
|
TestDox::fromClassNameAndMethodName('', ''),
|
||||||
|
MetadataCollection::fromArray([]),
|
||||||
|
TestDataCollection::fromArray([])
|
||||||
|
),
|
||||||
|
TestResult::PASS
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $state;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user