chore: phpstan level 5

This commit is contained in:
Nuno Maduro
2021-11-14 21:23:02 +00:00
parent 8ace01b6f1
commit 183f975166
23 changed files with 65 additions and 796 deletions

View File

@ -39,376 +39,7 @@ use function trim;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class JUnit extends Printer implements TestListener
final class JUnit extends Printer
{
private DOMDocument $document;
private DOMElement $root;
/**
* @var array<int, DOMElement>
*/
private array $testSuites = [];
/**
* @var int[]
*/
private array $testSuiteTests = [0];
/**
* @var int[]
*/
private array $testSuiteAssertions = [0];
/**
* @var int[]
*/
private array $testSuiteErrors = [0];
/**
* @var int[]
*/
private array $testSuiteWarnings = [0];
/**
* @var int[]
*/
private array $testSuiteFailures = [0];
/**
* @var int[]
*/
private array $testSuiteSkipped = [0];
private array $testSuiteTimes = [0];
private int $testSuiteLevel = 0;
private ?DOMElement $currentTestCase = null;
public function __construct(string $out)
{
$this->document = new DOMDocument('1.0', 'UTF-8');
$this->document->formatOutput = true;
$this->root = $this->document->createElement('testsuites');
$this->document->appendChild($this->root);
parent::__construct($out);
}
/**
* Flush buffer and close output.
*/
public function flush(): void
{
$this->write($this->getXML());
parent::flush();
}
/**
* An error occurred.
*/
public function addError(Test $test, Throwable $t, float $time): void
{
$this->doAddFault($test, $t, 'error');
$this->testSuiteErrors[$this->testSuiteLevel]++;
}
/**
* A warning occurred.
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->doAddFault($test, $e, 'warning');
$this->testSuiteWarnings[$this->testSuiteLevel]++;
}
/**
* A failure occurred.
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->doAddFault($test, $e, 'failure');
$this->testSuiteFailures[$this->testSuiteLevel]++;
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, Throwable $t, float $time): void
{
$this->doAddSkipped();
}
/**
* Risky test.
*/
public function addRiskyTest(Test $test, Throwable $t, float $time): void
{
}
/**
* Skipped test.
*/
public function addSkippedTest(Test $test, Throwable $t, float $time): void
{
$this->doAddSkipped();
}
/** @phpstan-ignore-next-line */
public function startTestSuite(TestSuite $suite): void
{
$testSuite = $this->document->createElement('testsuite');
$testSuite->setAttribute('name', $suite->getName());
if (class_exists($suite->getName(), false)) {
try {
$class = new ReflectionClass($suite->getName());
if ($class->hasMethod('__getFileName')) {
$fileName = $class->getMethod('__getFileName')->invoke(null);
} else {
$fileName = $class->getFileName();
}
$testSuite->setAttribute('file', $fileName);
} catch (ReflectionException) {
// @ignoreException
}
}
if ($this->testSuiteLevel > 0) {
$this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
} else {
$this->root->appendChild($testSuite);
}
$this->testSuiteLevel++;
$this->testSuites[$this->testSuiteLevel] = $testSuite;
$this->testSuiteTests[$this->testSuiteLevel] = 0;
$this->testSuiteAssertions[$this->testSuiteLevel] = 0;
$this->testSuiteErrors[$this->testSuiteLevel] = 0;
$this->testSuiteWarnings[$this->testSuiteLevel] = 0;
$this->testSuiteFailures[$this->testSuiteLevel] = 0;
$this->testSuiteSkipped[$this->testSuiteLevel] = 0;
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
}
/** @phpstan-ignore-next-line */
public function endTestSuite(TestSuite $suite): void
{
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'tests',
(string) $this->testSuiteTests[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'assertions',
(string) $this->testSuiteAssertions[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'errors',
(string) $this->testSuiteErrors[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'warnings',
(string) $this->testSuiteWarnings[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'failures',
(string) $this->testSuiteFailures[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'skipped',
(string) $this->testSuiteSkipped[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'time',
sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])
);
if ($this->testSuiteLevel > 1) {
$this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
$this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
$this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
$this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel];
$this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
$this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
$this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
}
$this->testSuiteLevel--;
}
/**
* A test started.
*
* @param Test|Testable $test
*/
public function startTest(Test $test): void
{
$usesDataprovider = false;
if (method_exists($test, 'usesDataProvider')) {
$usesDataprovider = $test->usesDataProvider();
}
$testCase = $this->document->createElement('testcase');
$testCase->setAttribute('name', $test->getName());
try {
$class = new ReflectionClass($test);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
// @phpstan-ignore-next-line
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
}
// @codeCoverageIgnoreEnd
$methodName = $test->getName(!$usesDataprovider);
if ($class->hasMethod($methodName)) {
try {
$method = $class->getMethod($methodName);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
// @phpstan-ignore-next-line
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
}
// @codeCoverageIgnoreEnd
$testCase->setAttribute('class', $class->getName());
$testCase->setAttribute('classname', str_replace('\\', '.', $class->getName()));
$fileName = $class->getFileName();
if ($fileName !== false) {
$testCase->setAttribute('file', $fileName);
}
$testCase->setAttribute('line', (string) $method->getStartLine());
}
if (TeamCity::isPestTest($test)) {
$testCase->setAttribute('class', $test->getPrintableTestCaseName());
$testCase->setAttribute('classname', str_replace('\\', '.', $test->getPrintableTestCaseName()));
// @phpstan-ignore-next-line
$testCase->setAttribute('file', $test->__getFilename());
}
$this->currentTestCase = $testCase;
}
/**
* A test ended.
*/
public function endTest(Test $test, float $time): void
{
$numAssertions = 0;
if (method_exists($test, 'getNumAssertions')) {
$numAssertions = $test->getNumAssertions();
}
$this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
if ($this->currentTestCase !== null) {
$this->currentTestCase->setAttribute(
'assertions',
(string) $numAssertions
);
$this->currentTestCase->setAttribute(
'time',
sprintf('%F', $time)
);
$this->testSuites[$this->testSuiteLevel]->appendChild(
$this->currentTestCase
);
}
$this->testSuiteTests[$this->testSuiteLevel]++;
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
$testOutput = '';
if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) {
$testOutput = $test->hasOutput() ? $test->getActualOutput() : '';
}
if ($testOutput !== '') {
$systemOut = $this->document->createElement(
'system-out',
Xml::prepareString($testOutput)
);
if ($this->currentTestCase !== null) {
$this->currentTestCase->appendChild($systemOut);
}
}
$this->currentTestCase = null;
}
/**
* Returns the XML as a string.
*/
public function getXML(): string
{
$xml = $this->document->saveXML();
if ($xml === false) {
return '';
}
return $xml;
}
private function doAddFault(Test $test, Throwable $t, string $type): void
{
if ($this->currentTestCase === null) {
return;
}
if ($test instanceof SelfDescribing) {
$buffer = $test->toString() . "\n";
} else {
$buffer = '';
}
$buffer .= trim(
TestFailure::exceptionToString($t) . "\n" .
Filter::getFilteredStacktrace($t)
);
$fault = $this->document->createElement(
$type,
Xml::prepareString($buffer)
);
if ($t instanceof ExceptionWrapper) {
$fault->setAttribute('type', $t->getClassName());
} else {
$fault->setAttribute('type', $t::class);
}
$this->currentTestCase->appendChild($fault);
}
private function doAddSkipped(): void
{
if ($this->currentTestCase === null) {
return;
}
$skipped = $this->document->createElement('skipped');
$this->currentTestCase->appendChild($skipped);
$this->testSuiteSkipped[$this->testSuiteLevel]++;
}
// @todo
}

View File

@ -4,289 +4,9 @@ declare(strict_types=1);
namespace Pest\Logging;
use function getmypid;
use Pest\Concerns\Logging\WritesToConsole;
use Pest\Concerns\Testable;
use Pest\Support\ExceptionTrace;
use function Pest\version;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\TextUI\DefaultResultPrinter;
use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity as BaseTeamCity;
use function round;
use function str_replace;
use Throwable;
final class TeamCity extends DefaultResultPrinter
{
use WritesToConsole;
private const PROTOCOL = 'pest_qn://';
private const NAME = 'name';
private const LOCATION_HINT = 'locationHint';
private const DURATION = 'duration';
private const TEST_SUITE_STARTED = 'testSuiteStarted';
private const TEST_SUITE_FINISHED = 'testSuiteFinished';
private const TEST_COUNT = 'testCount';
private const TEST_STARTED = 'testStarted';
private const TEST_FINISHED = 'testFinished';
private ?int $flowId = null;
private bool $isSummaryTestCountPrinted = false;
private BaseTeamCity $phpunitTeamCity;
/**
* Creates a new printer instance.
*/
public function __construct(string|null $out, bool $verbose, string $colors)
{
parent::__construct($out, $verbose, $colors);
$this->phpunitTeamCity = new BaseTeamCity($out, $verbose, $colors);
$this->logo();
}
private function logo(): void
{
$this->writeNewLine();
$this->write('Pest ' . version());
$this->writeNewLine();
}
public function printResult(TestResult $result): void
{
$this->write('Tests: ');
$results = [
'failed' => ['count' => $result->errorCount() + $result->failureCount(), 'color' => 'fg-red'],
'skipped' => ['count' => $result->skippedCount(), 'color' => 'fg-yellow'],
'warned' => ['count' => $result->warningCount(), 'color' => 'fg-yellow'],
'risked' => ['count' => $result->riskyCount(), 'color' => 'fg-yellow'],
'incomplete' => ['count' => $result->notImplementedCount(), 'color' => 'fg-yellow'],
'passed' => ['count' => $this->successfulTestCount($result), 'color' => 'fg-green'],
];
$filteredResults = array_filter($results, fn ($item): bool => $item['count'] > 0);
foreach ($filteredResults as $key => $info) {
$this->writeWithColor($info['color'], $info['count'] . " $key", false);
if ($key !== array_reverse(array_keys($filteredResults))[0]) {
$this->write(', ');
}
}
$this->writeNewLine();
$this->write("Assertions: $this->numAssertions");
$this->writeNewLine();
$this->write("Time: {$result->time()}s");
$this->writeNewLine();
}
private function successfulTestCount(TestResult $result): int
{
return $result->count()
- $result->failureCount()
- $result->errorCount()
- $result->skippedCount()
- $result->warningCount()
- $result->notImplementedCount()
- $result->riskyCount();
}
/** @phpstan-ignore-next-line */
public function startTestSuite(TestSuite $suite): void
{
$suiteName = $suite->getName();
if (static::isCompoundTestSuite($suite)) {
$this->writeWithColor('bold', ' ' . $suiteName);
} elseif (static::isPestTestSuite($suite)) {
$this->writeWithColor('fg-white, bold', ' ' . substr_replace($suiteName, '', 0, 2) . ' ');
} else {
$this->writeWithColor('fg-white, bold', ' ' . $suiteName);
}
$this->writeNewLine();
$this->flowId = (int) getmypid();
if (!$this->isSummaryTestCountPrinted) {
$this->printEvent(self::TEST_COUNT, [
'count' => $suite->count(),
]);
$this->isSummaryTestCountPrinted = true;
}
$this->printEvent(self::TEST_SUITE_STARTED, [
self::NAME => static::isCompoundTestSuite($suite) ? $suiteName : substr($suiteName, 2),
self::LOCATION_HINT => self::PROTOCOL . (static::isCompoundTestSuite($suite) ? $suiteName : $suiteName::__getFileName()),
]);
}
/**
* @param array<string, string|int> $params
*/
private function printEvent(string $eventName, array $params = []): void
{
$this->write("##teamcity[{$eventName}");
if ($this->flowId !== 0) {
$params['flowId'] = $this->flowId;
}
foreach ($params as $key => $value) {
$escapedValue = self::escapeValue((string) $value);
$this->write(" {$key}='{$escapedValue}'");
}
$this->write("]\n");
}
private static function escapeValue(string $text): string
{
return str_replace(
['|', "'", "\n", "\r", ']', '['],
['||', "|'", '|n', '|r', '|]', '|['],
$text
);
}
/** @phpstan-ignore-next-line */
public function endTestSuite(TestSuite $suite): void
{
$suiteName = $suite->getName();
$this->writeNewLine();
$this->writeNewLine();
$this->printEvent(self::TEST_SUITE_FINISHED, [
self::NAME => static::isCompoundTestSuite($suite) ? $suiteName : substr($suiteName, 2),
self::LOCATION_HINT => self::PROTOCOL . (static::isCompoundTestSuite($suite) ? $suiteName : $suiteName::__getFileName()),
]);
}
/**
* @param Test|Testable $test
*/
public function startTest(Test $test): void
{
if (!TeamCity::isPestTest($test)) {
$this->phpunitTeamCity->startTest($test);
return;
}
$this->printEvent(self::TEST_STARTED, [
self::NAME => $test->getName(),
// @phpstan-ignore-next-line
self::LOCATION_HINT => self::PROTOCOL . $test->toString(),
]);
}
/**
* Verify that the given test suite is a valid Pest suite.
*
* @param TestSuite<Test> $suite
*/
private static function isPestTestSuite(TestSuite $suite): bool
{
return str_starts_with($suite->getName(), 'P\\');
}
/**
* Determine if the test suite is made up of multiple smaller test suites.
*
* @param TestSuite<Test> $suite
*/
private static function isCompoundTestSuite(TestSuite $suite): bool
{
return file_exists($suite->getName()) || !method_exists($suite->getName(), '__getFileName');
}
public static function isPestTest(Test $test): bool
{
/** @var array<string, string> $uses */
$uses = class_uses($test);
return in_array(Testable::class, $uses, true);
}
/**
* @param Test|Testable $test
*/
public function endTest(Test $test, float $time): void
{
$this->printEvent(self::TEST_FINISHED, [
self::NAME => $test->getName(),
self::DURATION => self::toMilliseconds($time),
]);
if (!$this->lastTestFailed) {
$this->writeSuccess($test->getName());
}
$this->numAssertions += $test instanceof TestCase ? $test->getNumAssertions() : 1;
$this->lastTestFailed = false;
}
private static function toMilliseconds(float $time): int
{
return (int) round($time * 1000);
}
public function addError(Test $test, Throwable $t, float $time): void
{
$this->markAsFailure($t);
$this->writeError($test->getName());
$this->phpunitTeamCity->addError($test, $t, $time);
}
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->markAsFailure($e);
$this->writeError($test->getName());
$this->phpunitTeamCity->addFailure($test, $e, $time);
}
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->markAsFailure($e);
$this->writeWarning($test->getName());
$this->phpunitTeamCity->addWarning($test, $e, $time);
}
public function addIncompleteTest(Test $test, Throwable $t, float $time): void
{
$this->markAsFailure($t);
$this->writeWarning($test->getName());
$this->phpunitTeamCity->addIncompleteTest($test, $t, $time);
}
public function addRiskyTest(Test $test, Throwable $t, float $time): void
{
$this->markAsFailure($t);
$this->writeWarning($test->getName());
$this->phpunitTeamCity->addRiskyTest($test, $t, $time);
}
public function addSkippedTest(Test $test, Throwable $t, float $time): void
{
$this->markAsFailure($t);
$this->writeWarning($test->getName());
$this->phpunitTeamCity->printIgnoredTest($test->getName(), $t, $time);
}
private function markAsFailure(Throwable $t): void
{
$this->lastTestFailed = true;
ExceptionTrace::removePestReferences($t);
}
// @todo
}