mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
WIP
This commit is contained in:
committed by
Nuno Maduro
parent
951b54e7cd
commit
d03302db7b
@ -4,7 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Plugins\Parallel\Handlers;
|
namespace Pest\Plugins\Parallel\Handlers;
|
||||||
|
|
||||||
|
use Illuminate\Testing\ParallelRunner;
|
||||||
|
use ParaTest\Options;
|
||||||
|
use ParaTest\RunnerInterface;
|
||||||
use Pest\Plugins\Concerns\HandleArguments;
|
use Pest\Plugins\Concerns\HandleArguments;
|
||||||
|
use Pest\Plugins\Parallel\Paratest\WrapperRunner;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -19,6 +24,8 @@ final class Laravel
|
|||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->setLaravelParallelRunner();
|
||||||
|
|
||||||
foreach ($args as $value) {
|
foreach ($args as $value) {
|
||||||
if (str_starts_with($value, '--runner')) {
|
if (str_starts_with($value, '--runner')) {
|
||||||
$args = $this->popArgument($value, $args);
|
$args = $this->popArgument($value, $args);
|
||||||
@ -28,6 +35,17 @@ final class Laravel
|
|||||||
return $this->pushArgument('--runner=\Illuminate\Testing\ParallelRunner', $args);
|
return $this->pushArgument('--runner=\Illuminate\Testing\ParallelRunner', $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function setLaravelParallelRunner(): void
|
||||||
|
{
|
||||||
|
if (!method_exists(ParallelRunner::class, 'resolveRunnerUsing')) {
|
||||||
|
exit('Using parallel with Pest requires Laravel v8.55.0 or higher.');
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelRunner::resolveRunnerUsing(function (Options $options, OutputInterface $output): RunnerInterface {
|
||||||
|
return new WrapperRunner($options, $output);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static function isALaravelApplication(): bool
|
private static function isALaravelApplication(): bool
|
||||||
{
|
{
|
||||||
return class_exists(\Illuminate\Foundation\Application::class)
|
return class_exists(\Illuminate\Foundation\Application::class)
|
||||||
|
|||||||
@ -4,7 +4,10 @@ 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\Exceptions\TestException;
|
||||||
use ParaTest\Options;
|
use ParaTest\Options;
|
||||||
|
use Pest\Plugins\Parallel\Support\CompactPrinter;
|
||||||
use PHPUnit\Runner\TestSuiteSorter;
|
use PHPUnit\Runner\TestSuiteSorter;
|
||||||
use PHPUnit\TestRunner\TestResult\TestResult;
|
use PHPUnit\TestRunner\TestResult\TestResult;
|
||||||
use PHPUnit\TextUI\Output\Default\ResultPrinter as DefaultResultPrinter;
|
use PHPUnit\TextUI\Output\Default\ResultPrinter as DefaultResultPrinter;
|
||||||
@ -13,11 +16,13 @@ use PHPUnit\TextUI\Output\SummaryPrinter;
|
|||||||
use PHPUnit\Util\Color;
|
use PHPUnit\Util\Color;
|
||||||
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||||
use SebastianBergmann\CodeCoverage\Filter;
|
use SebastianBergmann\CodeCoverage\Filter;
|
||||||
|
use SebastianBergmann\Timer\Duration;
|
||||||
use SebastianBergmann\Timer\ResourceUsageFormatter;
|
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;
|
||||||
@ -32,6 +37,7 @@ use function sprintf;
|
|||||||
use function str_repeat;
|
use function str_repeat;
|
||||||
use function strlen;
|
use function strlen;
|
||||||
|
|
||||||
|
use function Termwind\terminal;
|
||||||
use const DIRECTORY_SEPARATOR;
|
use const DIRECTORY_SEPARATOR;
|
||||||
use const PHP_EOL;
|
use const PHP_EOL;
|
||||||
use const PHP_VERSION;
|
use const PHP_VERSION;
|
||||||
@ -40,6 +46,7 @@ use const PHP_VERSION;
|
|||||||
final class ResultPrinter
|
final class ResultPrinter
|
||||||
{
|
{
|
||||||
public readonly Printer $printer;
|
public readonly Printer $printer;
|
||||||
|
private readonly CompactPrinter $compactPrinter;
|
||||||
|
|
||||||
private int $numTestsWidth = 0;
|
private int $numTestsWidth = 0;
|
||||||
private int $maxColumn = 0;
|
private int $maxColumn = 0;
|
||||||
@ -72,6 +79,8 @@ final class ResultPrinter
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$this->compactPrinter = new CompactPrinter();
|
||||||
|
|
||||||
if (! $this->options->configuration->hasLogfileTeamcity()) {
|
if (! $this->options->configuration->hasLogfileTeamcity()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -88,19 +97,13 @@ final class ResultPrinter
|
|||||||
|
|
||||||
public function start(): void
|
public function start(): void
|
||||||
{
|
{
|
||||||
$this->numTestsWidth = strlen((string) $this->totalCases);
|
$this->compactPrinter->line(sprintf(
|
||||||
$this->maxColumn = $this->numberOfColumns
|
'Running %d test file%s using %d process%s',
|
||||||
+ (DIRECTORY_SEPARATOR === '\\' ? -1 : 0) // fix windows blank lines
|
$this->totalCases,
|
||||||
- strlen($this->getProgress());
|
$this->totalCases === 1 ? '' : 's',
|
||||||
|
$this->options->processes,
|
||||||
// @see \PHPUnit\TextUI\TestRunner::writeMessage()
|
$this->options->processes === 1 ? '' : 'es')
|
||||||
$output = $this->output;
|
);
|
||||||
$write = static function (string $type, string $message) use ($output): void {
|
|
||||||
$output->write(sprintf("%-15s%s\n", $type . ':', $message));
|
|
||||||
};
|
|
||||||
|
|
||||||
// @see \PHPUnit\TextUI\Application::writeRuntimeInformation()
|
|
||||||
$write('Processes', (string) $this->options->processes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param list<SplFileInfo> $teamcityFiles */
|
/** @param list<SplFileInfo> $teamcityFiles */
|
||||||
@ -142,7 +145,7 @@ final class ResultPrinter
|
|||||||
* @param list<SplFileInfo> $teamcityFiles
|
* @param list<SplFileInfo> $teamcityFiles
|
||||||
* @param list<SplFileInfo> $testdoxFiles
|
* @param list<SplFileInfo> $testdoxFiles
|
||||||
*/
|
*/
|
||||||
public function printResults(TestResult $testResult, array $teamcityFiles, array $testdoxFiles): void
|
public function printResults(TestResult $testResult, array $teamcityFiles, array $testdoxFiles, Duration $duration): void
|
||||||
{
|
{
|
||||||
if ($this->options->needsTeamcity) {
|
if ($this->options->needsTeamcity) {
|
||||||
$teamcityProgress = $this->tailMultiple($teamcityFiles);
|
$teamcityProgress = $this->tailMultiple($teamcityFiles);
|
||||||
@ -168,78 +171,21 @@ final class ResultPrinter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$resultPrinter = new DefaultResultPrinter(
|
$this->compactPrinter->newLine();
|
||||||
$this->printer,
|
|
||||||
$this->options->configuration->displayDetailsOnIncompleteTests(),
|
|
||||||
$this->options->configuration->displayDetailsOnSkippedTests(),
|
|
||||||
$this->options->configuration->displayDetailsOnTestsThatTriggerDeprecations(),
|
|
||||||
$this->options->configuration->displayDetailsOnTestsThatTriggerErrors(),
|
|
||||||
$this->options->configuration->displayDetailsOnTestsThatTriggerNotices(),
|
|
||||||
$this->options->configuration->displayDetailsOnTestsThatTriggerWarnings(),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
$summaryPrinter = new SummaryPrinter(
|
|
||||||
$this->printer,
|
|
||||||
$this->options->configuration->colors(),
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->printer->print(PHP_EOL . (new ResourceUsageFormatter())->resourceUsageSinceStartOfRequest() . PHP_EOL . PHP_EOL);
|
$issues = array_map(fn($event) => CollisionTestResult::fromTestCase(
|
||||||
|
$event->test(),
|
||||||
|
CollisionTestResult::FAIL,
|
||||||
|
$event->throwable(),
|
||||||
|
), [...$testResult->testFailedEvents(), ...$testResult->testErroredEvents()]);
|
||||||
|
|
||||||
$resultPrinter->print($testResult);
|
$this->compactPrinter->errors($issues);
|
||||||
$summaryPrinter->print($testResult);
|
$this->compactPrinter->recap($testResult, $duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function printFeedbackItem(string $item): void
|
private function printFeedbackItem(string $item): void
|
||||||
{
|
{
|
||||||
$this->printFeedbackItemColor($item);
|
$this->compactPrinter->descriptionItem($item);
|
||||||
++$this->column;
|
|
||||||
++$this->casesProcessed;
|
|
||||||
if ($this->column !== $this->maxColumn && $this->casesProcessed < $this->totalCases) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
$this->casesProcessed > 0
|
|
||||||
&& $this->casesProcessed === $this->totalCases
|
|
||||||
&& ($pad = $this->maxColumn - $this->column) > 0
|
|
||||||
) {
|
|
||||||
$this->output->write(str_repeat(' ', $pad));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->output->write($this->getProgress() . "\n");
|
|
||||||
$this->column = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function printFeedbackItemColor(string $item): void
|
|
||||||
{
|
|
||||||
$buffer = match ($item) {
|
|
||||||
'E' => $this->colorizeTextBox('fg-red, bold', $item),
|
|
||||||
'F' => $this->colorizeTextBox('bg-red, fg-white', $item),
|
|
||||||
'I', 'N', 'D', 'R', 'W' => $this->colorizeTextBox('fg-yellow, bold', $item),
|
|
||||||
'S' => $this->colorizeTextBox('fg-cyan, bold', $item),
|
|
||||||
default => $item,
|
|
||||||
};
|
|
||||||
|
|
||||||
$this->output->write($buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getProgress(): string
|
|
||||||
{
|
|
||||||
return sprintf(
|
|
||||||
' %' . $this->numTestsWidth . 'd / %' . $this->numTestsWidth . 'd (%3s%%)',
|
|
||||||
$this->casesProcessed,
|
|
||||||
$this->totalCases,
|
|
||||||
floor(($this->totalCases > 0 ? $this->casesProcessed / $this->totalCases : 0) * 100),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function colorizeTextBox(string $color, string $buffer): string
|
|
||||||
{
|
|
||||||
if (! $this->options->configuration->colors()) {
|
|
||||||
return $buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Color::colorizeTextBox($color, $buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param list<SplFileInfo> $files */
|
/** @param list<SplFileInfo> $files */
|
||||||
|
|||||||
@ -18,6 +18,7 @@ use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade;
|
|||||||
use PHPUnit\TestRunner\TestResult\TestResult;
|
use PHPUnit\TestRunner\TestResult\TestResult;
|
||||||
use PHPUnit\TextUI\ShellExitCodeCalculator;
|
use PHPUnit\TextUI\ShellExitCodeCalculator;
|
||||||
use PHPUnit\Util\ExcludeList;
|
use PHPUnit\Util\ExcludeList;
|
||||||
|
use SebastianBergmann\Timer\Timer;
|
||||||
use SplFileInfo;
|
use SplFileInfo;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Process\PhpExecutableFinder;
|
use Symfony\Component\Process\PhpExecutableFinder;
|
||||||
@ -42,6 +43,7 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
{
|
{
|
||||||
private const CYCLE_SLEEP = 10000;
|
private const CYCLE_SLEEP = 10000;
|
||||||
private readonly ResultPrinter $printer;
|
private readonly ResultPrinter $printer;
|
||||||
|
private Timer $timer;
|
||||||
|
|
||||||
/** @var non-empty-string[] */
|
/** @var non-empty-string[] */
|
||||||
private array $pending = [];
|
private array $pending = [];
|
||||||
@ -70,6 +72,7 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
private readonly OutputInterface $output
|
private readonly OutputInterface $output
|
||||||
) {
|
) {
|
||||||
$this->printer = new ResultPrinter($output, $options);
|
$this->printer = new ResultPrinter($output, $options);
|
||||||
|
$this->timer = new Timer();
|
||||||
|
|
||||||
$wrapper = realpath(
|
$wrapper = realpath(
|
||||||
dirname(__DIR__, 4) . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'pest-wrapper.php',
|
dirname(__DIR__, 4) . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'pest-wrapper.php',
|
||||||
@ -93,6 +96,8 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
public function run(): int
|
public function run(): int
|
||||||
{
|
{
|
||||||
|
$this->timer->start();
|
||||||
|
|
||||||
ExcludeList::addDirectory(dirname(__DIR__));
|
ExcludeList::addDirectory(dirname(__DIR__));
|
||||||
TestResultFacade::init();
|
TestResultFacade::init();
|
||||||
EventFacade::seal();
|
EventFacade::seal();
|
||||||
@ -277,6 +282,7 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
$testResultSum,
|
$testResultSum,
|
||||||
$this->teamcityFiles,
|
$this->teamcityFiles,
|
||||||
$this->testdoxFiles,
|
$this->testdoxFiles,
|
||||||
|
$this->timer->stop(),
|
||||||
);
|
);
|
||||||
$this->generateCodeCoverageReports();
|
$this->generateCodeCoverageReports();
|
||||||
$this->generateLogs();
|
$this->generateLogs();
|
||||||
|
|||||||
181
src/Plugins/Parallel/Support/CompactPrinter.php
Normal file
181
src/Plugins/Parallel/Support/CompactPrinter.php
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Plugins\Parallel\Support;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Adapters\Phpunit\State;
|
||||||
|
use NunoMaduro\Collision\Adapters\Phpunit\Style;
|
||||||
|
use NunoMaduro\Collision\Adapters\Phpunit\TestResult;
|
||||||
|
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
use PHPUnit\Framework\IncompleteTestError;
|
||||||
|
use ReflectionClass;
|
||||||
|
use SebastianBergmann\Timer\Duration;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
|
use Termwind\Terminal;
|
||||||
|
use function Termwind\render;
|
||||||
|
use function Termwind\renderUsing;
|
||||||
|
use function Termwind\terminal;
|
||||||
|
|
||||||
|
final class CompactPrinter
|
||||||
|
{
|
||||||
|
private readonly Terminal $terminal;
|
||||||
|
private readonly ConsoleOutputInterface $output;
|
||||||
|
private readonly Style $style;
|
||||||
|
|
||||||
|
private int $compactProcessed = 0;
|
||||||
|
private int $compactSymbolsPerLine = 0;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->terminal = terminal();
|
||||||
|
$this->output = new ConsoleOutput(decorated: true);
|
||||||
|
$this->style = new Style($this->output);
|
||||||
|
|
||||||
|
$this->compactSymbolsPerLine = $this->terminal->width() - 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newLine(): void
|
||||||
|
{
|
||||||
|
render('<div class="py-1"></div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function line(string $message): void
|
||||||
|
{
|
||||||
|
render("<span class='mx-2 py-1 text-gray-700'>{$message}</span>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function descriptionItem(string $item): void
|
||||||
|
{
|
||||||
|
// TODO: Support todos
|
||||||
|
|
||||||
|
$icon = match (strtolower($item)) {
|
||||||
|
'f', 'e' => '⨯', // FAILED
|
||||||
|
's' => 's', // SKIPPED
|
||||||
|
'w', 'r' => '!', // WARN, RISKY
|
||||||
|
'i' => '…', // INCOMPLETE
|
||||||
|
'.' => '.', // PASSED
|
||||||
|
default => $item,
|
||||||
|
};
|
||||||
|
|
||||||
|
$color = match (strtolower($item)) {
|
||||||
|
'f', 'e' => 'red',
|
||||||
|
'd', 's', 'i', 'r', 'w' => 'yellow',
|
||||||
|
default => 'gray',
|
||||||
|
};
|
||||||
|
|
||||||
|
$symbolsOnCurrentLine = $this->compactProcessed % $this->compactSymbolsPerLine;
|
||||||
|
|
||||||
|
if ($symbolsOnCurrentLine >= $this->terminal->width() - 4) {
|
||||||
|
$symbolsOnCurrentLine = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($symbolsOnCurrentLine === 0) {
|
||||||
|
$this->output->writeln('');
|
||||||
|
$this->output->write(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->output->write(sprintf('<fg=%s;options=bold>%s</>', $color, $icon));
|
||||||
|
|
||||||
|
$this->compactProcessed++;
|
||||||
|
|
||||||
|
//switch ($item) {
|
||||||
|
// case self::TODO:
|
||||||
|
// return '↓';
|
||||||
|
// case self::RUNS:
|
||||||
|
// return '•';
|
||||||
|
// default:
|
||||||
|
// return '✓';
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function errors(array $errors): void
|
||||||
|
{
|
||||||
|
array_map(function (TestResult $testResult): void {
|
||||||
|
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
|
||||||
|
{
|
||||||
|
$testCounts = [
|
||||||
|
'passed' => ['green', $testResult->numberOfTestsRun()],
|
||||||
|
'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 = [];
|
||||||
|
|
||||||
|
foreach ($testCounts as $type => [$color, $count]) {
|
||||||
|
if ($count === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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, '.', '')
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->output->writeln('');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user