You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
389 lines
11 KiB
389 lines
11 KiB
<?php declare(strict_types=1);
|
|
/*
|
|
* This file is part of PHPUnit.
|
|
*
|
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
namespace PHPUnit\Util\TestDox;
|
|
|
|
use const PHP_EOL;
|
|
use function array_map;
|
|
use function get_class;
|
|
use function implode;
|
|
use function method_exists;
|
|
use function preg_split;
|
|
use function trim;
|
|
use PHPUnit\Framework\AssertionFailedError;
|
|
use PHPUnit\Framework\Reorderable;
|
|
use PHPUnit\Framework\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
use PHPUnit\Framework\TestResult;
|
|
use PHPUnit\Framework\TestSuite;
|
|
use PHPUnit\Framework\Warning;
|
|
use PHPUnit\Runner\BaseTestRunner;
|
|
use PHPUnit\Runner\PhptTestCase;
|
|
use PHPUnit\TextUI\DefaultResultPrinter;
|
|
use Throwable;
|
|
|
|
/**
|
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
|
*/
|
|
class TestDoxPrinter extends DefaultResultPrinter
|
|
{
|
|
/**
|
|
* @var NamePrettifier
|
|
*/
|
|
protected $prettifier;
|
|
|
|
/**
|
|
* @var int The number of test results received from the TestRunner
|
|
*/
|
|
protected $testIndex = 0;
|
|
|
|
/**
|
|
* @var int The number of test results already sent to the output
|
|
*/
|
|
protected $testFlushIndex = 0;
|
|
|
|
/**
|
|
* @var array<int, array> Buffer for test results
|
|
*/
|
|
protected $testResults = [];
|
|
|
|
/**
|
|
* @var array<string, int> Lookup table for testname to testResults[index]
|
|
*/
|
|
protected $testNameResultIndex = [];
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $enableOutputBuffer = false;
|
|
|
|
/**
|
|
* @var array array<string>
|
|
*/
|
|
protected $originalExecutionOrder = [];
|
|
|
|
/**
|
|
* @var int
|
|
*/
|
|
protected $spinState = 0;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $showProgress = true;
|
|
|
|
/**
|
|
* @param null|resource|string $out
|
|
* @param int|string $numberOfColumns
|
|
*
|
|
* @throws \PHPUnit\Framework\Exception
|
|
*/
|
|
public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false)
|
|
{
|
|
parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse);
|
|
|
|
$this->prettifier = new NamePrettifier($this->colors);
|
|
}
|
|
|
|
public function setOriginalExecutionOrder(array $order): void
|
|
{
|
|
$this->originalExecutionOrder = $order;
|
|
$this->enableOutputBuffer = !empty($order);
|
|
}
|
|
|
|
public function setShowProgressAnimation(bool $showProgress): void
|
|
{
|
|
$this->showProgress = $showProgress;
|
|
}
|
|
|
|
public function printResult(TestResult $result): void
|
|
{
|
|
}
|
|
|
|
/**
|
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
|
*/
|
|
public function endTest(Test $test, float $time): void
|
|
{
|
|
if (!$test instanceof TestCase && !$test instanceof PhptTestCase && !$test instanceof TestSuite) {
|
|
return;
|
|
}
|
|
|
|
if ($this->testHasPassed()) {
|
|
$this->registerTestResult($test, null, BaseTestRunner::STATUS_PASSED, $time, false);
|
|
}
|
|
|
|
if ($test instanceof TestCase || $test instanceof PhptTestCase) {
|
|
$this->testIndex++;
|
|
}
|
|
|
|
parent::endTest($test, $time);
|
|
}
|
|
|
|
/**
|
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
|
*/
|
|
public function addError(Test $test, Throwable $t, float $time): void
|
|
{
|
|
$this->registerTestResult($test, $t, BaseTestRunner::STATUS_ERROR, $time, true);
|
|
}
|
|
|
|
/**
|
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
|
*/
|
|
public function addWarning(Test $test, Warning $e, float $time): void
|
|
{
|
|
$this->registerTestResult($test, $e, BaseTestRunner::STATUS_WARNING, $time, true);
|
|
}
|
|
|
|
/**
|
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
|
*/
|
|
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
|
|
{
|
|
$this->registerTestResult($test, $e, BaseTestRunner::STATUS_FAILURE, $time, true);
|
|
}
|
|
|
|
/**
|
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
|
*/
|
|
public function addIncompleteTest(Test $test, Throwable $t, float $time): void
|
|
{
|
|
$this->registerTestResult($test, $t, BaseTestRunner::STATUS_INCOMPLETE, $time, false);
|
|
}
|
|
|
|
/**
|
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
|
*/
|
|
public function addRiskyTest(Test $test, Throwable $t, float $time): void
|
|
{
|
|
$this->registerTestResult($test, $t, BaseTestRunner::STATUS_RISKY, $time, false);
|
|
}
|
|
|
|
/**
|
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
|
*/
|
|
public function addSkippedTest(Test $test, Throwable $t, float $time): void
|
|
{
|
|
$this->registerTestResult($test, $t, BaseTestRunner::STATUS_SKIPPED, $time, false);
|
|
}
|
|
|
|
public function writeProgress(string $progress): void
|
|
{
|
|
$this->flushOutputBuffer();
|
|
}
|
|
|
|
public function flush(): void
|
|
{
|
|
$this->flushOutputBuffer(true);
|
|
}
|
|
|
|
/**
|
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
|
*/
|
|
protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void
|
|
{
|
|
$testName = $test instanceof Reorderable ? $test->sortId() : $test->getName();
|
|
|
|
$result = [
|
|
'className' => $this->formatClassName($test),
|
|
'testName' => $testName,
|
|
'testMethod' => $this->formatTestName($test),
|
|
'message' => '',
|
|
'status' => $status,
|
|
'time' => $time,
|
|
'verbose' => $verbose,
|
|
];
|
|
|
|
if ($t !== null) {
|
|
$result['message'] = $this->formatTestResultMessage($t, $result);
|
|
}
|
|
|
|
$this->testResults[$this->testIndex] = $result;
|
|
$this->testNameResultIndex[$testName] = $this->testIndex;
|
|
}
|
|
|
|
protected function formatTestName(Test $test): string
|
|
{
|
|
return method_exists($test, 'getName') ? $test->getName() : '';
|
|
}
|
|
|
|
protected function formatClassName(Test $test): string
|
|
{
|
|
return get_class($test);
|
|
}
|
|
|
|
protected function testHasPassed(): bool
|
|
{
|
|
if (!isset($this->testResults[$this->testIndex]['status'])) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->testResults[$this->testIndex]['status'] === BaseTestRunner::STATUS_PASSED) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function flushOutputBuffer(bool $forceFlush = false): void
|
|
{
|
|
if ($this->testFlushIndex === $this->testIndex) {
|
|
return;
|
|
}
|
|
|
|
if ($this->testFlushIndex > 0) {
|
|
if ($this->enableOutputBuffer &&
|
|
isset($this->originalExecutionOrder[$this->testFlushIndex - 1])) {
|
|
$prevResult = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex - 1]);
|
|
} else {
|
|
$prevResult = $this->testResults[$this->testFlushIndex - 1];
|
|
}
|
|
} else {
|
|
$prevResult = $this->getEmptyTestResult();
|
|
}
|
|
|
|
if (!$this->enableOutputBuffer) {
|
|
$this->writeTestResult($prevResult, $this->testResults[$this->testFlushIndex++]);
|
|
} else {
|
|
do {
|
|
$flushed = false;
|
|
|
|
if (!$forceFlush && isset($this->originalExecutionOrder[$this->testFlushIndex])) {
|
|
$result = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex]);
|
|
} else {
|
|
// This test(name) cannot found in original execution order,
|
|
// flush result to output stream right away
|
|
$result = $this->testResults[$this->testFlushIndex];
|
|
}
|
|
|
|
if (!empty($result)) {
|
|
$this->hideSpinner();
|
|
$this->writeTestResult($prevResult, $result);
|
|
$this->testFlushIndex++;
|
|
$prevResult = $result;
|
|
$flushed = true;
|
|
} else {
|
|
$this->showSpinner();
|
|
}
|
|
} while ($flushed && $this->testFlushIndex < $this->testIndex);
|
|
}
|
|
}
|
|
|
|
protected function showSpinner(): void
|
|
{
|
|
if (!$this->showProgress) {
|
|
return;
|
|
}
|
|
|
|
if ($this->spinState) {
|
|
$this->undrawSpinner();
|
|
}
|
|
|
|
$this->spinState++;
|
|
$this->drawSpinner();
|
|
}
|
|
|
|
protected function hideSpinner(): void
|
|
{
|
|
if (!$this->showProgress) {
|
|
return;
|
|
}
|
|
|
|
if ($this->spinState) {
|
|
$this->undrawSpinner();
|
|
}
|
|
|
|
$this->spinState = 0;
|
|
}
|
|
|
|
protected function drawSpinner(): void
|
|
{
|
|
// optional for CLI printers: show the user a 'buffering output' spinner
|
|
}
|
|
|
|
protected function undrawSpinner(): void
|
|
{
|
|
// remove the spinner from the current line
|
|
}
|
|
|
|
protected function writeTestResult(array $prevResult, array $result): void
|
|
{
|
|
}
|
|
|
|
protected function getEmptyTestResult(): array
|
|
{
|
|
return [
|
|
'className' => '',
|
|
'testName' => '',
|
|
'message' => '',
|
|
'failed' => '',
|
|
'verbose' => '',
|
|
];
|
|
}
|
|
|
|
protected function getTestResultByName(?string $testName): array
|
|
{
|
|
if (isset($this->testNameResultIndex[$testName])) {
|
|
return $this->testResults[$this->testNameResultIndex[$testName]];
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
protected function formatThrowable(Throwable $t, ?int $status = null): string
|
|
{
|
|
$message = trim(\PHPUnit\Framework\TestFailure::exceptionToString($t));
|
|
|
|
if ($message) {
|
|
$message .= PHP_EOL . PHP_EOL . $this->formatStacktrace($t);
|
|
} else {
|
|
$message = $this->formatStacktrace($t);
|
|
}
|
|
|
|
return $message;
|
|
}
|
|
|
|
protected function formatStacktrace(Throwable $t): string
|
|
{
|
|
return \PHPUnit\Util\Filter::getFilteredStacktrace($t);
|
|
}
|
|
|
|
protected function formatTestResultMessage(Throwable $t, array $result, string $prefix = '│'): string
|
|
{
|
|
$message = $this->formatThrowable($t, $result['status']);
|
|
|
|
if ($message === '') {
|
|
return '';
|
|
}
|
|
|
|
if (!($this->verbose || $result['verbose'])) {
|
|
return '';
|
|
}
|
|
|
|
return $this->prefixLines($prefix, $message);
|
|
}
|
|
|
|
protected function prefixLines(string $prefix, string $message): string
|
|
{
|
|
$message = trim($message);
|
|
|
|
return implode(
|
|
PHP_EOL,
|
|
array_map(
|
|
static function (string $text) use ($prefix)
|
|
{
|
|
return ' ' . $prefix . ($text ? ' ' . $text : '');
|
|
},
|
|
preg_split('/\r\n|\r|\n/', $message),
|
|
),
|
|
);
|
|
}
|
|
}
|