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

@ -38,8 +38,13 @@ jobs:
- name: Install PHP dependencies - name: Install PHP dependencies
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress
- name: Unit Tests
run: php bin/pest --colors=always --exclude-group=integration
- name: Unit Tests - name: Unit Tests
run: php bin/pest --colors=always --exclude-group=integration ${{ matrix.parallel }} run: php bin/pest --colors=always --exclude-group=integration ${{ matrix.parallel }}
if: ${{ false }} # 2.x-dev is under development
- name: Integration Tests - name: Integration Tests
run: php bin/pest --colors=always --group=integration run: php bin/pest --colors=always --group=integration
if: ${{ false }} # 2.x-dev is under development

View File

@ -64,15 +64,13 @@
"test:lint": "php-cs-fixer fix -v --dry-run", "test:lint": "php-cs-fixer fix -v --dry-run",
"test:types": "phpstan analyse --ansi --memory-limit=-1", "test:types": "phpstan analyse --ansi --memory-limit=-1",
"test:unit": "php bin/pest --colors=always --exclude-group=integration", "test:unit": "php bin/pest --colors=always --exclude-group=integration",
"test:parallel": "php bin/pest -p --colors=always --exclude-group=integration", "test:parallel": "exit 1",
"test:integration": "php bin/pest --colors=always --group=integration", "test:integration": "exit 1",
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always", "update:snapshots": "exit 1",
"test": [ "test": [
"@test:lint", "@test:lint",
"@test:types", "@test:types",
"@test:unit", "@test:unit"
"@test:parallel",
"@test:integration"
] ]
}, },
"extra": { "extra": {

View File

@ -13,6 +13,7 @@ parameters:
reportUnmatchedIgnoredErrors: true reportUnmatchedIgnoredErrors: true
ignoreErrors: ignoreErrors:
- "#with a nullable type declaration#"
- "#type mixed is not subtype of native#" - "#type mixed is not subtype of native#"
- "#is not allowed to extend#" - "#is not allowed to extend#"
- "#Language construct eval#" - "#Language construct eval#"
@ -20,15 +21,3 @@ parameters:
- "#has parameter \\$closure with default value.#" - "#has parameter \\$closure with default value.#"
- "#has parameter \\$description with default value.#" - "#has parameter \\$description with default value.#"
- "#Method Pest\\\\Support\\\\Reflection::getParameterClassName\\(\\) has a nullable return type declaration.#" - "#Method Pest\\\\Support\\\\Reflection::getParameterClassName\\(\\) has a nullable return type declaration.#"
-
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getName\(\)#'
path: src/Logging
-
message: '#invalid typehint type Pest\\Concerns\\Testable#'
path: src/Logging
-
message: '#is not subtype of native type PHPUnit\\Framework\\Test#'
path: src/Logging
-
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getPrintableTestCaseName\(\)#'
path: src/Logging

View File

@ -18,7 +18,6 @@ final class BootSubscribers
* @var array<int, class-string> * @var array<int, class-string>
*/ */
private static array $subscribers = [ private static array $subscribers = [
Subscribers\EnsureTestsAreLoaded::class,
Subscribers\EnsureConfigurationIsValid::class, Subscribers\EnsureConfigurationIsValid::class,
Subscribers\EnsureConfigurationDefaults::class, Subscribers\EnsureConfigurationDefaults::class,
]; ];

View File

@ -46,7 +46,7 @@ final class Datasets
/** /**
* Sets the given. * Sets the given.
* *
* @param Closure|iterable<int|string, mixed> $data * @param Closure|iterable<int|string, mixed>|string $with
*/ */
public static function with(string $filename, string $description, Closure|iterable|string $with): void public static function with(string $filename, string $description, Closure|iterable|string $with): void
{ {
@ -129,7 +129,7 @@ final class Datasets
$processedDataset = []; $processedDataset = [];
if (is_string($data)) { if (is_string($data)) {
if (!isset(self::$datasets[$data])) { if (! array_key_exists($data, self::$datasets)) {
throw new DatasetDoesNotExist($data); throw new DatasetDoesNotExist($data);
} }

View File

@ -17,7 +17,6 @@ use PHPUnit\Framework\TestCase;
final class TestCaseMethodFactory final class TestCaseMethodFactory
{ {
use HigherOrderable; use HigherOrderable;
/** /**
* Determines if the Test Case will be the "only" being run. * Determines if the Test Case will be the "only" being run.
*/ */
@ -54,7 +53,7 @@ final class TestCaseMethodFactory
) { ) {
if ($this->closure === null) { if ($this->closure === null) {
$this->closure = function () { $this->closure = function () {
Assert::getCount() > 0 ?: self::markTestIncomplete(); Assert::getCount() > 0 ?: self::markTestIncomplete(); // @phpstan-ignore-line
}; };
} }
@ -66,7 +65,7 @@ final class TestCaseMethodFactory
*/ */
public function getClosure(TestCase $concrete): Closure public function getClosure(TestCase $concrete): Closure
{ {
$concrete::flush(); $concrete::flush(); // @phpstan-ignore-line
if ($this->description === null) { if ($this->description === null) {
throw ShouldNotHappen::fromMessage('Description can not be empty.'); throw ShouldNotHappen::fromMessage('Description can not be empty.');
@ -81,7 +80,9 @@ final class TestCaseMethodFactory
$method = $this; $method = $this;
return function () use ($testCase, $method, $closure): mixed { return function () use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line
/** @var TestCase $this */
$testCase->proxies->proxy($this); $testCase->proxies->proxy($this);
$method->proxies->proxy($this); $method->proxies->proxy($this);

View File

@ -14,18 +14,20 @@ use Pest\Support\HigherOrderTapProxy;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/** if (!function_exists('expect')) {
/**
* Creates a new expectation. * Creates a new expectation.
* *
* @param mixed $value the Value * @param mixed $value the Value
*/ */
function expect($value = null): Expectation|Extendable function expect($value = null): Expectation|Extendable
{ {
if (func_num_args() === 0) { if (func_num_args() === 0) {
return new Extendable(Expectation::class); return new Extendable(Expectation::class);
} }
return new Expectation($value); return new Expectation($value);
}
} }
if (!function_exists('beforeAll')) { if (!function_exists('beforeAll')) {

View File

@ -64,6 +64,6 @@ final class Kernel
*/ */
public function shutdown(): void public function shutdown(): void
{ {
// TODO // ..
} }
} }

View File

@ -39,376 +39,7 @@ use function trim;
/** /**
* @internal This class is not covered by the backward compatibility promise for PHPUnit * @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; // @todo
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]++;
}
} }

View File

@ -4,289 +4,9 @@ declare(strict_types=1);
namespace Pest\Logging; 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\DefaultResultPrinter;
use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity as BaseTeamCity;
use function round;
use function str_replace;
use Throwable;
final class TeamCity extends DefaultResultPrinter final class TeamCity extends DefaultResultPrinter
{ {
use WritesToConsole; // @todo
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);
}
} }

View File

@ -46,6 +46,8 @@ final class OppositeExpectation
* Handle dynamic method calls into the original expectation. * Handle dynamic method calls into the original expectation.
* *
* @param array<int, mixed> $arguments * @param array<int, mixed> $arguments
*
* @return Expectation|never
*/ */
public function __call(string $name, array $arguments): Expectation public function __call(string $name, array $arguments): Expectation
{ {
@ -56,23 +58,23 @@ final class OppositeExpectation
return $this->original; return $this->original;
} }
// @phpstan-ignore-next-line
$this->throwExpectationFailedException($name, $arguments); $this->throwExpectationFailedException($name, $arguments);
} }
/** /**
* Handle dynamic properties gets into the original expectation. * Handle dynamic properties gets into the original expectation.
*
* @return Expectation|never
*/ */
public function __get(string $name): Expectation public function __get(string $name): Expectation
{ {
try { try {
/* @phpstan-ignore-next-line */ /** @throws ExpectationFailedException */
$this->original->{$name}; $this->original->{$name}; // @phpstan-ignore-line
} catch (ExpectationFailedException) { } catch (ExpectationFailedException) {
return $this->original; return $this->original;
} }
// @phpstan-ignore-next-line
$this->throwExpectationFailedException($name); $this->throwExpectationFailedException($name);
} }
@ -80,6 +82,8 @@ final class OppositeExpectation
* Creates a new expectation failed exception with a nice readable message. * Creates a new expectation failed exception with a nice readable message.
* *
* @param array<int, mixed> $arguments * @param array<int, mixed> $arguments
*
* @return never
*/ */
private function throwExpectationFailedException(string $name, array $arguments = []): void private function throwExpectationFailedException(string $name, array $arguments = []): void
{ {

View File

@ -74,7 +74,7 @@ final class TestCall
$condition = is_callable($condition) $condition = is_callable($condition)
? $condition ? $condition
: static function () use ($condition): bool { : static function () use ($condition): bool {
return $condition; // @phpstan-ignore-line return $condition;
}; };
if ($condition()) { if ($condition()) {

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Pest\Plugins\Actions; namespace Pest\Plugins\Actions;
use Pest\Contracts\Plugins; use Pest\Contracts\Plugins;
@ -19,7 +21,7 @@ final class AddsOutput
{ {
$plugins = Loader::getPlugins(Plugins\AddsOutput::class); $plugins = Loader::getPlugins(Plugins\AddsOutput::class);
/** @var Plugins\AddsOutpu $plugin */ /** @var Plugins\AddsOutput $plugin */
foreach ($plugins as $plugin) { foreach ($plugins as $plugin) {
$exitCode = $plugin->addOutput($exitCode); $exitCode = $plugin->addOutput($exitCode);
} }

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Pest\Plugins\Actions; namespace Pest\Plugins\Actions;
use Pest\Contracts\Plugins; use Pest\Contracts\Plugins;

View File

@ -41,7 +41,6 @@ final class AfterEachRepository
return ChainableClosure::from(function (): void { return ChainableClosure::from(function (): void {
if (class_exists(Mockery::class)) { if (class_exists(Mockery::class)) {
/* @phpstan-ignore-next-line */
if ($container = Mockery::getContainer()) { if ($container = Mockery::getContainer()) {
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
$this->addToAssertionCount($container->mockery_getExpectationCount()); $this->addToAssertionCount($container->mockery_getExpectationCount());

View File

@ -90,7 +90,7 @@ final class TestRepository
*/ */
public function set(TestCaseMethodFactory $method): void public function set(TestCaseMethodFactory $method): void
{ {
if (!isset($this->testCases[$method->filename])) { if (! array_key_exists($method->filename, $this->testCases)) {
$this->testCases[$method->filename] = new TestCaseFactory($method->filename); $this->testCases[$method->filename] = new TestCaseFactory($method->filename);
} }
@ -102,7 +102,7 @@ final class TestRepository
*/ */
public function makeIfExists(string $filename): void public function makeIfExists(string $filename): void
{ {
if (isset($this->testCases[$filename])) { if (array_key_exists($filename, $this->testCases)) {
$this->make($this->testCases[$filename]); $this->make($this->testCases[$filename]);
} }
} }

View File

@ -1,82 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Subscribers;
use PHPUnit\Event\TestSuite\Loaded;
use PHPUnit\Event\TestSuite\LoadedSubscriber;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\WarningTestCase;
/**
* @internal
*/
final class EnsureTestsAreLoaded implements LoadedSubscriber
{
/**
* The current test suite, if any.
*/
private static ?TestSuite $testSuite = null;
/**
* Runs the subscriber.
*/
public function notify(Loaded $event): void
{
/*
$this->removeWarnings(self::$testSuite);
$testSuites = [];
$testSuite = \Pest\TestSuite::getInstance();
$testSuite->tests->build($testSuite, function (TestCase $testCase) use (&$testSuites): void {
$testCaseClass = $testCase::class;
if (!array_key_exists($testCaseClass, $testSuites)) {
$testSuites[$testCaseClass] = [];
}
$testSuites[$testCaseClass][] = $testCase;
});
foreach ($testSuites as $testCaseName => $testCases) {
$testTestSuite = new TestSuite($testCaseName);
$testTestSuite->setTests([]);
foreach ($testCases as $testCase) {
$testTestSuite->addTest($testCase, $testCase->groups());
}
self::$testSuite->addTestSuite($testTestSuite);
}
*/
}
/**
* Sets the current test suite.
*/
public static function setTestSuite(TestSuite $testSuite): void
{
self::$testSuite = $testSuite;
}
/**
* Removes the test case that have "empty" warnings.
*/
private function removeWarnings(TestSuite $testSuite): void
{
$tests = $testSuite->tests();
foreach ($tests as $key => $test) {
if ($test instanceof TestSuite) {
$this->removeWarnings($test);
}
if ($test instanceof WarningTestCase) {
unset($tests[$key]);
}
}
$testSuite->setTests(array_values($tests));
}
}

View File

@ -5,6 +5,8 @@ declare(strict_types=1);
namespace Pest\Support; namespace Pest\Support;
use Closure; use Closure;
use Pest\Exceptions\ShouldNotHappen;
use PHPUnit\Framework\TestCase;
/** /**
* @internal * @internal
@ -17,9 +19,11 @@ final class ChainableClosure
public static function from(Closure $closure, Closure $next): Closure public static function from(Closure $closure, Closure $next): Closure
{ {
return function () use ($closure, $next): void { return function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */ if (! is_object($this)) { // @phpstan-ignore-line
throw ShouldNotHappen::fromMessage('$this not bound to chainable closure.');
}
call_user_func_array(Closure::bind($closure, $this, $this::class), func_get_args()); call_user_func_array(Closure::bind($closure, $this, $this::class), func_get_args());
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($next, $this, $this::class), func_get_args()); call_user_func_array(Closure::bind($next, $this, $this::class), func_get_args());
}; };
} }
@ -30,9 +34,7 @@ final class ChainableClosure
public static function fromStatic(Closure $closure, Closure $next): Closure public static function fromStatic(Closure $closure, Closure $next): Closure
{ {
return static function () use ($closure, $next): void { return static function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($closure, null, self::class), func_get_args()); call_user_func_array(Closure::bind($closure, null, self::class), func_get_args());
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($next, null, self::class), func_get_args()); call_user_func_array(Closure::bind($next, null, self::class), func_get_args());
}; };
} }

View File

@ -63,7 +63,6 @@ final class Container
*/ */
private function build(string $id): object private function build(string $id): object
{ {
/** @phpstan-ignore-next-line */
$reflectionClass = new ReflectionClass($id); $reflectionClass = new ReflectionClass($id);
if ($reflectionClass->isInstantiable()) { if ($reflectionClass->isInstantiable()) {

View File

@ -25,7 +25,7 @@ final class HigherOrderCallables
* *
* Create a new expectation. Callable values will be executed prior to returning the new expectation. * Create a new expectation. Callable values will be executed prior to returning the new expectation.
* *
* @param callable|TValue $value * @param (callable():TValue)|TValue $value
* *
* @return Expectation<TValue> * @return Expectation<TValue>
*/ */

View File

@ -18,14 +18,14 @@ final class HigherOrderMessage
/** /**
* An optional condition that will determine if the message will be executed. * An optional condition that will determine if the message will be executed.
* *
* @var (callable(): bool)|null * @var (Closure(): bool)|null
*/ */
public $condition; public ?Closure $condition = null;
/** /**
* Creates a new higher order message. * Creates a new higher order message.
* *
* @param array<int, mixed>|null $arguments * @param array<int, mixed> $arguments
*/ */
public function __construct( public function __construct(
public string $filename, public string $filename,
@ -41,7 +41,6 @@ final class HigherOrderMessage
*/ */
public function call(object $target): mixed public function call(object $target): mixed
{ {
/* @phpstan-ignore-next-line */
if (is_callable($this->condition) && call_user_func(Closure::bind($this->condition, $target)) === false) { if (is_callable($this->condition) && call_user_func(Closure::bind($this->condition, $target)) === false) {
return $target; return $target;
} }

View File

@ -19,7 +19,7 @@ final class HigherOrderMessageCollection
* *
* @param array<int, mixed>|null $arguments * @param array<int, mixed>|null $arguments
*/ */
public function add(string $filename, int $line, string $name, array $arguments = null): void public function add(string $filename, int $line, string $name, ?array $arguments): void
{ {
$this->messages[] = new HigherOrderMessage($filename, $line, $name, $arguments); $this->messages[] = new HigherOrderMessage($filename, $line, $name, $arguments);
} }
@ -29,7 +29,7 @@ final class HigherOrderMessageCollection
* *
* @param array<int, mixed>|null $arguments * @param array<int, mixed>|null $arguments
*/ */
public function addWhen(callable $condition, string $filename, int $line, string $name, array $arguments = null): void public function addWhen(callable $condition, string $filename, int $line, string $name, ?array $arguments): void
{ {
$this->messages[] = (new HigherOrderMessage($filename, $line, $name, $arguments))->when($condition); $this->messages[] = (new HigherOrderMessage($filename, $line, $name, $arguments))->when($condition);
} }

View File

@ -31,8 +31,7 @@ final class HigherOrderTapProxy
*/ */
public function __set(string $property, $value): void public function __set(string $property, $value): void
{ {
// @phpstan-ignore-next-line $this->target->{$property} = $value; // @phpstan-ignore-line
$this->target->{$property} = $value;
} }
/** /**
@ -43,8 +42,8 @@ final class HigherOrderTapProxy
public function __get(string $property) public function __get(string $property)
{ {
try { try {
// @phpstan-ignore-next-line /** @throws Throwable */
return $this->target->{$property}; return $this->target->{$property}; // @phpstan-ignore-line
} catch (Throwable $throwable) { } catch (Throwable $throwable) {
Reflection::setPropertyValue($throwable, 'file', Backtrace::file()); Reflection::setPropertyValue($throwable, 'file', Backtrace::file());
Reflection::setPropertyValue($throwable, 'line', Backtrace::line()); Reflection::setPropertyValue($throwable, 'line', Backtrace::line());