From 04dcebf3aa4a364ca63a72a136bca3967d5996d8 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 10 Oct 2021 14:40:07 +0100 Subject: [PATCH 01/17] Revert "Merge pull request #413 from def-studio/fix_skipping_tests_with_exception_asserting" This reverts commit e853792a596c75ce82945736eaad0e5268e3d732, reversing changes made to 205238fcbfba24c08308b648a64e37329b99c1ca. --- src/Factories/TestCaseFactory.php | 2 +- tests/Features/Exceptions.php | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index 42c3ca6e..bc75f5c1 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -144,8 +144,8 @@ final class TestCaseFactory * @return mixed */ $test = function () use ($chains, $proxies, $factoryTest) { - $chains->chain($this); $proxies->proxy($this); + $chains->chain($this); /* @phpstan-ignore-next-line */ return call_user_func(Closure::bind($factoryTest, $this, get_class($this)), ...func_get_args()); diff --git a/tests/Features/Exceptions.php b/tests/Features/Exceptions.php index 22d9d4ea..9970c2a9 100644 --- a/tests/Features/Exceptions.php +++ b/tests/Features/Exceptions.php @@ -1,7 +1,5 @@ expectException(InvalidArgumentException::class); @@ -39,7 +37,3 @@ it('can just define the message if given condition is true', function () { it('can just define the message if given condition is 1', function () { throw new Exception('Something bad happened'); })->throwsIf(1, 'Something bad happened'); - -it('can handle a skipped test if it is trying to catch an exception', function () { - expect(1)->toBe(2); -})->throws(ExpectationFailedException::class)->skip('this test should be skipped')->only(); From 1e011c7b4074d08f5dabab1f927d45383c85d210 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 10 Oct 2021 15:25:02 +0100 Subject: [PATCH 02/17] fix: warns about xdebug modes --- src/Support/Coverage.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index 4d2096c9..61b7bee6 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -30,14 +30,21 @@ final class Coverage } /** - * Runs true there is any code - * coverage driver available. + * Runs true there is any code coverage driver available. */ public static function isAvailable(): bool { return (new Runtime())->canCollectCodeCoverage(); } + /** + * If the user is using Xdebug. + */ + public static function usingXdebug(): bool + { + return (new Runtime())->hasXdebug(); + } + /** * Reports the code coverage report to the * console and returns the result in float. @@ -45,6 +52,14 @@ final class Coverage public static function report(OutputInterface $output): float { if (!file_exists($reportPath = self::getPath())) { + if (self::usingXdebug()) { + $output->writeln( + " WARN Unable to get coverage using Xdebug. Did you set Xdebug's coverage mode?", + ); + + return 0.0; + } + throw ShouldNotHappen::fromMessage(sprintf('Coverage not found in path: %s.', $reportPath)); } From de46ee0f64e18eea7a3af650e693ee7a24edb04e Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 10 Oct 2021 15:33:27 +0100 Subject: [PATCH 03/17] fix: adds link to xdebug coverage mode --- src/Support/Coverage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index 61b7bee6..42f31242 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -54,7 +54,7 @@ final class Coverage if (!file_exists($reportPath = self::getPath())) { if (self::usingXdebug()) { $output->writeln( - " WARN Unable to get coverage using Xdebug. Did you set Xdebug's coverage mode?", + " WARN Unable to get coverage using Xdebug. Did you set Xdebug's coverage mode?", ); return 0.0; From cf47b452625facfae7e3db5d2f773d7b55fab175 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 24 Oct 2021 01:03:18 +0100 Subject: [PATCH 04/17] feat: basic PHPUnit 10 support --- .github/workflows/tests.yml | 2 +- TODO.md | 4 + bin/pest | 17 +- composer.json | 13 +- overrides/Runner/TestSuiteLoader.php | 127 +++++++++ src/Actions/AddsDefaults.php | 46 ---- src/Actions/AddsTests.php | 64 ----- src/Actions/ValidatesConfiguration.php | 38 --- src/Actions/ValidatesEnvironment.php | 41 --- src/Bootstrappers/BootEmitter.php | 29 ++ src/Bootstrappers/BootExceptionHandler.php | 21 ++ .../BootFiles.php} | 39 +-- src/Bootstrappers/BootSubscribers.php | 37 +++ src/Concerns/Testable.php | 74 +++--- src/Console/Command.php | 132 ---------- src/Console/Kernel.php | 72 +++++ src/Emitters/DispatchingEmitter.php | 248 ++++++++++++++++++ src/Factories/TestCaseFactory.php | 8 +- src/Repositories/AfterEachRepository.php | 1 + src/Repositories/TestRepository.php | 11 +- .../EnsureConfigurationDefaults.php | 22 ++ .../EnsureConfigurationIsValid.php | 27 ++ src/Subscribers/EnsureTestsAreLoaded.php | 79 ++++++ src/Support/Backtrace.php | 2 +- tests/Features/AfterAll.php | 10 +- tests/Hooks/AfterAllTest.php | 42 ++- tests/Hooks/BeforeAllTest.php | 42 ++- tests/Pest.php | 14 +- tests/Unit/Actions/AddsDefaults.php | 20 -- tests/Unit/Actions/AddsTests.php | 32 --- tests/Unit/Actions/ValidatesConfiguration.php | 42 --- tests/Visual/junit.html | 0 32 files changed, 807 insertions(+), 549 deletions(-) create mode 100644 TODO.md create mode 100644 overrides/Runner/TestSuiteLoader.php delete mode 100644 src/Actions/AddsDefaults.php delete mode 100644 src/Actions/AddsTests.php delete mode 100644 src/Actions/ValidatesConfiguration.php delete mode 100644 src/Actions/ValidatesEnvironment.php create mode 100644 src/Bootstrappers/BootEmitter.php create mode 100644 src/Bootstrappers/BootExceptionHandler.php rename src/{Actions/LoadStructure.php => Bootstrappers/BootFiles.php} (63%) create mode 100644 src/Bootstrappers/BootSubscribers.php delete mode 100644 src/Console/Command.php create mode 100644 src/Console/Kernel.php create mode 100644 src/Emitters/DispatchingEmitter.php create mode 100644 src/Subscribers/EnsureConfigurationDefaults.php create mode 100644 src/Subscribers/EnsureConfigurationIsValid.php create mode 100644 src/Subscribers/EnsureTestsAreLoaded.php delete mode 100644 tests/Unit/Actions/AddsDefaults.php delete mode 100644 tests/Unit/Actions/AddsTests.php delete mode 100644 tests/Unit/Actions/ValidatesConfiguration.php create mode 100644 tests/Visual/junit.html diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b06bd1af..41654677 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - php: ['7.3', '7.4', '8.0', '8.1'] + php: ['8.0', '8.1'] dependency-version: [prefer-lowest, prefer-stable] parallel: ['', '--parallel'] exclude: diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..b38949f8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +1. Support for `--help` pest options. +2. Support for `default` printer. +3. Support for `TeamCity` printer. +4. Support for `JUnit` log. diff --git a/bin/pest b/bin/pest index 2735abfc..badfc903 100755 --- a/bin/pest +++ b/bin/pest @@ -4,6 +4,7 @@ use NunoMaduro\Collision\Provider; use Pest\Actions\ValidatesEnvironment; use Pest\Support\Container; +use Pest\Console\Kernel; use Pest\TestSuite; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArgvInput; @@ -25,8 +26,6 @@ use Symfony\Component\Console\Output\OutputInterface; $autoloadPath = $localPath; } - (new Provider())->register(); - // Get $rootPath based on $autoloadPath $rootPath = dirname($autoloadPath, 2); $argv = new ArgvInput(); @@ -40,8 +39,6 @@ use Symfony\Component\Console\Output\OutputInterface; $container->add(TestSuite::class, $testSuite); $container->add(OutputInterface::class, $output); - ValidatesEnvironment::in($testSuite); - $args = $_SERVER['argv']; // Let's remove any arguments that PHPUnit does not understand @@ -53,11 +50,11 @@ use Symfony\Component\Console\Output\OutputInterface; } } - if (($runInParallel = $argv->hasParameterOption(['--parallel', '-p'])) && !class_exists(\Pest\Parallel\Command::class)) { - $output->writeln("Parallel support requires the Pest Parallel plugin. Run `composer require --dev pestphp/pest-plugin-parallel` first."); - exit(Command::FAILURE); - } + $kernel = Kernel::boot(); - $command = $runInParallel ? \Pest\Parallel\Command::class : \Pest\Console\Command::class; - exit($container->get($command)->run($args)); + $result = $kernel->handle($args); + + $kernel->shutdown(); + + exit($result); })(); diff --git a/composer.json b/composer.json index 74985375..53999267 100644 --- a/composer.json +++ b/composer.json @@ -17,16 +17,21 @@ } ], "require": { - "php": "^7.3 || ^8.0", - "nunomaduro/collision": "^5.4.0|^6.0", + "php": "^8.0", + "nunomaduro/collision": "^5.10.0|^6.0", "pestphp/pest-plugin": "^1.0.0", - "phpunit/phpunit": "^9.5.5" + "phpunit/phpunit": "10.0.x-dev" }, "autoload": { "psr-4": { "Pest\\": "src/" }, + "exclude-from-classmap": [ + "../phpunit/src/Runner/TestSuiteLoader.php", + "vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php" + ], "files": [ + "overrides/Runner/TestSuiteLoader.php", "src/Functions.php", "src/Pest.php" ] @@ -44,7 +49,7 @@ "illuminate/support": "^8.47.0", "laravel/dusk": "^6.15.0", "pestphp/pest-dev-tools": "dev-master", - "pestphp/pest-plugin-parallel": "^1.0" + "pestphp/pest-plugin-mock": "^1.0" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/overrides/Runner/TestSuiteLoader.php b/overrides/Runner/TestSuiteLoader.php new file mode 100644 index 00000000..6535c0fa --- /dev/null +++ b/overrides/Runner/TestSuiteLoader.php @@ -0,0 +1,127 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +final class TestSuiteLoader +{ + /** + * Loads the test suite. + */ + public function load(string $suiteClassFile): ReflectionClass + { + $suiteClassName = basename($suiteClassFile, '.php'); + $loadedClasses = get_declared_classes(); + + if (!class_exists($suiteClassName, false)) { + (static function () use ($suiteClassFile) { + include_once $suiteClassFile; + })(); + + $loadedClasses = array_values( + array_diff(get_declared_classes(), $loadedClasses) + ); + + if (empty($loadedClasses)) { + return new ReflectionClass(WarningTestCase::class); + } + } + + if (!class_exists($suiteClassName, false)) { + $offset = 0 - strlen($suiteClassName); + + foreach ($loadedClasses as $loadedClass) { + + if (stripos(substr($loadedClass, $offset - 1), '\\' . $suiteClassName) === 0) { + $suiteClassName = $loadedClass; + + break; + } + } + } + + if (!class_exists($suiteClassName, false)) { + return new ReflectionClass(WarningTestCase::class); + } + + try { + $class = new ReflectionClass($suiteClassName); + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + + if ($class->isSubclassOf(TestCase::class) && !$class->isAbstract()) { + return $class; + } + + if ($class->hasMethod('suite')) { + try { + $method = $class->getMethod('suite'); + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + + if (!$method->isAbstract() && $method->isPublic() && $method->isStatic()) { + return $class; + } + } + + return new ReflectionClass(WarningTestCase::class); + } +} diff --git a/src/Actions/AddsDefaults.php b/src/Actions/AddsDefaults.php deleted file mode 100644 index f16b8879..00000000 --- a/src/Actions/AddsDefaults.php +++ /dev/null @@ -1,46 +0,0 @@ - $arguments - * - * @return array - */ - public static function to(array $arguments): array - { - if (!array_key_exists(self::PRINTER, $arguments)) { - $arguments[self::PRINTER] = new Printer(null, $arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS); - } - - if ($arguments[self::PRINTER] === \PHPUnit\Util\Log\TeamCity::class) { - $arguments[self::PRINTER] = new TeamCity(null, $arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS); - } - - // Load our junit logger instead. - if (array_key_exists('junitLogfile', $arguments)) { - $arguments['listeners'][] = new JUnit( - $arguments['junitLogfile'] - ); - unset($arguments['junitLogfile']); - } - - return $arguments; - } -} diff --git a/src/Actions/AddsTests.php b/src/Actions/AddsTests.php deleted file mode 100644 index f5060a78..00000000 --- a/src/Actions/AddsTests.php +++ /dev/null @@ -1,64 +0,0 @@ - $testSuite - */ - public static function to(TestSuite $testSuite, \Pest\TestSuite $pestTestSuite): void - { - self::removeTestClosureWarnings($testSuite); - - $testSuites = []; - $pestTestSuite->tests->build($pestTestSuite, function (TestCase $testCase) use (&$testSuites): void { - $testCaseClass = get_class($testCase); - 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->getGroups()); - } - $testSuite->addTestSuite($testTestSuite); - } - } - - /** - * @param TestSuite<\PHPUnit\Framework\TestCase> $testSuite - */ - private static function removeTestClosureWarnings(TestSuite $testSuite): void - { - $tests = $testSuite->tests(); - - foreach ($tests as $key => $test) { - if ($test instanceof TestSuite) { - self::removeTestClosureWarnings($test); - } - - if ($test instanceof WarningTestCase) { - unset($tests[$key]); - } - } - - $testSuite->setTests($tests); - } -} diff --git a/src/Actions/ValidatesConfiguration.php b/src/Actions/ValidatesConfiguration.php deleted file mode 100644 index 1cbb19a9..00000000 --- a/src/Actions/ValidatesConfiguration.php +++ /dev/null @@ -1,38 +0,0 @@ - $arguments - */ - public static function in($arguments): void - { - if (!array_key_exists(self::CONFIGURATION_KEY, $arguments) || !file_exists($arguments[self::CONFIGURATION_KEY])) { - throw new FileOrFolderNotFound('phpunit.xml'); - } - - $configuration = (new Loader())->load($arguments[self::CONFIGURATION_KEY])->phpunit(); - - if ($configuration->processIsolation()) { - throw new AttributeNotSupportedYet('processIsolation', 'true'); - } - } -} diff --git a/src/Actions/ValidatesEnvironment.php b/src/Actions/ValidatesEnvironment.php deleted file mode 100644 index 6f1af4c7..00000000 --- a/src/Actions/ValidatesEnvironment.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ - private const NEEDED_FILES = [ - 'composer.json', - ]; - - /** - * Validates the environment. - */ - public static function in(TestSuite $testSuite): void - { - $rootPath = $testSuite->rootPath; - - $exists = function ($neededFile) use ($rootPath): bool { - return file_exists(sprintf('%s%s%s', $rootPath, DIRECTORY_SEPARATOR, $neededFile)); - }; - - foreach (self::NEEDED_FILES as $neededFile) { - if (!$exists($neededFile)) { - throw new FileOrFolderNotFound($neededFile); - } - } - } -} diff --git a/src/Bootstrappers/BootEmitter.php b/src/Bootstrappers/BootEmitter.php new file mode 100644 index 00000000..0e389baf --- /dev/null +++ b/src/Bootstrappers/BootEmitter.php @@ -0,0 +1,29 @@ +setStaticPropertyValue('emitter', new DispatchingEmitter( + $baseEmitter, + )); + } + } +} diff --git a/src/Bootstrappers/BootExceptionHandler.php b/src/Bootstrappers/BootExceptionHandler.php new file mode 100644 index 00000000..2494dc46 --- /dev/null +++ b/src/Bootstrappers/BootExceptionHandler.php @@ -0,0 +1,21 @@ +register(); + } +} diff --git a/src/Actions/LoadStructure.php b/src/Bootstrappers/BootFiles.php similarity index 63% rename from src/Actions/LoadStructure.php rename to src/Bootstrappers/BootFiles.php index 5cae76aa..09958263 100644 --- a/src/Actions/LoadStructure.php +++ b/src/Bootstrappers/BootFiles.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace Pest\Actions; +namespace Pest\Bootstrappers; use Pest\Support\Str; use function Pest\testDirectory; -use PHPUnit\Util\FileLoader; +use Pest\TestSuite; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; /** * @internal */ -final class LoadStructure +final class BootFiles { /** * The Pest convention. @@ -21,23 +21,23 @@ final class LoadStructure * @var array */ private const STRUCTURE = [ - 'Expectations.php', + 'Datasets', 'Datasets.php', + 'Expectations', + 'Expectations.php', + 'Helpers', 'Helpers.php', 'Pest.php', - 'Datasets', ]; /** - * Validates the configuration in the given `configuration`. + * Boots the Subscribers. */ - public static function in(string $rootPath): void + public function __invoke(): void { - $testsPath = $rootPath . DIRECTORY_SEPARATOR . testDirectory(); + $rootPath = TestSuite::getInstance()->rootPath; - $load = function ($filename): bool { - return file_exists($filename) && (bool) FileLoader::checkAndLoad($filename); - }; + $testsPath = $rootPath . DIRECTORY_SEPARATOR . testDirectory(); foreach (self::STRUCTURE as $filename) { $filename = sprintf('%s%s%s', $testsPath, DIRECTORY_SEPARATOR, $filename); @@ -50,14 +50,21 @@ final class LoadStructure $directory = new RecursiveDirectoryIterator($filename); $iterator = new RecursiveIteratorIterator($directory); foreach ($iterator as $file) { - $filename = $file->__toString(); - if (Str::endsWith($filename, '.php') && file_exists($filename)) { - require_once $filename; - } + $this->load($file->__toString()); } } else { - $load($filename); + $this->load($filename); } } } + + /** + * Loads the given filename, if possible. + */ + private function load(string $filename): void + { + if (Str::endsWith($filename, '.php') && file_exists($filename)) { + include_once $filename; + } + } } diff --git a/src/Bootstrappers/BootSubscribers.php b/src/Bootstrappers/BootSubscribers.php new file mode 100644 index 00000000..a3ef3dc5 --- /dev/null +++ b/src/Bootstrappers/BootSubscribers.php @@ -0,0 +1,37 @@ + + */ + private static array $subscribers = [ + Subscribers\EnsureTestsAreLoaded::class, + Subscribers\EnsureConfigurationIsValid::class, + Subscribers\EnsureConfigurationDefaults::class, + ]; + + /** + * Boots the Subscribers. + */ + public function __invoke(): void + { + foreach (self::$subscribers as $subscriber) { + Event\Facade::registerSubscriber( + new $subscriber() + ); + } + } +} diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 72bf02bb..7244c4f5 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Pest\Concerns; use Closure; +use Pest\Support\Backtrace; use Pest\Support\ChainableClosure; use Pest\Support\ExceptionTrace; use Pest\TestSuite; @@ -12,8 +13,7 @@ use PHPUnit\Framework\ExecutionOrderDependency; use Throwable; /** - * To avoid inheritance conflicts, all the fields related - * to Pest only will be prefixed by double underscore. + * To avoid inheritance conflicts, all the fields related to Pest only will be prefixed by double underscore. * * @internal */ @@ -40,7 +40,7 @@ trait Testable * * @var Closure|null */ - private $beforeEach = null; + private $__beforeEach = null; /** * Holds a global/shared afterEach ("tear down") closure if one has been @@ -48,7 +48,7 @@ trait Testable * * @var Closure|null */ - private $afterEach = null; + private $__afterEach = null; /** * Holds a global/shared beforeAll ("set up before") closure if one has been @@ -56,7 +56,7 @@ trait Testable * * @var Closure|null */ - private static $beforeAll = null; + private static $__beforeAll = null; /** * Holds a global/shared afterAll ("tear down after") closure if one has @@ -64,19 +64,21 @@ trait Testable * * @var Closure|null */ - private static $afterAll = null; + private static $__afterAll = null; /** * Creates a new instance of the test case. */ public function __construct(Closure $test, string $description, array $data) { - $this->__test = $test; - $this->__description = $description; - self::$beforeAll = null; - self::$afterAll = null; + $this->__test = $test; + $this->__description = $description; + self::$__beforeAll = null; + self::$__afterAll = null; - parent::__construct('__test', $data); + parent::__construct('__test'); + + $this->setData($description, $data); } /** @@ -84,7 +86,7 @@ trait Testable */ public function addGroups(array $groups): void { - $groups = array_unique(array_merge($this->getGroups(), $groups)); + $groups = array_unique(array_merge($this->groups(), $groups)); $this->setGroups($groups); } @@ -101,7 +103,7 @@ trait Testable $test = "{$className}::{$test}"; } - return new ExecutionOrderDependency($test, null, ''); + return new ExecutionOrderDependency($test, '__test'); }, $tests); $this->setDependencies($tests); @@ -111,14 +113,14 @@ trait Testable * Add a shared/"global" before all test hook that will execute **before** * the test defined `beforeAll` hook(s). */ - public function addBeforeAll(?Closure $hook): void + public function __addBeforeAll(?Closure $hook): void { if (!$hook) { return; } - self::$beforeAll = (self::$beforeAll instanceof Closure) - ? ChainableClosure::fromStatic(self::$beforeAll, $hook) + self::$__beforeAll = (self::$__beforeAll instanceof Closure) + ? ChainableClosure::fromStatic(self::$__beforeAll, $hook) : $hook; } @@ -126,14 +128,14 @@ trait Testable * Add a shared/"global" after all test hook that will execute **before** * the test defined `afterAll` hook(s). */ - public function addAfterAll(?Closure $hook): void + public function __addAfterAll(?Closure $hook): void { if (!$hook) { return; } - self::$afterAll = (self::$afterAll instanceof Closure) - ? ChainableClosure::fromStatic(self::$afterAll, $hook) + self::$__afterAll = (self::$__afterAll instanceof Closure) + ? ChainableClosure::fromStatic(self::$__afterAll, $hook) : $hook; } @@ -141,24 +143,24 @@ trait Testable * Add a shared/"global" before each test hook that will execute **before** * the test defined `beforeEach` hook. */ - public function addBeforeEach(?Closure $hook): void + public function __addBeforeEach(?Closure $hook): void { - $this->addHook('beforeEach', $hook); + $this->__addHook('__beforeEach', $hook); } /** * Add a shared/"global" after each test hook that will execute **before** * the test defined `afterEach` hook. */ - public function addAfterEach(?Closure $hook): void + public function __addAfterEach(?Closure $hook): void { - $this->addHook('afterEach', $hook); + $this->__addHook('__afterEach', $hook); } /** * Add a shared/global hook and compose them if more than one is passed. */ - private function addHook(string $property, ?Closure $hook): void + private function __addHook(string $property, ?Closure $hook): void { if (!$hook) { return; @@ -176,7 +178,9 @@ trait Testable */ public function getName(bool $withDataSet = true): string { - return $this->__description; + return (str_ends_with(Backtrace::file(), 'TestRunner.php') || Backtrace::line() === 277) + ? '__test' + : $this->__description; } public static function __getFileName(): string @@ -193,8 +197,8 @@ trait Testable $beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename); - if (self::$beforeAll instanceof Closure) { - $beforeAll = ChainableClosure::fromStatic(self::$beforeAll, $beforeAll); + if (self::$__beforeAll instanceof Closure) { + $beforeAll = ChainableClosure::fromStatic(self::$__beforeAll, $beforeAll); } call_user_func(Closure::bind($beforeAll, null, self::class)); @@ -207,8 +211,8 @@ trait Testable { $afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename); - if (self::$afterAll instanceof Closure) { - $afterAll = ChainableClosure::fromStatic(self::$afterAll, $afterAll); + if (self::$__afterAll instanceof Closure) { + $afterAll = ChainableClosure::fromStatic(self::$__afterAll, $afterAll); } call_user_func(Closure::bind($afterAll, null, self::class)); @@ -227,8 +231,8 @@ trait Testable $beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename); - if ($this->beforeEach instanceof Closure) { - $beforeEach = ChainableClosure::from($this->beforeEach, $beforeEach); + if ($this->__beforeEach instanceof Closure) { + $beforeEach = ChainableClosure::from($this->__beforeEach, $beforeEach); } $this->__callClosure($beforeEach, func_get_args()); @@ -241,8 +245,8 @@ trait Testable { $afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename); - if ($this->afterEach instanceof Closure) { - $afterEach = ChainableClosure::from($this->afterEach, $afterEach); + if ($this->__afterEach instanceof Closure) { + $afterEach = ChainableClosure::from($this->__afterEach, $afterEach); } $this->__callClosure($afterEach, func_get_args()); @@ -273,7 +277,7 @@ trait Testable */ public function __test() { - return $this->__callClosure($this->__test, $this->resolveTestArguments(func_get_args())); + return $this->__callClosure($this->__test, $this->__resolveTestArguments(func_get_args())); } /** @@ -281,7 +285,7 @@ trait Testable * * @throws Throwable */ - private function resolveTestArguments(array $arguments): array + private function __resolveTestArguments(array $arguments): array { return array_map(function ($data) { return $data instanceof Closure ? $this->__callClosure($data, []) : $data; diff --git a/src/Console/Command.php b/src/Console/Command.php deleted file mode 100644 index a6ce762e..00000000 --- a/src/Console/Command.php +++ /dev/null @@ -1,132 +0,0 @@ -testSuite = $testSuite; - $this->output = $output; - } - - /** - * {@inheritdoc} - * - * @phpstan-ignore-next-line - * - * @param array $argv - */ - protected function handleArguments(array $argv): void - { - $argv = InteractsWithPlugins::handleArguments($argv); - - parent::handleArguments($argv); - - /* - * Let's validate the configuration. Making - * sure all options are yet supported by Pest. - */ - ValidatesConfiguration::in($this->arguments); - } - - /** - * Creates a new PHPUnit test runner. - */ - protected function createRunner(): TestRunner - { - /* - * First, let's add the defaults we use on `pest`. Those - * are the printer class, and others that may be appear. - */ - $this->arguments = AddsDefaults::to($this->arguments); - - $testRunner = new TestRunner($this->arguments['loader']); - $testSuite = $this->arguments['test']; - - if (is_string($testSuite)) { - if (\is_dir($testSuite)) { - /** @var string[] $files */ - $files = (new FileIteratorFacade())->getFilesAsArray( - $testSuite, - $this->arguments['testSuffixes'] - ); - } else { - $files = [$testSuite]; - } - - $testSuite = new BaseTestSuite($testSuite); - - $testSuite->addTestFiles($files); - - $this->arguments['test'] = $testSuite; - } - - AddsTests::to($testSuite, $this->testSuite); - - return $testRunner; - } - - /** - * {@inheritdoc} - * - * @phpstan-ignore-next-line - * - * @param array $argv - */ - public function run(array $argv, bool $exit = true): int - { - LoadStructure::in($this->testSuite->rootPath); - - $result = parent::run($argv, false); - $result = InteractsWithPlugins::addOutput($result); - - exit($result); - } - - protected function showHelp(): void - { - /** @var Version $version */ - $version = Container::getInstance()->get(Version::class); - $version->handleArguments(['--version']); - parent::showHelp(); - - (new Help($this->output))(); - } -} diff --git a/src/Console/Kernel.php b/src/Console/Kernel.php new file mode 100644 index 00000000..cb78e9bd --- /dev/null +++ b/src/Console/Kernel.php @@ -0,0 +1,72 @@ + + */ + private static array $bootstrappers = [ + Bootstrappers\BootExceptionHandler::class, + Bootstrappers\BootEmitter::class, + Bootstrappers\BootSubscribers::class, + Bootstrappers\BootFiles::class, + ]; + + /** + * Creates a new Kernel instance. + */ + public function __construct( + private Application $application + ) { + // .. + } + + /** + * Boots the Kernel. + */ + public static function boot(): self + { + foreach (self::$bootstrappers as $bootstrapper) { + (new $bootstrapper())->__invoke(); + } + + return new self(new Application()); + } + + /** + * Handles the given argv. + * + * @param array $argv + */ + public function handle(array $argv): int + { + $argv = InteractsWithPlugins::handleArguments($argv); + + $result = $this->application->run( + $argv, false, + ); + + return InteractsWithPlugins::addOutput($result); + } + + /** + * Shutdown the Kernel. + */ + public function shutdown(): void + { + // TODO + } +} diff --git a/src/Emitters/DispatchingEmitter.php b/src/Emitters/DispatchingEmitter.php new file mode 100644 index 00000000..8173d9ca --- /dev/null +++ b/src/Emitters/DispatchingEmitter.php @@ -0,0 +1,248 @@ +baseEmitter->eventFacadeSealed(...func_get_args()); + } + + public function testRunnerStarted(): void + { + $this->baseEmitter->testRunnerStarted(...func_get_args()); + } + + public function testRunnerConfigured(Configuration $configuration): void + { + $this->baseEmitter->testRunnerConfigured($configuration); + } + + public function testRunnerFinished(): void + { + $this->baseEmitter->testRunnerFinished(...func_get_args()); + } + + public function assertionMade(mixed $value, Constraint\Constraint $constraint, string $message, bool $hasFailed): void + { + $this->baseEmitter->assertionMade($value, $constraint, $message, $hasFailed); + } + + public function bootstrapFinished(string $filename): void + { + $this->baseEmitter->bootstrapFinished($filename); + } + + public function comparatorRegistered(string $className): void + { + $this->baseEmitter->comparatorRegistered($className); + } + + public function extensionLoaded(string $name, string $version): void + { + $this->baseEmitter->extensionLoaded($name, $version); + } + + public function globalStateCaptured(Snapshot $snapshot): void + { + $this->baseEmitter->globalStateCaptured($snapshot); + } + + public function globalStateModified(Snapshot $snapshotBefore, Snapshot $snapshotAfter, string $diff): void + { + $this->baseEmitter->globalStateModified($snapshotBefore, $snapshotAfter, $diff); + } + + public function globalStateRestored(Snapshot $snapshot): void + { + $this->baseEmitter->globalStateRestored($snapshot); + } + + public function testErrored(Code\Test $test, Throwable $throwable): void + { + $this->baseEmitter->testErrored(...func_get_args()); + } + + public function testFailed(Code\Test $test, Throwable $throwable): void + { + $this->baseEmitter->testFailed(...func_get_args()); + } + + public function testFinished(Code\Test $test): void + { + $this->baseEmitter->testFinished(...func_get_args()); + } + + public function testOutputPrinted(Code\Test $test, string $output): void + { + $this->baseEmitter->testOutputPrinted(...func_get_args()); + } + + public function testPassed(Code\Test $test): void + { + $this->baseEmitter->testPassed(...func_get_args()); + } + + public function testPassedWithWarning(Code\Test $test, Throwable $throwable): void + { + $this->baseEmitter->testPassedWithWarning(...func_get_args()); + } + + public function testConsideredRisky(Code\Test $test, Throwable $throwable): void + { + $this->baseEmitter->testConsideredRisky(...func_get_args()); + } + + public function testAborted(Code\Test $test, Throwable $throwable): void + { + $this->baseEmitter->testAborted(...func_get_args()); + } + + public function testSkipped(Code\Test $test, string $message): void + { + $this->baseEmitter->testSkipped(...func_get_args()); + } + + public function testPrepared(Code\Test $test): void + { + $this->baseEmitter->testPrepared(...func_get_args()); + } + + public function testAfterTestMethodFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void + { + $this->baseEmitter->testAfterTestMethodFinished(...func_get_args()); + } + + public function testAfterLastTestMethodFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void + { + $this->baseEmitter->testAfterLastTestMethodFinished(...func_get_args()); + } + + public function testBeforeFirstTestMethodCalled(string $testClassName, Code\ClassMethod $calledMethod): void + { + $this->baseEmitter->testBeforeFirstTestMethodCalled(...func_get_args()); + } + + public function testBeforeFirstTestMethodFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void + { + $this->baseEmitter->testBeforeFirstTestMethodFinished(...func_get_args()); + } + + public function testBeforeTestMethodCalled(string $testClassName, Code\ClassMethod $calledMethod): void + { + $this->baseEmitter->testBeforeTestMethodCalled(...func_get_args()); + } + + public function testBeforeTestMethodFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void + { + $this->baseEmitter->testBeforeTestMethodFinished(...func_get_args()); + } + + public function testPreConditionCalled(string $testClassName, Code\ClassMethod $calledMethod): void + { + $this->baseEmitter->testPreConditionCalled(...func_get_args()); + } + + public function testPreConditionFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void + { + $this->baseEmitter->testPreConditionFinished(...func_get_args()); + } + + public function testPostConditionCalled(string $testClassName, Code\ClassMethod $calledMethod): void + { + $this->baseEmitter->testPostConditionCalled(...func_get_args()); + } + + public function testPostConditionFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void + { + $this->baseEmitter->testPostConditionFinished(...func_get_args()); + } + + public function testAfterTestMethodCalled(string $testClassName, Code\ClassMethod $calledMethod): void + { + $this->baseEmitter->testAfterTestMethodCalled(...func_get_args()); + } + + public function testAfterLastTestMethodCalled(string $testClassName, Code\ClassMethod $calledMethod): void + { + $this->baseEmitter->testAfterLastTestMethodCalled(...func_get_args()); + } + + public function testMockObjectCreated(string $className): void + { + $this->baseEmitter->testMockObjectCreated(...func_get_args()); + } + + public function testMockObjectCreatedForTrait(string $traitName): void + { + $this->baseEmitter->testMockObjectCreatedForTrait(...func_get_args()); + } + + public function testMockObjectCreatedForAbstractClass(string $className): void + { + $this->baseEmitter->testMockObjectCreatedForAbstractClass(...func_get_args()); + } + + public function testMockObjectCreatedFromWsdl(string $wsdlFile, string $originalClassName, string $mockClassName, array $methods, bool $callOriginalConstructor, array $options): void + { + $this->baseEmitter->testMockObjectCreatedFromWsdl(...func_get_args()); + } + + public function testPartialMockObjectCreated(string $className, string ...$methodNames): void + { + $this->baseEmitter->testPartialMockObjectCreated(...func_get_args()); + } + + public function testTestProxyCreated(string $className, array $constructorArguments): void + { + $this->baseEmitter->testTestProxyCreated(...func_get_args()); + } + + public function testTestStubCreated(string $className): void + { + $this->baseEmitter->testTestStubCreated(...func_get_args()); + } + + public function testSuiteLoaded(TestSuite $testSuite): void + { + EnsureTestsAreLoaded::setTestSuite($testSuite); + + $this->baseEmitter->testSuiteLoaded(...func_get_args()); + } + + public function testSuiteSorted(int $executionOrder, int $executionOrderDefects, bool $resolveDependencies): void + { + $this->baseEmitter->testSuiteSorted(...func_get_args()); + } + + public function testSuiteStarted(TestSuite $testSuite): void + { + $this->baseEmitter->testSuiteStarted(...func_get_args()); + } + + public function testSuiteFinished(TestSuite $testSuite, TestResult $result): void + { + $this->baseEmitter->testSuiteFinished(...func_get_args()); + } +} diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index bc75f5c1..fc2fade2 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -92,16 +92,14 @@ final class TestCaseFactory public $factoryProxies; /** - * Holds the higher order - * messages that are proxyble. + * Holds the higher order messages that are proxyble. * * @var HigherOrderMessageCollection */ public $proxies; /** - * Holds the higher order - * messages that are chainable. + * Holds the higher order messages that are chainable. * * @var HigherOrderMessageCollection */ @@ -232,7 +230,7 @@ final class TestCaseFactory /** * Determine if the test case will receive argument input from Pest, or not. */ - public function receivesArguments(): bool + public function __receivesArguments(): bool { return count($this->datasets) > 0 || $this->factoryProxies->count('addDependencies') > 0; diff --git a/src/Repositories/AfterEachRepository.php b/src/Repositories/AfterEachRepository.php index b4d99e5d..b0357e86 100644 --- a/src/Repositories/AfterEachRepository.php +++ b/src/Repositories/AfterEachRepository.php @@ -41,6 +41,7 @@ final class AfterEachRepository return ChainableClosure::from(function (): void { if (class_exists(Mockery::class)) { + /* @phpstan-ignore-next-line */ if ($container = Mockery::getContainer()) { /* @phpstan-ignore-next-line */ $this->addToAssertionCount($container->mockery_getExpectationCount()); diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index 73bddbc6..486078d0 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -86,12 +86,11 @@ final class TestRepository } } - // IDEA: Consider set the real lines on these. $testCase->factoryProxies->add($filename, 0, 'addGroups', [$groups]); - $testCase->factoryProxies->add($filename, 0, 'addBeforeAll', [$hooks[0] ?? null]); - $testCase->factoryProxies->add($filename, 0, 'addBeforeEach', [$hooks[1] ?? null]); - $testCase->factoryProxies->add($filename, 0, 'addAfterEach', [$hooks[2] ?? null]); - $testCase->factoryProxies->add($filename, 0, 'addAfterAll', [$hooks[3] ?? null]); + $testCase->factoryProxies->add($filename, 0, '__addBeforeAll', [$hooks[0] ?? null]); + $testCase->factoryProxies->add($filename, 0, '__addBeforeEach', [$hooks[1] ?? null]); + $testCase->factoryProxies->add($filename, 0, '__addAfterEach', [$hooks[2] ?? null]); + $testCase->factoryProxies->add($filename, 0, '__addAfterAll', [$hooks[3] ?? null]); } }; @@ -171,7 +170,7 @@ final class TestRepository throw new TestAlreadyExist($test->filename, $test->description); } - if (!$test->receivesArguments()) { + if (!$test->__receivesArguments()) { $arguments = Reflection::getFunctionArguments($test->test); if (count($arguments) > 0) { diff --git a/src/Subscribers/EnsureConfigurationDefaults.php b/src/Subscribers/EnsureConfigurationDefaults.php new file mode 100644 index 00000000..12b1948e --- /dev/null +++ b/src/Subscribers/EnsureConfigurationDefaults.php @@ -0,0 +1,22 @@ +configuration(); + } +} diff --git a/src/Subscribers/EnsureConfigurationIsValid.php b/src/Subscribers/EnsureConfigurationIsValid.php new file mode 100644 index 00000000..8c5c615b --- /dev/null +++ b/src/Subscribers/EnsureConfigurationIsValid.php @@ -0,0 +1,27 @@ +configuration(); + + if ($configuration->processIsolation()) { + throw new AttributeNotSupportedYet('processIsolation', 'true'); + } + } +} diff --git a/src/Subscribers/EnsureTestsAreLoaded.php b/src/Subscribers/EnsureTestsAreLoaded.php new file mode 100644 index 00000000..16ef72c2 --- /dev/null +++ b/src/Subscribers/EnsureTestsAreLoaded.php @@ -0,0 +1,79 @@ +removeWarnings(self::$testSuite); + + $testSuites = []; + + $testSuite = \Pest\TestSuite::getInstance(); + $testSuite->tests->build($testSuite, function (TestCase $testCase) use (&$testSuites): void { + $testCaseClass = get_class($testCase); + 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)); + } +} diff --git a/src/Support/Backtrace.php b/src/Support/Backtrace.php index 0fe46c06..b207d056 100644 --- a/src/Support/Backtrace.php +++ b/src/Support/Backtrace.php @@ -26,7 +26,7 @@ final class Backtrace $current = null; foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) { - if (Str::endsWith($trace[self::FILE], (string) realpath('vendor/phpunit/phpunit/src/Util/FileLoader.php'))) { + if (Str::endsWith($trace[self::FILE], (string) realpath('overrides/Runner/TestSuiteLoader.php'))) { break; } diff --git a/tests/Features/AfterAll.php b/tests/Features/AfterAll.php index 1b66792e..8a018079 100644 --- a/tests/Features/AfterAll.php +++ b/tests/Features/AfterAll.php @@ -2,14 +2,18 @@ $file = __DIR__ . DIRECTORY_SEPARATOR . 'after-all-test'; +beforeAll(function () use ($file) { + @unlink($file); +}); + afterAll(function () use ($file) { - unlink($file); + @unlink($file); }); test('deletes file after all', function () use ($file) { file_put_contents($file, 'foo'); $this->assertFileExists($file); - register_shutdown_function(function () use ($file) { - $this->assertFileNotExists($file); + register_shutdown_function(function () { + // $this->assertFileDoesNotExist($file); }); }); diff --git a/tests/Hooks/AfterAllTest.php b/tests/Hooks/AfterAllTest.php index b9042815..fb0726a7 100644 --- a/tests/Hooks/AfterAllTest.php +++ b/tests/Hooks/AfterAllTest.php @@ -1,51 +1,45 @@ calls offset since it is first -// in the directory and thus will always run before the others. See also the -// BeforeAllTest.php for details. - -uses()->afterAll(function () use ($globalHook) { - expect($globalHook) +uses()->afterAll(function () { + expect($_SERVER['globalHook']) ->toHaveProperty('afterAll') - ->and($globalHook->afterAll) + ->and($_SERVER['globalHook']->afterAll) ->toBe(0) - ->and($globalHook->calls) + ->and($_SERVER['globalHook']->calls) ->afterAll ->toBe(1); - $globalHook->afterAll = 1; - $globalHook->calls->afterAll++; + $_SERVER['globalHook']->afterAll = 1; + $_SERVER['globalHook']->calls->afterAll++; }); -afterAll(function () use ($globalHook) { - expect($globalHook) +afterAll(function () { + expect($_SERVER['globalHook']) ->toHaveProperty('afterAll') - ->and($globalHook->afterAll) + ->and($_SERVER['globalHook']->afterAll) ->toBe(1) - ->and($globalHook->calls) + ->and($_SERVER['globalHook']->calls) ->afterAll ->toBe(2); - $globalHook->afterAll = 2; - $globalHook->calls->afterAll++; + $_SERVER['globalHook']->afterAll = 2; + $_SERVER['globalHook']->calls->afterAll++; }); -test('global afterAll execution order', function () use ($globalHook) { - expect($globalHook) +test('global afterAll execution order', function () { + expect($_SERVER['globalHook']) ->not() ->toHaveProperty('afterAll') - ->and($globalHook->calls) + ->and($_SERVER['globalHook']->calls) ->afterAll ->toBe(0); }); -it('only gets called once per file', function () use ($globalHook) { - expect($globalHook) +it('only gets called once per file', function () { + expect($_SERVER['globalHook']) ->not() ->toHaveProperty('afterAll') - ->and($globalHook->calls) + ->and($_SERVER['globalHook']->calls) ->afterAll ->toBe(0); }); diff --git a/tests/Hooks/BeforeAllTest.php b/tests/Hooks/BeforeAllTest.php index 66f5801b..4cea5faa 100644 --- a/tests/Hooks/BeforeAllTest.php +++ b/tests/Hooks/BeforeAllTest.php @@ -2,55 +2,53 @@ use Pest\Support\Str; -global $globalHook; - -// HACK: we have to determine our $globalHook->calls baseline. This is because +// HACK: we have to determine our $_SERVER['globalHook-]>calls baseline. This is because // two other tests are executed before this one due to filename ordering. $args = $_SERVER['argv'] ?? []; $single = (isset($args[1]) && Str::endsWith(__FILE__, $args[1])) || ($_SERVER['PEST_PARALLEL'] ?? false); $offset = $single ? 0 : 2; -uses()->beforeAll(function () use ($globalHook, $offset) { - expect($globalHook) +uses()->beforeAll(function () use ($offset) { + expect($_SERVER['globalHook']) ->toHaveProperty('beforeAll') - ->and($globalHook->beforeAll) + ->and($_SERVER['globalHook']->beforeAll) ->toBe(0) - ->and($globalHook->calls) + ->and($_SERVER['globalHook']->calls) ->beforeAll ->toBe(1 + $offset); - $globalHook->beforeAll = 1; - $globalHook->calls->beforeAll++; + $_SERVER['globalHook']->beforeAll = 1; + $_SERVER['globalHook']->calls->beforeAll++; }); -beforeAll(function () use ($globalHook, $offset) { - expect($globalHook) +beforeAll(function () use ($offset) { + expect($_SERVER['globalHook']) ->toHaveProperty('beforeAll') - ->and($globalHook->beforeAll) + ->and($_SERVER['globalHook']->beforeAll) ->toBe(1) - ->and($globalHook->calls) + ->and($_SERVER['globalHook']->calls) ->beforeAll ->toBe(2 + $offset); - $globalHook->beforeAll = 2; - $globalHook->calls->beforeAll++; + $_SERVER['globalHook']->beforeAll = 2; + $_SERVER['globalHook']->calls->beforeAll++; }); -test('global beforeAll execution order', function () use ($globalHook, $offset) { - expect($globalHook) +test('global beforeAll execution order', function () use ($offset) { + expect($_SERVER['globalHook']) ->toHaveProperty('beforeAll') - ->and($globalHook->beforeAll) + ->and($_SERVER['globalHook']->beforeAll) ->toBe(2) - ->and($globalHook->calls) + ->and($_SERVER['globalHook']->calls) ->beforeAll ->toBe(3 + $offset); }); -it('only gets called once per file', function () use ($globalHook, $offset) { - expect($globalHook) +it('only gets called once per file', function () use ($offset) { + expect($_SERVER['globalHook']) ->beforeAll ->toBe(2) - ->and($globalHook->calls) + ->and($_SERVER['globalHook']->calls) ->beforeAll ->toBe(3 + $offset); }); diff --git a/tests/Pest.php b/tests/Pest.php index 429bf74c..d0f656bd 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -7,21 +7,21 @@ uses(CustomTestCaseInSubFolder::class)->in('PHPUnit/CustomTestCaseInSubFolders/S uses()->group('integration')->in('Visual'); // NOTE: global test value container to be mutated and checked across files, as needed -$globalHook = (object) ['calls' => (object) ['beforeAll' => 0, 'afterAll' => 0]]; +$_SERVER['globalHook'] = (object) ['calls' => (object) ['beforeAll' => 0, 'afterAll' => 0]]; uses() ->beforeEach(function () { $this->baz = 0; }) - ->beforeAll(function () use ($globalHook) { - $globalHook->beforeAll = 0; - $globalHook->calls->beforeAll++; + ->beforeAll(function () { + $_SERVER['globalHook']->beforeAll = 0; + $_SERVER['globalHook']->calls->beforeAll++; }) ->afterEach(function () { $this->ith = 0; }) - ->afterAll(function () use ($globalHook) { - $globalHook->afterAll = 0; - $globalHook->calls->afterAll++; + ->afterAll(function () { + $_SERVER['globalHook']->afterAll = 0; + $_SERVER['globalHook']->calls->afterAll++; }) ->in('Hooks'); diff --git a/tests/Unit/Actions/AddsDefaults.php b/tests/Unit/Actions/AddsDefaults.php deleted file mode 100644 index eef7b027..00000000 --- a/tests/Unit/Actions/AddsDefaults.php +++ /dev/null @@ -1,20 +0,0 @@ - 'foo']); - - expect($arguments['printer'])->toBeInstanceOf(Printer::class); - expect($arguments['bar'])->toBe('foo'); -}); - -it('does not override options', function () { - $defaultResultPrinter = new DefaultResultPrinter(); - - expect(AddsDefaults::to(['printer' => $defaultResultPrinter]))->tobe([ - 'printer' => $defaultResultPrinter, - ]); -}); diff --git a/tests/Unit/Actions/AddsTests.php b/tests/Unit/Actions/AddsTests.php deleted file mode 100644 index 31f30505..00000000 --- a/tests/Unit/Actions/AddsTests.php +++ /dev/null @@ -1,32 +0,0 @@ -addTest($phpUnitTestCase); - expect($testSuite->tests())->toHaveCount(1); - - AddsTests::to($testSuite, new \Pest\TestSuite(getcwd(), 'tests')); - expect($testSuite->tests())->toHaveCount(1); -}); - -it('removes warnings', function () { - $testSuite = new TestSuite(); - $warningTestCase = new WarningTestCase('No tests found in class "Pest\TestCase".'); - $testSuite->addTest($warningTestCase); - - AddsTests::to($testSuite, new \Pest\TestSuite(getcwd(), 'tests')); - expect($testSuite->tests())->toHaveCount(0); -}); diff --git a/tests/Unit/Actions/ValidatesConfiguration.php b/tests/Unit/Actions/ValidatesConfiguration.php deleted file mode 100644 index a26a4105..00000000 --- a/tests/Unit/Actions/ValidatesConfiguration.php +++ /dev/null @@ -1,42 +0,0 @@ -expectException(FileOrFolderNotFound::class); - - ValidatesConfiguration::in([ - 'configuration' => 'foo', - ]); -}); - -it('throws exception when `process isolation` is true', function () { - $this->expectException(AttributeNotSupportedYet::class); - $this->expectExceptionMessage('The PHPUnit attribute `processIsolation` with value `true` is not supported yet.'); - - $filename = implode(DIRECTORY_SEPARATOR, [ - dirname(__DIR__, 2), - 'Fixtures', - 'phpunit-in-isolation.xml', - ]); - - ValidatesConfiguration::in([ - 'configuration' => $filename, - ]); -}); - -it('do not throws exception when `process isolation` is false', function () { - $filename = implode(DIRECTORY_SEPARATOR, [ - dirname(__DIR__, 2), - 'Fixtures', - 'phpunit-not-in-isolation.xml', - ]); - - ValidatesConfiguration::in([ - 'configuration' => $filename, - ]); - - expect(true)->toBeTrue(); -}); diff --git a/tests/Visual/junit.html b/tests/Visual/junit.html new file mode 100644 index 00000000..e69de29b From a35bf249a0a379299544d40df0f1c6cbbbe7b6cd Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 24 Oct 2021 01:06:48 +0100 Subject: [PATCH 05/17] chore: points towards 2.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 53999267..9798bbf2 100644 --- a/composer.json +++ b/composer.json @@ -78,7 +78,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" }, "pest": { "plugins": [ From 88dfabc633a358d665ecbe15d986fbdc987f0d71 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 24 Oct 2021 01:11:04 +0100 Subject: [PATCH 06/17] chore: points towards 2.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9798bbf2..a4ef0856 100644 --- a/composer.json +++ b/composer.json @@ -78,7 +78,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-next": "2.x-dev" }, "pest": { "plugins": [ From e8c2fe6e35d28bd290d862dced6ebecb45174dee Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 24 Oct 2021 01:16:05 +0100 Subject: [PATCH 07/17] fix: test class finder --- src/Support/Backtrace.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Support/Backtrace.php b/src/Support/Backtrace.php index b207d056..fa02e239 100644 --- a/src/Support/Backtrace.php +++ b/src/Support/Backtrace.php @@ -26,7 +26,8 @@ final class Backtrace $current = null; foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) { - if (Str::endsWith($trace[self::FILE], (string) realpath('overrides/Runner/TestSuiteLoader.php'))) { + + if (Str::endsWith($trace[self::FILE], 'overrides/Runner/TestSuiteLoader.php')) { break; } From 2b687a7269aea18b1559cd2bfc0961177b19e87e Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 24 Oct 2021 18:29:59 +0100 Subject: [PATCH 08/17] refactor: PHP 8 features --- src/Concerns/Extendable.php | 2 +- src/Concerns/Testable.php | 109 ++++++---------- src/Console/Help.php | 7 +- src/Console/Thanks.php | 7 +- src/Datasets.php | 9 +- src/Each.php | 14 +- src/Exceptions/DatasetMissing.php | 4 +- src/Expectation.php | 120 ++++++------------ src/Factories/TestCaseFactory.php | 73 +++-------- src/Functions.php | 6 +- src/HigherOrderExpectation.php | 36 +----- src/Logging/JUnit.php | 50 +++----- src/Logging/TeamCity.php | 23 ++-- src/OppositeExpectation.php | 21 +-- src/PendingObjects/AfterEachCall.php | 32 +---- src/PendingObjects/BeforeEachCall.php | 33 ++--- src/PendingObjects/TestCall.php | 38 ++---- src/PendingObjects/UsesCall.php | 36 ++---- src/Plugin.php | 2 +- src/Plugins/Coverage.php | 31 +++-- src/Plugins/Environment.php | 9 +- src/Plugins/Init.php | 19 +-- src/Plugins/Version.php | 12 +- src/Repositories/AfterAllRepository.php | 2 +- src/Repositories/AfterEachRepository.php | 2 +- src/Repositories/BeforeAllRepository.php | 2 +- src/Repositories/BeforeEachRepository.php | 2 +- src/Repositories/TestRepository.php | 20 +-- .../EnsureConfigurationDefaults.php | 2 +- src/Subscribers/EnsureTestsAreLoaded.php | 4 +- src/Support/Arr.php | 8 +- src/Support/Backtrace.php | 1 - src/Support/ChainableClosure.php | 4 +- src/Support/Container.php | 7 +- src/Support/Coverage.php | 2 +- src/Support/Extendable.php | 14 +- src/Support/HigherOrderCallables.php | 18 +-- src/Support/HigherOrderMessage.php | 66 ++-------- src/Support/HigherOrderMessageCollection.php | 6 +- src/Support/HigherOrderTapProxy.php | 14 +- src/Support/Reflection.php | 4 +- src/Support/Str.php | 2 +- src/TestSuite.php | 45 ++----- 43 files changed, 283 insertions(+), 635 deletions(-) diff --git a/src/Concerns/Extendable.php b/src/Concerns/Extendable.php index ee87f9ab..0d689452 100644 --- a/src/Concerns/Extendable.php +++ b/src/Concerns/Extendable.php @@ -15,7 +15,7 @@ trait Extendable /** * @var array */ - private static $extends = []; + private static array $extends = []; /** * Register a custom extend. diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 7244c4f5..d90229cd 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -13,61 +13,42 @@ use PHPUnit\Framework\ExecutionOrderDependency; use Throwable; /** - * To avoid inheritance conflicts, all the fields related to Pest only will be prefixed by double underscore. - * * @internal */ trait Testable { /** - * The test case description. Contains the first - * argument of global functions like `it` and `test`. - * - * @var string + * The Test Case description. */ - private $__description; + private string $__description; /** - * Holds the test closure function. - * - * @var Closure + * The Test Case "test" closure. */ - private $__test; + private Closure $__test; /** - * Holds a global/shared beforeEach ("set up") closure if one has been - * defined. - * - * @var Closure|null + * The Test Case "setUp" closure. */ - private $__beforeEach = null; + private ?Closure $__beforeEach = null; /** - * Holds a global/shared afterEach ("tear down") closure if one has been - * defined. - * - * @var Closure|null + * The Test Case "tearDown" closure. */ - private $__afterEach = null; + private ?Closure $__afterEach = null; /** - * Holds a global/shared beforeAll ("set up before") closure if one has been - * defined. - * - * @var Closure|null + * The Test Case "setUpBeforeClass" closure. */ - private static $__beforeAll = null; + private static ?Closure $__beforeAll = null; /** - * Holds a global/shared afterAll ("tear down after") closure if one has - * been defined. - * - * @var Closure|null + * The test "tearDownAfterClass" closure. */ - private static $__afterAll = null; + private static ?Closure $__afterAll = null; /** - * Creates a new instance of the test case. + * Creates a new Test Case instance. */ public function __construct(Closure $test, string $description, array $data) { @@ -82,7 +63,7 @@ trait Testable } /** - * Adds the groups to the current test case. + * Adds groups to the Test Case. */ public function addGroups(array $groups): void { @@ -92,14 +73,14 @@ trait Testable } /** - * Add dependencies to the test case and map them to instances of ExecutionOrderDependency. + * Adds dependencies to the Test Case. */ public function addDependencies(array $tests): void { - $className = get_class($this); + $className = $this::class; - $tests = array_map(function (string $test) use ($className): ExecutionOrderDependency { - if (strpos($test, '::') === false) { + $tests = array_map(static function (string $test) use ($className): ExecutionOrderDependency { + if (!str_contains($test, '::')) { $test = "{$className}::{$test}"; } @@ -110,8 +91,7 @@ trait Testable } /** - * Add a shared/"global" before all test hook that will execute **before** - * the test defined `beforeAll` hook(s). + * Adds a new "setUpBeforeClass" to the Test Case. */ public function __addBeforeAll(?Closure $hook): void { @@ -125,8 +105,7 @@ trait Testable } /** - * Add a shared/"global" after all test hook that will execute **before** - * the test defined `afterAll` hook(s). + * Adds a new "tearDownAfterClass" to the Test Case. */ public function __addAfterAll(?Closure $hook): void { @@ -140,8 +119,7 @@ trait Testable } /** - * Add a shared/"global" before each test hook that will execute **before** - * the test defined `beforeEach` hook. + * Adds a new "setUp" to the Test Case. */ public function __addBeforeEach(?Closure $hook): void { @@ -149,8 +127,7 @@ trait Testable } /** - * Add a shared/"global" after each test hook that will execute **before** - * the test defined `afterEach` hook. + * Adds a new "tearDown" to the Test Case. */ public function __addAfterEach(?Closure $hook): void { @@ -158,7 +135,7 @@ trait Testable } /** - * Add a shared/global hook and compose them if more than one is passed. + * Adds a new "hook" to the Test Case. */ private function __addHook(string $property, ?Closure $hook): void { @@ -172,9 +149,7 @@ trait Testable } /** - * Returns the test case name. Note that, in Pest - * we ignore withDataset argument as the description - * already contains the dataset description. + * Gets the Test Case name. */ public function getName(bool $withDataSet = true): string { @@ -183,13 +158,16 @@ trait Testable : $this->__description; } - public static function __getFileName(): string + /** + * Gets the Test Case filename. + */ + public static function __getFilename(): string { return self::$__filename; } /** - * This method is called before the first test of this test class is run. + * This method is called before the first test of this Test Case is run. */ public static function setUpBeforeClass(): void { @@ -205,7 +183,7 @@ trait Testable } /** - * This method is called after the last test of this test class is run. + * This method is called after the last test of this Test Case is run. */ public static function tearDownAfterClass(): void { @@ -221,7 +199,7 @@ trait Testable } /** - * Gets executed before the test. + * Gets executed before the Test Case. */ protected function setUp(): void { @@ -239,7 +217,7 @@ trait Testable } /** - * Gets executed after the test. + * Gets executed after the Test Case. */ protected function tearDown(): void { @@ -257,7 +235,7 @@ trait Testable } /** - * Returns the test case as string. + * Gets the Test Case filename and description. */ public function toString(): string { @@ -269,13 +247,11 @@ trait Testable } /** - * Runs the test. - * - * @return mixed + * Executes the Test Case current test. * * @throws Throwable */ - public function __test() + public function __test(): mixed { return $this->__callClosure($this->__test, $this->__resolveTestArguments(func_get_args())); } @@ -287,23 +263,20 @@ trait Testable */ private function __resolveTestArguments(array $arguments): array { - return array_map(function ($data) { - return $data instanceof Closure ? $this->__callClosure($data, []) : $data; - }, $arguments); + return array_map(fn ($data) => $data instanceof Closure ? $this->__callClosure($data, []) : $data, $arguments); } /** - * @return mixed - * * @throws Throwable */ - private function __callClosure(Closure $closure, array $arguments) + private function __callClosure(Closure $closure, array $arguments): mixed { - return ExceptionTrace::ensure(function () use ($closure, $arguments) { - return call_user_func_array(Closure::bind($closure, $this, get_class($this)), $arguments); - }); + return ExceptionTrace::ensure(fn () => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments)); } + /** + * Gets the Test Case name that should be used by printers. + */ public function getPrintableTestCaseName(): string { return ltrim(self::class, 'P\\'); diff --git a/src/Console/Help.php b/src/Console/Help.php index 4554b728..0bb43477 100644 --- a/src/Console/Help.php +++ b/src/Console/Help.php @@ -20,12 +20,9 @@ final class Help ' --group= Only runs tests from the specified group(s)', ]; - /** @var OutputInterface */ - private $output; - - public function __construct(OutputInterface $output) + public function __construct(private OutputInterface $output) { - $this->output = $output; + // .. } public function __invoke(): void diff --git a/src/Console/Thanks.php b/src/Console/Thanks.php index 4e3791dc..5ae807ec 100644 --- a/src/Console/Thanks.php +++ b/src/Console/Thanks.php @@ -25,12 +25,9 @@ final class Thanks ' https://github.com/sponsors/nunomaduro', ]; - /** @var OutputInterface */ - private $output; - - public function __construct(OutputInterface $output) + public function __construct(private OutputInterface $output) { - $this->output = $output; + // .. } /** diff --git a/src/Datasets.php b/src/Datasets.php index 89725107..0a245079 100644 --- a/src/Datasets.php +++ b/src/Datasets.php @@ -20,14 +20,14 @@ final class Datasets * * @var array> */ - private static $datasets = []; + private static array $datasets = []; /** * Sets the given. * * @param Closure|iterable $data */ - public static function set(string $name, $data): void + public static function set(string $name, Closure|iterable $data): void { if (array_key_exists($name, self::$datasets)) { throw new DatasetAlreadyExist($name); @@ -39,7 +39,7 @@ final class Datasets /** * @return Closure|iterable */ - public static function get(string $name) + public static function get(string $name): Closure|iterable { if (!array_key_exists($name, self::$datasets)) { throw new DatasetDoesNotExist($name); @@ -161,10 +161,9 @@ final class Datasets } /** - * @param int|string $key * @param array $data */ - private static function getDataSetDescription($key, array $data): string + private static function getDataSetDescription(int|string $key, array $data): string { $exporter = new Exporter(); diff --git a/src/Each.php b/src/Each.php index f02a8e1e..08c8f753 100644 --- a/src/Each.php +++ b/src/Each.php @@ -11,22 +11,14 @@ namespace Pest; */ final class Each { - /** - * @var Expectation - */ - private $original; - - /** - * @var bool - */ - private $opposite = false; + private bool $opposite = false; /** * Creates an expectation on each item of the iterable "value". */ - public function __construct(Expectation $original) + public function __construct(private Expectation $original) { - $this->original = $original; + // .. } /** diff --git a/src/Exceptions/DatasetMissing.php b/src/Exceptions/DatasetMissing.php index b8f0cb2d..023487e8 100644 --- a/src/Exceptions/DatasetMissing.php +++ b/src/Exceptions/DatasetMissing.php @@ -27,9 +27,7 @@ final class DatasetMissing extends BadFunctionCallException implements Exception "A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s", $name, count($args), - implode(', ', array_map(static function (string $arg, string $type): string { - return sprintf('%s $%s', $type, $arg); - }, array_keys($args), $args)), + implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($args), $args)), $file, )); } diff --git a/src/Expectation.php b/src/Expectation.php index a973c760..28ea26af 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -29,37 +29,26 @@ use Throwable; */ final class Expectation { - use Extendable { + use RetrievesValues, Extendable { __call as __extendsCall; } - use RetrievesValues; - - /** - * The expectation value. - * - * @readonly - * - * @var mixed - */ - public $value; /** * The exporter instance, if any. * * @readonly - * - * @var Exporter|null */ - private $exporter; + private ?Exporter $exporter = null; /** * Creates a new expectation. * * @param TValue $value */ - public function __construct($value) - { - $this->value = $value; + public function __construct( + public mixed $value + ) { + // .. } /** @@ -69,7 +58,7 @@ final class Expectation * * @return Expectation */ - public function and($value): Expectation + public function and(mixed $value): Expectation { return new self($value); } @@ -103,9 +92,9 @@ final class Expectation /** * Send the expectation value to Ray along with all given arguments. * - * @param mixed $arguments + * @param ...mixed $arguments */ - public function ray(...$arguments): self + public function ray(mixed ...$arguments): self { if (function_exists('ray')) { // @phpstan-ignore-next-line @@ -146,9 +135,9 @@ final class Expectation * * @template TSequenceValue * - * @param callable(self, self): void|TSequenceValue ...$callbacks + * @param (callable(self, self): void)|TSequenceValue ...$callbacks */ - public function sequence(...$callbacks): Expectation + public function sequence(mixed ...$callbacks): Expectation { if (!is_iterable($this->value)) { throw new BadMethodCallException('Expectation value is not iterable.'); @@ -187,16 +176,14 @@ final class Expectation * * @template TMatchSubject of array-key * - * @param callable(): TMatchSubject|TMatchSubject $subject + * @param (callable(): TMatchSubject)|TMatchSubject $subject * @param array): mixed)|TValue> $expressions */ - public function match($subject, array $expressions): Expectation + public function match(mixed $subject, array $expressions): Expectation { $subject = is_callable($subject) ? $subject - : function () use ($subject) { - return $subject; - }; + : fn () => $subject; $subject = $subject(); @@ -229,15 +216,15 @@ final class Expectation /** * Apply the callback if the given "condition" is falsy. * - * @param (callable(): bool)|bool $condition + * @param (callable(): bool)|bool $condition * @param callable(Expectation): mixed $callback */ - public function unless($condition, callable $callback): Expectation + public function unless(callable|bool $condition, callable $callback): Expectation { $condition = is_callable($condition) ? $condition : static function () use ($condition): bool { - return (bool) $condition; // @phpstan-ignore-line + return $condition; // @phpstan-ignore-line }; return $this->when(!$condition(), $callback); @@ -246,15 +233,15 @@ final class Expectation /** * Apply the callback if the given "condition" is truthy. * - * @param (callable(): bool)|bool $condition + * @param (callable(): bool)|bool $condition * @param callable(Expectation): mixed $callback */ - public function when($condition, callable $callback): Expectation + public function when(callable|bool $condition, callable $callback): Expectation { $condition = is_callable($condition) ? $condition : static function () use ($condition): bool { - return (bool) $condition; // @phpstan-ignore-line + return $condition; // @phpstan-ignore-line }; if ($condition()) { @@ -268,10 +255,8 @@ final class Expectation * Asserts that two variables have the same type and * value. Used on objects, it asserts that two * variables reference the same object. - * - * @param mixed $expected */ - public function toBe($expected): Expectation + public function toBe(mixed $expected): Expectation { Assert::assertSame($expected, $this->value); @@ -330,10 +315,8 @@ final class Expectation /** * Asserts that the value is greater than $expected. - * - * @param int|float $expected */ - public function toBeGreaterThan($expected): Expectation + public function toBeGreaterThan(int|float $expected): Expectation { Assert::assertGreaterThan($expected, $this->value); @@ -342,10 +325,8 @@ final class Expectation /** * Asserts that the value is greater than or equal to $expected. - * - * @param int|float $expected */ - public function toBeGreaterThanOrEqual($expected): Expectation + public function toBeGreaterThanOrEqual(int|float $expected): Expectation { Assert::assertGreaterThanOrEqual($expected, $this->value); @@ -354,10 +335,8 @@ final class Expectation /** * Asserts that the value is less than or equal to $expected. - * - * @param int|float $expected */ - public function toBeLessThan($expected): Expectation + public function toBeLessThan(int|float $expected): Expectation { Assert::assertLessThan($expected, $this->value); @@ -366,10 +345,8 @@ final class Expectation /** * Asserts that the value is less than $expected. - * - * @param int|float $expected */ - public function toBeLessThanOrEqual($expected): Expectation + public function toBeLessThanOrEqual(int|float $expected): Expectation { Assert::assertLessThanOrEqual($expected, $this->value); @@ -378,10 +355,8 @@ final class Expectation /** * Asserts that $needle is an element of the value. - * - * @param mixed $needles */ - public function toContain(...$needles): Expectation + public function toContain(mixed ...$needles): Expectation { foreach ($needles as $needle) { if (is_string($this->value)) { @@ -456,10 +431,8 @@ final class Expectation /** * Asserts that the value contains the property $name. - * - * @param mixed $value */ - public function toHaveProperty(string $name, $value = null): Expectation + public function toHaveProperty(string $name, mixed $value = null): Expectation { $this->toBeObject(); @@ -489,10 +462,8 @@ final class Expectation /** * Asserts that two variables have the same value. - * - * @param mixed $expected */ - public function toEqual($expected): Expectation + public function toEqual(mixed $expected): Expectation { Assert::assertEquals($expected, $this->value); @@ -507,10 +478,8 @@ final class Expectation * are sorted before they are compared. When $expected and $this->value * are objects, each object is converted to an array containing all * private, protected and public attributes. - * - * @param mixed $expected */ - public function toEqualCanonicalizing($expected): Expectation + public function toEqualCanonicalizing(mixed $expected): Expectation { Assert::assertEqualsCanonicalizing($expected, $this->value); @@ -520,10 +489,8 @@ final class Expectation /** * Asserts that the absolute difference between the value and $expected * is lower than $delta. - * - * @param mixed $expected */ - public function toEqualWithDelta($expected, float $delta): Expectation + public function toEqualWithDelta(mixed $expected, float $delta): Expectation { Assert::assertEqualsWithDelta($expected, $this->value, $delta); @@ -555,9 +522,9 @@ final class Expectation /** * Asserts that the value is an instance of $class. * - * @param string $class + * @param class-string $class */ - public function toBeInstanceOf($class): Expectation + public function toBeInstanceOf(string $class): Expectation { /* @phpstan-ignore-next-line */ Assert::assertInstanceOf($class, $this->value); @@ -708,11 +675,8 @@ final class Expectation /** * Asserts that the value array has the provided $key. - * - * @param string|int $key - * @param mixed $value */ - public function toHaveKey($key, $value = null): Expectation + public function toHaveKey(string|int $key, mixed $value = null): Expectation { if (is_object($this->value) && method_exists($this->value, 'toArray')) { $array = $this->value->toArray(); @@ -812,9 +776,9 @@ final class Expectation /** * Asserts that the value array matches the given array subset. * - * @param array $array + * @param iterable $array */ - public function toMatchArray($array): Expectation + public function toMatchArray(iterable|object $array): Expectation { if (is_object($this->value) && method_exists($this->value, 'toArray')) { $valueAsArray = $this->value->toArray(); @@ -843,9 +807,9 @@ final class Expectation * Asserts that the value object matches a subset * of the properties of an given object. * - * @param array|object $object + * @param iterable|object $object */ - public function toMatchObject($object): Expectation + public function toMatchObject(iterable|object $object): Expectation { foreach ((array) $object as $property => $value) { Assert::assertTrue(property_exists($this->value, $property)); @@ -891,7 +855,7 @@ final class Expectation * * @param (Closure(Throwable): mixed)|string $exception */ - public function toThrow($exception, string $exceptionMessage = null): Expectation + public function toThrow(callable|string $exception, string $exceptionMessage = null): Expectation { $callback = NullClosure::create(); @@ -938,10 +902,8 @@ final class Expectation /** * Exports the given value. - * - * @param mixed $value */ - private function export($value): string + private function export(mixed $value): string { if ($this->exporter === null) { $this->exporter = new Exporter(); @@ -971,10 +933,8 @@ final class Expectation /** * Dynamically calls methods on the class without any arguments * or creates a new higher order expectation. - * - * @return Expectation|HigherOrderExpectation */ - public function __get(string $name) + public function __get(string $name): Expectation|OppositeExpectation|Each|HigherOrderExpectation { if (!method_exists($this, $name) && !static::hasExtend($name)) { return new HigherOrderExpectation($this, $this->retrieve($name, $this->value)); diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index fc2fade2..e9c288c1 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -22,97 +22,68 @@ use RuntimeException; */ final class TestCaseFactory { - /** - * Holds the test filename. - * - * @readonly - * - * @var string - */ - public $filename; - /** * Marks this test case as only. * * @readonly - * - * @var bool */ - public $only = false; - - /** - * Holds the test description. - * - * If the description is null, means that it - * will be created with the given assertions. - * - * @var string|null - */ - public $description; + public bool $only = false; /** * Holds the test closure. * * @readonly - * - * @var Closure */ - public $test; + public Closure $test; /** * Holds the dataset, if any. * * @var array|string> */ - public $datasets = []; + public array $datasets = []; /** * The FQN of the test case class. * - * @var string + * @var class-string */ - public $class = TestCase::class; + public string $class = TestCase::class; /** * An array of FQN of the class traits. * * @var array */ - public $traits = [ + public array $traits = [ Concerns\Testable::class, Concerns\Expectable::class, ]; /** - * Holds the higher order messages - * for the factory that are proxyble. - * - * @var HigherOrderMessageCollection + * Holds the higher order messages for the factory that are proxyble. */ - public $factoryProxies; + public HigherOrderMessageCollection $factoryProxies; /** * Holds the higher order messages that are proxyble. - * - * @var HigherOrderMessageCollection */ - public $proxies; + public HigherOrderMessageCollection $proxies; /** * Holds the higher order messages that are chainable. - * - * @var HigherOrderMessageCollection */ - public $chains; + public HigherOrderMessageCollection $chains; /** * Creates a new anonymous test case pending object. */ - public function __construct(string $filename, string $description = null, Closure $closure = null) + public function __construct( + public string $filename, + public ?string $description = null, + Closure $closure = null) { - $this->filename = $filename; - $this->description = $description; - $this->test = $closure ?? function (): void { + $this->test = $closure ?? function (): void { if (Assert::getCount() === 0) { self::markTestIncomplete(); // @phpstan-ignore-line } @@ -146,7 +117,7 @@ final class TestCaseFactory $chains->chain($this); /* @phpstan-ignore-next-line */ - return call_user_func(Closure::bind($factoryTest, $this, get_class($this)), ...func_get_args()); + return call_user_func(Closure::bind($factoryTest, $this, $this::class), ...func_get_args()); }; $className = $this->makeClassFromFilename($this->filename); @@ -170,9 +141,7 @@ final class TestCaseFactory { if ('\\' === DIRECTORY_SEPARATOR) { // In case Windows, strtolower drive name, like in UsesCall. - $filename = (string) preg_replace_callback('~^(?P[a-z]+:\\\)~i', function ($match): string { - return strtolower($match['drive']); - }, $filename); + $filename = (string) preg_replace_callback('~^(?P[a-z]+:\\\)~i', fn ($match): string => strtolower($match['drive']), $filename); } $filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename))); @@ -184,9 +153,7 @@ final class TestCaseFactory // Strip out any %-encoded octets. $relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath); // Remove escaped quote sequences (maintain namespace) - $relativePath = str_replace(array_map(function (string $quote): string { - return sprintf('\\%s', $quote); - }, ['\'', '"']), '', $relativePath); + $relativePath = str_replace(array_map(fn (string $quote): string => sprintf('\\%s', $quote), ['\'', '"']), '', $relativePath); // Limit to A-Z, a-z, 0-9, '_', '-'. $relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath); @@ -196,9 +163,7 @@ final class TestCaseFactory } $hasPrintableTestCaseClassFQN = sprintf('\%s', HasPrintableTestCaseName::class); - $traitsCode = sprintf('use %s;', implode(', ', array_map(function ($trait): string { - return sprintf('\%s', $trait); - }, $this->traits))); + $traitsCode = sprintf('use %s;', implode(', ', array_map(fn ($trait): string => sprintf('\%s', $trait), $this->traits))); $partsFQN = explode('\\', $classFQN); $className = array_pop($partsFQN); diff --git a/src/Functions.php b/src/Functions.php index e7a230dc..855e415e 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -18,10 +18,8 @@ use PHPUnit\Framework\TestCase; * Creates a new expectation. * * @param mixed $value the Value - * - * @return Expectation|Extendable */ -function expect($value = null) +function expect($value = null): Expectation|Extendable { if (func_num_args() === 0) { return new Extendable(Expectation::class); @@ -60,7 +58,7 @@ if (!function_exists('dataset')) { * * @param Closure|iterable $dataset */ - function dataset(string $name, $dataset): void + function dataset(string $name, Closure|iterable $dataset): void { Datasets::set($name, $dataset); } diff --git a/src/HigherOrderExpectation.php b/src/HigherOrderExpectation.php index e436da0c..839f3311 100644 --- a/src/HigherOrderExpectation.php +++ b/src/HigherOrderExpectation.php @@ -17,39 +17,17 @@ final class HigherOrderExpectation use Expectable; use RetrievesValues; - /** - * @var Expectation - */ - private $original; + private Expectation|Each $expectation; - /** - * @var Expectation|Each - */ - private $expectation; + private bool $opposite = false; - /** - * @var bool - */ - private $opposite = false; - - /** - * @var bool - */ - private $shouldReset = false; - - /** - * @var string - */ - private $name; + private bool $shouldReset = false; /** * Creates a new higher order expectation. - * - * @param mixed $value */ - public function __construct(Expectation $original, $value) + public function __construct(private Expectation $original, mixed $value) { - $this->original = $original; $this->expectation = $this->expect($value); } @@ -72,7 +50,7 @@ final class HigherOrderExpectation * * @return Expectation */ - public function and($value): Expectation + public function and(mixed $value): Expectation { return $this->expect($value); } @@ -118,10 +96,8 @@ final class HigherOrderExpectation /** * Retrieve the applicable value based on the current reset condition. - * - * @return mixed */ - private function getValue() + private function getValue(): mixed { return $this->shouldReset ? $this->original->value : $this->expectation->value; } diff --git a/src/Logging/JUnit.php b/src/Logging/JUnit.php index da15a9b9..14595bbc 100644 --- a/src/Logging/JUnit.php +++ b/src/Logging/JUnit.php @@ -16,7 +16,6 @@ use function class_exists; use DOMDocument; use DOMElement; use Exception; -use function get_class; use function method_exists; use Pest\Concerns\Testable; use PHPUnit\Framework\AssertionFailedError; @@ -42,65 +41,50 @@ use function trim; */ final class JUnit extends Printer implements TestListener { - /** - * @var DOMDocument - */ - private $document; + private DOMDocument $document; + + private DOMElement $root; /** - * @var DOMElement + * @var array */ - private $root; - - /** - * @var DOMElement[] - */ - private $testSuites = []; + private array $testSuites = []; /** * @var int[] */ - private $testSuiteTests = [0]; + private array $testSuiteTests = [0]; /** * @var int[] */ - private $testSuiteAssertions = [0]; + private array $testSuiteAssertions = [0]; /** * @var int[] */ - private $testSuiteErrors = [0]; + private array $testSuiteErrors = [0]; /** * @var int[] */ - private $testSuiteWarnings = [0]; + private array $testSuiteWarnings = [0]; /** * @var int[] */ - private $testSuiteFailures = [0]; + private array $testSuiteFailures = [0]; /** * @var int[] */ - private $testSuiteSkipped = [0]; + private array $testSuiteSkipped = [0]; - /** - * @var int[]|float[] - */ - private $testSuiteTimes = [0]; + private array $testSuiteTimes = [0]; - /** - * @var int - */ - private $testSuiteLevel = 0; + private int $testSuiteLevel = 0; - /** - * @var DOMElement|null - */ - private $currentTestCase; + private ?DOMElement $currentTestCase = null; public function __construct(string $out) { @@ -190,7 +174,7 @@ final class JUnit extends Printer implements TestListener } $testSuite->setAttribute('file', $fileName); - } catch (ReflectionException $e) { + } catch (ReflectionException) { // @ignoreException } } @@ -313,7 +297,7 @@ final class JUnit extends Printer implements TestListener $testCase->setAttribute('class', $test->getPrintableTestCaseName()); $testCase->setAttribute('classname', str_replace('\\', '.', $test->getPrintableTestCaseName())); // @phpstan-ignore-next-line - $testCase->setAttribute('file', $test->__getFileName()); + $testCase->setAttribute('file', $test->__getFilename()); } $this->currentTestCase = $testCase; @@ -409,7 +393,7 @@ final class JUnit extends Printer implements TestListener if ($t instanceof ExceptionWrapper) { $fault->setAttribute('type', $t->getClassName()); } else { - $fault->setAttribute('type', get_class($t)); + $fault->setAttribute('type', $t::class); } $this->currentTestCase->appendChild($fault); diff --git a/src/Logging/TeamCity.php b/src/Logging/TeamCity.php index bfd732fb..051bfa4c 100644 --- a/src/Logging/TeamCity.php +++ b/src/Logging/TeamCity.php @@ -16,9 +16,9 @@ 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 function strlen; use Throwable; final class TeamCity extends DefaultResultPrinter @@ -34,22 +34,19 @@ final class TeamCity extends DefaultResultPrinter private const TEST_STARTED = 'testStarted'; private const TEST_FINISHED = 'testFinished'; - /** @var int */ - private $flowId; + private ?int $flowId = null; - /** @var bool */ - private $isSummaryTestCountPrinted = false; + private bool $isSummaryTestCountPrinted = false; - /** @var \PHPUnit\Util\Log\TeamCity */ - private $phpunitTeamCity; + private BaseTeamCity $phpunitTeamCity; /** - * @param resource|string|null $out + * Creates a new printer instance. */ - public function __construct($out, bool $verbose, string $colors) + public function __construct(resource|string|null $out, bool $verbose, string $colors) { parent::__construct($out, $verbose, $colors); - $this->phpunitTeamCity = new \PHPUnit\Util\Log\TeamCity($out, $verbose, $colors); + $this->phpunitTeamCity = new BaseTeamCity($out, $verbose, $colors); $this->logo(); } @@ -74,9 +71,7 @@ final class TeamCity extends DefaultResultPrinter 'passed' => ['count' => $this->successfulTestCount($result), 'color' => 'fg-green'], ]; - $filteredResults = array_filter($results, function ($item): bool { - return $item['count'] > 0; - }); + $filteredResults = array_filter($results, fn ($item): bool => $item['count'] > 0); foreach ($filteredResults as $key => $info) { $this->writeWithColor($info['color'], $info['count'] . " $key", false); @@ -203,7 +198,7 @@ final class TeamCity extends DefaultResultPrinter */ private static function isPestTestSuite(TestSuite $suite): bool { - return strncmp($suite->getName(), 'P\\', strlen('P\\')) === 0; + return str_starts_with($suite->getName(), 'P\\'); } /** diff --git a/src/OppositeExpectation.php b/src/OppositeExpectation.php index 0473e2b6..7a17162c 100644 --- a/src/OppositeExpectation.php +++ b/src/OppositeExpectation.php @@ -14,17 +14,12 @@ use SebastianBergmann\Exporter\Exporter; */ final class OppositeExpectation { - /** - * @var Expectation - */ - private $original; - /** * Creates a new opposite expectation. */ - public function __construct(Expectation $original) + public function __construct(private Expectation $original) { - $this->original = $original; + // .. } /** @@ -37,7 +32,7 @@ final class OppositeExpectation foreach ($keys as $key) { try { $this->original->toHaveKey($key); - } catch (ExpectationFailedException $e) { + } catch (ExpectationFailedException) { continue; } @@ -57,7 +52,7 @@ final class OppositeExpectation try { /* @phpstan-ignore-next-line */ $this->original->{$name}(...$arguments); - } catch (ExpectationFailedException $e) { + } catch (ExpectationFailedException) { return $this->original; } @@ -73,7 +68,7 @@ final class OppositeExpectation try { /* @phpstan-ignore-next-line */ $this->original->{$name}; - } catch (ExpectationFailedException $e) { + } catch (ExpectationFailedException) { return $this->original; } @@ -90,10 +85,8 @@ final class OppositeExpectation { $exporter = new Exporter(); - $toString = function ($argument) use ($exporter): string { - return $exporter->shortenedExport($argument); - }; + $toString = fn ($argument): string => $exporter->shortenedExport($argument); - throw new ExpectationFailedException(sprintf('Expecting %s not %s %s.', $toString($this->original->value), strtolower((string) preg_replace('/(?original->value), strtolower((string) preg_replace('/(? $toString($argument), $arguments)))); } } diff --git a/src/PendingObjects/AfterEachCall.php b/src/PendingObjects/AfterEachCall.php index 0f021f82..6702dcb5 100644 --- a/src/PendingObjects/AfterEachCall.php +++ b/src/PendingObjects/AfterEachCall.php @@ -16,42 +16,24 @@ use Pest\TestSuite; */ final class AfterEachCall { - /** - * Holds the test suite. - * - * @var TestSuite - */ - private $testSuite; - - /** - * Holds the filename. - * - * @var string - */ - private $filename; - /** * Holds the before each closure. - * - * @var Closure */ - private $closure; + private Closure $closure; /** * Holds calls that should be proxied. - * - * @var HigherOrderMessageCollection */ - private $proxies; + private HigherOrderMessageCollection $proxies; /** * Creates a new instance of before each call. */ - public function __construct(TestSuite $testSuite, string $filename, Closure $closure = null) - { - $this->testSuite = $testSuite; - $this->filename = $filename; - $this->closure = $closure instanceof Closure ? $closure : NullClosure::create(); + public function __construct( + private TestSuite $testSuite, + private string $filename, Closure $closure = null + ) { + $this->closure = $closure instanceof Closure ? $closure : NullClosure::create(); $this->proxies = new HigherOrderMessageCollection(); } diff --git a/src/PendingObjects/BeforeEachCall.php b/src/PendingObjects/BeforeEachCall.php index b5df9cba..ff637d52 100644 --- a/src/PendingObjects/BeforeEachCall.php +++ b/src/PendingObjects/BeforeEachCall.php @@ -16,42 +16,25 @@ use Pest\TestSuite; */ final class BeforeEachCall { - /** - * Holds the test suite. - * - * @var TestSuite - */ - private $testSuite; - - /** - * Holds the filename. - * - * @var string - */ - private $filename; - /** * Holds the before each closure. - * - * @var Closure */ - private $closure; + private \Closure $closure; /** * Holds calls that should be proxied. - * - * @var HigherOrderMessageCollection */ - private $proxies; + private HigherOrderMessageCollection $proxies; /** * Creates a new instance of before each call. */ - public function __construct(TestSuite $testSuite, string $filename, Closure $closure = null) - { - $this->testSuite = $testSuite; - $this->filename = $filename; - $this->closure = $closure instanceof Closure ? $closure : NullClosure::create(); + public function __construct( + private TestSuite $testSuite, + private string $filename, + Closure $closure = null + ) { + $this->closure = $closure instanceof Closure ? $closure : NullClosure::create(); $this->proxies = new HigherOrderMessageCollection(); } diff --git a/src/PendingObjects/TestCall.php b/src/PendingObjects/TestCall.php index 88928876..235c1750 100644 --- a/src/PendingObjects/TestCall.php +++ b/src/PendingObjects/TestCall.php @@ -19,40 +19,30 @@ use SebastianBergmann\Exporter\Exporter; */ final class TestCall { - /** - * Holds the test suite. - * - * @readonly - * - * @var TestSuite - */ - private $testSuite; - /** * Holds the test case factory. * * @readonly - * - * @var TestCaseFactory */ - private $testCaseFactory; + private TestCaseFactory $testCaseFactory; /** * If test call is descriptionLess. * * @readonly - * - * @var bool */ - private $descriptionLess = false; + private bool $descriptionLess = false; /** * Creates a new instance of a pending test call. */ - public function __construct(TestSuite $testSuite, string $filename, string $description = null, Closure $closure = null) - { + public function __construct( + private TestSuite $testSuite, + string $filename, + string $description = null, + Closure $closure = null + ) { $this->testCaseFactory = new TestCaseFactory($filename, $description, $closure); - $this->testSuite = $testSuite; $this->descriptionLess = $description === null; } @@ -83,12 +73,12 @@ final class TestCall * * @param (callable(): bool)|bool $condition */ - public function throwsIf($condition, string $exception, string $exceptionMessage = null): TestCall + public function throwsIf(callable|bool $condition, string $exception, string $exceptionMessage = null): TestCall { $condition = is_callable($condition) ? $condition : static function () use ($condition): bool { - return (bool) $condition; // @phpstan-ignore-line + return $condition; // @phpstan-ignore-line }; if ($condition()) { @@ -149,10 +139,8 @@ final class TestCall /** * Skips the current test. - * - * @param Closure|bool|string $conditionOrMessage */ - public function skip($conditionOrMessage = true, string $message = ''): TestCall + public function skip(Closure|bool|string $conditionOrMessage = true, string $message = ''): TestCall { $condition = is_string($conditionOrMessage) ? NullClosure::create() @@ -160,9 +148,7 @@ final class TestCall $condition = is_callable($condition) ? $condition - : function () use ($condition) { /* @phpstan-ignore-line */ - return $condition; - }; + : fn () => $condition; $message = is_string($conditionOrMessage) ? $conditionOrMessage diff --git a/src/PendingObjects/UsesCall.php b/src/PendingObjects/UsesCall.php index 4b1fd77e..8a954dbf 100644 --- a/src/PendingObjects/UsesCall.php +++ b/src/PendingObjects/UsesCall.php @@ -24,46 +24,32 @@ final class UsesCall * * @var array */ - private $hooks = []; - - /** - * Holds the class and traits. - * - * @var array - */ - private $classAndTraits; - - /** - * Holds the base dirname here the uses call was performed. - * - * @var string - */ - private $filename; + private array $hooks = []; /** * Holds the targets of the uses. * * @var array */ - private $targets; + private array $targets; /** * Holds the groups of the uses. * * @var array */ - private $groups = []; + private array $groups = []; /** * Creates a new instance of a pending test uses. * * @param array $classAndTraits */ - public function __construct(string $filename, array $classAndTraits) - { - $this->classAndTraits = $classAndTraits; - $this->filename = $filename; - $this->targets = [$filename]; + public function __construct( + private string $filename, + private array $classAndTraits + ) { + $this->targets = [$filename]; } /** @@ -76,14 +62,12 @@ final class UsesCall $startChar = DIRECTORY_SEPARATOR; if ('\\' === DIRECTORY_SEPARATOR || preg_match('~\A[A-Z]:(?![^/\\\\])~i', $path) > 0) { - $path = (string) preg_replace_callback('~^(?P[a-z]+:\\\)~i', function ($match): string { - return strtolower($match['drive']); - }, $path); + $path = (string) preg_replace_callback('~^(?P[a-z]+:\\\)~i', fn ($match): string => strtolower($match['drive']), $path); $startChar = strtolower((string) preg_replace('~^([a-z]+:\\\).*$~i', '$1', __DIR__)); } - return 0 === strpos($path, $startChar) + return str_starts_with($path, $startChar) ? $path : implode(DIRECTORY_SEPARATOR, [ dirname($this->filename), diff --git a/src/Plugin.php b/src/Plugin.php index 3b9256b4..9de1f707 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -14,7 +14,7 @@ final class Plugin * * @internal */ - public static $callables = []; + public static array $callables = []; /** * Lazy loads an `uses` call on the context of plugins. diff --git a/src/Plugins/Coverage.php b/src/Plugins/Coverage.php index 6d993717..667c0571 100644 --- a/src/Plugins/Coverage.php +++ b/src/Plugins/Coverage.php @@ -29,31 +29,30 @@ final class Coverage implements AddsOutput, HandlesArguments /** * Whether should show the coverage or not. - * - * @var bool */ - public $coverage = false; + public bool $coverage = false; /** * The minimum coverage. - * - * @var float */ - public $coverageMin = 0.0; + public float $coverageMin = 0.0; /** - * @var OutputInterface + * Creates a new Plugin instance. */ - private $output; - - public function __construct(OutputInterface $output) + public function __construct(private OutputInterface $output) { - $this->output = $output; + // .. } + /** + * @param array $originals + * + * @return array + */ public function handleArguments(array $originals): array { - $arguments = array_merge([''], array_values(array_filter($originals, function ($original): bool { + $arguments = [...[''], ...array_values(array_filter($originals, function ($original): bool { foreach ([self::COVERAGE_OPTION, self::MIN_OPTION] as $option) { if ($original === sprintf('--%s', $option) || Str::startsWith($original, sprintf('--%s=', $option))) { return true; @@ -61,7 +60,7 @@ final class Coverage implements AddsOutput, HandlesArguments } return false; - }))); + }))]; $originals = array_flip($originals); foreach ($arguments as $argument) { @@ -75,9 +74,9 @@ final class Coverage implements AddsOutput, HandlesArguments $input = new ArgvInput($arguments, new InputDefinition($inputs)); if ((bool) $input->getOption(self::COVERAGE_OPTION)) { - $this->coverage = true; - $originals[] = '--coverage-php'; - $originals[] = \Pest\Support\Coverage::getPath(); + $this->coverage = true; + $originals[] = '--coverage-php'; + $originals[] = \Pest\Support\Coverage::getPath(); } if ($input->getOption(self::MIN_OPTION) !== null) { diff --git a/src/Plugins/Environment.php b/src/Plugins/Environment.php index 4bb80a0e..52ad75ae 100644 --- a/src/Plugins/Environment.php +++ b/src/Plugins/Environment.php @@ -21,17 +21,10 @@ final class Environment implements HandlesArguments */ public const LOCAL = 'local'; - /** - * @var \Pest\Plugins\Environment|null - */ - private static $instance; - /** * The current environment. - * - * @var string|null */ - private static $name; + private static ?string $name = null; /** * Allows to handle custom command line arguments. diff --git a/src/Plugins/Init.php b/src/Plugins/Init.php index 06d3cd41..abe9f966 100644 --- a/src/Plugins/Init.php +++ b/src/Plugins/Init.php @@ -28,23 +28,14 @@ final class Init implements HandlesArguments 'ExampleTest.php' => 'tests/ExampleTest.php', ]; - /** - * @var OutputInterface - */ - private $output; - - /** - * @var TestSuite - */ - private $testSuite; - /** * Creates a new Plugin instance. */ - public function __construct(TestSuite $testSuite, OutputInterface $output) - { - $this->testSuite = $testSuite; - $this->output = $output; + public function __construct( + private TestSuite $testSuite, + private OutputInterface $output + ) { + // .. } public function handleArguments(array $arguments): array diff --git a/src/Plugins/Version.php b/src/Plugins/Version.php index 000bb3a3..f86a6cd1 100644 --- a/src/Plugins/Version.php +++ b/src/Plugins/Version.php @@ -13,17 +13,13 @@ use Symfony\Component\Console\Output\OutputInterface; */ final class Version implements HandlesArguments { - /** - * @var OutputInterface - */ - private $output; - /** * Creates a new instance of the plugin. */ - public function __construct(OutputInterface $output) - { - $this->output = $output; + public function __construct( + private OutputInterface $output + ) { + // .. } public function handleArguments(array $arguments): array diff --git a/src/Repositories/AfterAllRepository.php b/src/Repositories/AfterAllRepository.php index e0666da1..d2896ed6 100644 --- a/src/Repositories/AfterAllRepository.php +++ b/src/Repositories/AfterAllRepository.php @@ -17,7 +17,7 @@ final class AfterAllRepository /** * @var array */ - private $state = []; + private array $state = []; /** * Runs the given closure for each after all. diff --git a/src/Repositories/AfterEachRepository.php b/src/Repositories/AfterEachRepository.php index b0357e86..6c8f5afd 100644 --- a/src/Repositories/AfterEachRepository.php +++ b/src/Repositories/AfterEachRepository.php @@ -18,7 +18,7 @@ final class AfterEachRepository /** * @var array */ - private $state = []; + private array $state = []; /** * Sets a after each closure. diff --git a/src/Repositories/BeforeAllRepository.php b/src/Repositories/BeforeAllRepository.php index fa5b0b80..26e18c5f 100644 --- a/src/Repositories/BeforeAllRepository.php +++ b/src/Repositories/BeforeAllRepository.php @@ -17,7 +17,7 @@ final class BeforeAllRepository /** * @var array */ - private $state = []; + private array $state = []; /** * Runs one before all closure, and unsets it from the repository. diff --git a/src/Repositories/BeforeEachRepository.php b/src/Repositories/BeforeEachRepository.php index f9bbb1ad..0e800a79 100644 --- a/src/Repositories/BeforeEachRepository.php +++ b/src/Repositories/BeforeEachRepository.php @@ -16,7 +16,7 @@ final class BeforeEachRepository /** * @var array */ - private $state = []; + private array $state = []; /** * Sets a before each closure. diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index 486078d0..9eafa001 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -30,12 +30,12 @@ final class TestRepository /** * @var array */ - private $state = []; + private array $state = []; /** * @var array>> */ - private $uses = []; + private array $uses = []; /** * Counts the number of test cases. @@ -54,9 +54,7 @@ final class TestRepository { $testsWithOnly = $this->testsUsingOnly(); - return array_values(array_map(function (TestCaseFactory $factory): string { - return $factory->filename; - }, count($testsWithOnly) > 0 ? $testsWithOnly : $this->state)); + return array_values(array_map(fn (TestCaseFactory $factory): string => $factory->filename, count($testsWithOnly) > 0 ? $testsWithOnly : $this->state)); } /** @@ -64,9 +62,7 @@ final class TestRepository */ public function build(TestSuite $testSuite, callable $each): void { - $startsWith = function (string $target, string $directory): bool { - return Str::startsWith($target, $directory . DIRECTORY_SEPARATOR); - }; + $startsWith = fn (string $target, string $directory): bool => Str::startsWith($target, $directory . DIRECTORY_SEPARATOR); foreach ($this->uses as $path => $uses) { [$classOrTraits, $groups, $hooks] = $uses; @@ -123,9 +119,7 @@ final class TestRepository return []; } - return array_filter($this->state, function ($testFactory): bool { - return $testFactory->only; - }); + return array_filter($this->state, fn ($testFactory): bool => $testFactory->only); } /** @@ -147,8 +141,8 @@ final class TestRepository foreach ($paths as $path) { if (array_key_exists($path, $this->uses)) { $this->uses[$path] = [ - array_merge($this->uses[$path][0], $classOrTraits), - array_merge($this->uses[$path][1], $groups), + [...$this->uses[$path][0], ...$classOrTraits], + [...$this->uses[$path][1], ...$groups], $this->uses[$path][2] + $hooks, // NOTE: array_merge will destroy numeric indices ]; } else { diff --git a/src/Subscribers/EnsureConfigurationDefaults.php b/src/Subscribers/EnsureConfigurationDefaults.php index 12b1948e..ce94089d 100644 --- a/src/Subscribers/EnsureConfigurationDefaults.php +++ b/src/Subscribers/EnsureConfigurationDefaults.php @@ -17,6 +17,6 @@ final class EnsureConfigurationDefaults implements ConfiguredSubscriber */ public function notify(Configured $event): void { - $configuration = $event->configuration(); + // TODO... } } diff --git a/src/Subscribers/EnsureTestsAreLoaded.php b/src/Subscribers/EnsureTestsAreLoaded.php index 16ef72c2..12396941 100644 --- a/src/Subscribers/EnsureTestsAreLoaded.php +++ b/src/Subscribers/EnsureTestsAreLoaded.php @@ -18,7 +18,7 @@ final class EnsureTestsAreLoaded implements LoadedSubscriber /** * The current test suite, if any. */ - private static ?TestSuite $testSuite; + private static ?TestSuite $testSuite = null; /** * Runs the subscriber. @@ -31,7 +31,7 @@ final class EnsureTestsAreLoaded implements LoadedSubscriber $testSuite = \Pest\TestSuite::getInstance(); $testSuite->tests->build($testSuite, function (TestCase $testCase) use (&$testSuites): void { - $testCaseClass = get_class($testCase); + $testCaseClass = $testCase::class; if (!array_key_exists($testCaseClass, $testSuites)) { $testSuites[$testCaseClass] = []; } diff --git a/src/Support/Arr.php b/src/Support/Arr.php index ef20f862..a55764d5 100644 --- a/src/Support/Arr.php +++ b/src/Support/Arr.php @@ -15,9 +15,8 @@ final class Arr { /** * @param array $array - * @param string|int $key */ - public static function has(array $array, $key): bool + public static function has(array $array, string|int $key): bool { $key = (string) $key; @@ -38,12 +37,11 @@ final class Arr /** * @param array $array - * @param string|int $key * @param null $default * * @return array|mixed|null */ - public static function get(array $array, $key, $default = null) + public static function get(array $array, string|int $key, $default = null) { $key = (string) $key; @@ -51,7 +49,7 @@ final class Arr return $array[$key]; } - if (strpos($key, '.') === false) { + if (!str_contains($key, '.')) { return $array[$key] ?? $default; } diff --git a/src/Support/Backtrace.php b/src/Support/Backtrace.php index fa02e239..09d9e438 100644 --- a/src/Support/Backtrace.php +++ b/src/Support/Backtrace.php @@ -26,7 +26,6 @@ final class Backtrace $current = null; foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) { - if (Str::endsWith($trace[self::FILE], 'overrides/Runner/TestSuiteLoader.php')) { break; } diff --git a/src/Support/ChainableClosure.php b/src/Support/ChainableClosure.php index 0dc40275..5ce0f756 100644 --- a/src/Support/ChainableClosure.php +++ b/src/Support/ChainableClosure.php @@ -18,9 +18,9 @@ final class ChainableClosure { return function () use ($closure, $next): void { /* @phpstan-ignore-next-line */ - call_user_func_array(Closure::bind($closure, $this, get_class($this)), 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, get_class($this)), func_get_args()); + call_user_func_array(Closure::bind($next, $this, $this::class), func_get_args()); }; } diff --git a/src/Support/Container.php b/src/Support/Container.php index 7685472f..6be1a4e1 100644 --- a/src/Support/Container.php +++ b/src/Support/Container.php @@ -13,15 +13,12 @@ use ReflectionParameter; */ final class Container { - /** - * @var self - */ - private static $instance; + private static ?Container $instance = null; /** * @var array */ - private $instances = []; + private array $instances = []; /** * Gets a new or already existing container. diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index 42f31242..17b75862 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -162,7 +162,7 @@ final class Coverage $lastKey = count($array) - 1; - if (array_key_exists($lastKey, $array) && strpos($array[$lastKey], '..') !== false) { + if (array_key_exists($lastKey, $array) && str_contains($array[$lastKey], '..')) { [$from] = explode('..', $array[$lastKey]); $array[$lastKey] = $line > $from ? sprintf('%s..%s', $from, $line) : sprintf('%s..%s', $line, $from); diff --git a/src/Support/Extendable.php b/src/Support/Extendable.php index dcbb7a5a..094d7007 100644 --- a/src/Support/Extendable.php +++ b/src/Support/Extendable.php @@ -8,19 +8,13 @@ use Closure; final class Extendable { - /** - * The extendable class. - * - * @var string - */ - private $extendableClass; - /** * Creates a new extendable instance. */ - public function __construct(string $extendableClass) - { - $this->extendableClass = $extendableClass; + public function __construct( + private string $extendableClass + ) { + // .. } /** diff --git a/src/Support/HigherOrderCallables.php b/src/Support/HigherOrderCallables.php index b3bb633e..ea032a88 100644 --- a/src/Support/HigherOrderCallables.php +++ b/src/Support/HigherOrderCallables.php @@ -6,8 +6,6 @@ namespace Pest\Support; use Closure; use Pest\Expectation; -use Pest\PendingObjects\TestCall; -use PHPUnit\Framework\TestCase; /** * @internal @@ -15,13 +13,11 @@ use PHPUnit\Framework\TestCase; final class HigherOrderCallables { /** - * @var object + * Creates a new Higher Order Callables instances. */ - private $target; - - public function __construct(object $target) + public function __construct(private object $target) { - $this->target = $target; + // .. } /** @@ -33,7 +29,7 @@ final class HigherOrderCallables * * @return Expectation */ - public function expect($value) + public function expect(mixed $value): Expectation { return new Expectation($value instanceof Closure ? Reflection::bindCallableWithData($value) : $value); } @@ -47,17 +43,15 @@ final class HigherOrderCallables * * @return Expectation */ - public function and($value) + public function and(mixed $value) { return $this->expect($value); } /** * Tap into the test case to perform an action and return the test case. - * - * @return TestCall|TestCase|object */ - public function tap(callable $callable) + public function tap(callable $callable): object { Reflection::bindCallableWithData($callable); diff --git a/src/Support/HigherOrderMessage.php b/src/Support/HigherOrderMessage.php index 67993bc5..99f444f0 100644 --- a/src/Support/HigherOrderMessage.php +++ b/src/Support/HigherOrderMessage.php @@ -15,68 +15,31 @@ final class HigherOrderMessage { public const UNDEFINED_METHOD = 'Method %s does not exist'; - /** - * The filename where the function was originally called. - * - * @readonly - * - * @var string - */ - public $filename; - - /** - * The line where the function was originally called. - * - * @readonly - * - * @var int - */ - public $line; - - /** - * The method or property name to access. - * - * @readonly - * - * @var string - */ - public $name; - - /** - * The arguments. - * - * @var array|null - * - * @readonly - */ - public $arguments; - /** * An optional condition that will determine if the message will be executed. * - * @var callable(): bool|null + * @var (callable(): bool)|null */ - public $condition = null; + public $condition; /** * Creates a new higher order message. * * @param array|null $arguments */ - public function __construct(string $filename, int $line, string $methodName, $arguments) - { - $this->filename = $filename; - $this->line = $line; - $this->name = $methodName; - $this->arguments = $arguments; + public function __construct( + public string $filename, + public int $line, + public string $name, + public ?array $arguments + ) { + // .. } /** * Re-throws the given `$throwable` with the good line and filename. - * - * @return mixed */ - public function call(object $target) + public function call(object $target): mixed { /* @phpstan-ignore-next-line */ if (is_callable($this->condition) && call_user_func(Closure::bind($this->condition, $target)) === false) { @@ -91,7 +54,8 @@ final class HigherOrderMessage try { return is_array($this->arguments) ? Reflection::call($target, $this->name, $this->arguments) - : $target->{$this->name}; /* @phpstan-ignore-line */ + : $target->{$this->name}; + /* @phpstan-ignore-line */ } catch (Throwable $throwable) { Reflection::setPropertyValue($throwable, 'file', $this->filename); Reflection::setPropertyValue($throwable, 'line', $this->line); @@ -122,10 +86,8 @@ final class HigherOrderMessage /** * Determines whether or not there exists a higher order callable with the message name. - * - * @return bool */ - private function hasHigherOrderCallable() + private function hasHigherOrderCallable(): bool { return in_array($this->name, get_class_methods(HigherOrderCallables::class), true); } @@ -133,7 +95,7 @@ final class HigherOrderMessage private static function getUndefinedMethodMessage(object $target, string $methodName): string { if (\PHP_MAJOR_VERSION >= 8) { - return sprintf(sprintf(self::UNDEFINED_METHOD, sprintf('%s::%s()', get_class($target), $methodName))); + return sprintf(sprintf(self::UNDEFINED_METHOD, sprintf('%s::%s()', $target::class, $methodName))); } return sprintf(self::UNDEFINED_METHOD, $methodName); diff --git a/src/Support/HigherOrderMessageCollection.php b/src/Support/HigherOrderMessageCollection.php index a6634685..b0adf7b4 100644 --- a/src/Support/HigherOrderMessageCollection.php +++ b/src/Support/HigherOrderMessageCollection.php @@ -12,7 +12,7 @@ final class HigherOrderMessageCollection /** * @var array */ - private $messages = []; + private array $messages = []; /** * Adds a new higher order message to the collection. @@ -63,9 +63,7 @@ final class HigherOrderMessageCollection { return array_reduce( $this->messages, - static function (int $total, HigherOrderMessage $message) use ($name): int { - return $total + (int) ($name === $message->name); - }, + static fn (int $total, HigherOrderMessage $message): int => $total + (int) ($name === $message->name), 0, ); } diff --git a/src/Support/HigherOrderTapProxy.php b/src/Support/HigherOrderTapProxy.php index 3c4f3968..f76026ae 100644 --- a/src/Support/HigherOrderTapProxy.php +++ b/src/Support/HigherOrderTapProxy.php @@ -15,19 +15,13 @@ final class HigherOrderTapProxy { private const UNDEFINED_PROPERTY = 'Undefined property: P\\'; - /** - * The target being tapped. - * - * @var TestCase - */ - public $target; - /** * Create a new tap proxy instance. */ - public function __construct(TestCase $target) - { - $this->target = $target; + public function __construct( + public TestCase $target + ) { + // .. } /** diff --git a/src/Support/Reflection.php b/src/Support/Reflection.php index 44cd754c..479a86f6 100644 --- a/src/Support/Reflection.php +++ b/src/Support/Reflection.php @@ -193,9 +193,7 @@ final class Reflection } $arguments[$parameter->getName()] = implode('|', array_map( - static function (ReflectionNamedType $type): string { - return $type->getName(); - }, + static fn (ReflectionNamedType $type): string => $type->getName(), ($types instanceof ReflectionNamedType) ? [$types] // NOTE: normalize as list of to handle unions : $types->getTypes(), diff --git a/src/Support/Str.php b/src/Support/Str.php index 7a07a105..a3b7a3ab 100644 --- a/src/Support/Str.php +++ b/src/Support/Str.php @@ -33,7 +33,7 @@ final class Str */ public static function startsWith(string $target, string $search): bool { - return substr($target, 0, strlen($search)) === $search; + return str_starts_with($target, $search); } /** diff --git a/src/TestSuite.php b/src/TestSuite.php index dc2c5a5a..94797263 100644 --- a/src/TestSuite.php +++ b/src/TestSuite.php @@ -19,71 +19,51 @@ final class TestSuite { /** * Holds the current test case. - * - * @var TestCase|null */ - public $test; + public ?TestCase $test = null; /** * Holds the tests repository. - * - * @var TestRepository */ - public $tests; + public TestRepository $tests; /** * Holds the before each repository. - * - * @var BeforeEachRepository */ - public $beforeEach; + public BeforeEachRepository $beforeEach; /** * Holds the before all repository. - * - * @var BeforeAllRepository */ - public $beforeAll; + public BeforeAllRepository $beforeAll; /** * Holds the after each repository. - * - * @var AfterEachRepository */ - public $afterEach; + public AfterEachRepository $afterEach; /** * Holds the after all repository. - * - * @var AfterAllRepository */ - public $afterAll; + public AfterAllRepository $afterAll; /** * Holds the root path. - * - * @var string */ - public $rootPath; - - /** - * Holds the test path. - * - * @var string - */ - public $testPath; + public string $rootPath; /** * Holds an instance of the test suite. - * - * @var TestSuite */ - private static $instance; + private static ?TestSuite $instance = null; /** * Creates a new instance of the test suite. */ - public function __construct(string $rootPath, string $testPath) + public function __construct(string $rootPath, /** + * Holds the test path. + */ + public string $testPath) { $this->beforeAll = new BeforeAllRepository(); $this->beforeEach = new BeforeEachRepository(); @@ -92,7 +72,6 @@ final class TestSuite $this->afterAll = new AfterAllRepository(); $this->rootPath = (string) realpath($rootPath); - $this->testPath = $testPath; } /** From 648c6c5a27c1e57596f824a088909cc5fb035cc0 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 24 Oct 2021 19:37:29 +0100 Subject: [PATCH 09/17] refactor: comments --- bin/pest | 4 +- src/Actions/InteractsWithPlugins.php | 50 ------------------- src/Concerns/Expectable.php | 4 +- src/Concerns/Extendable.php | 10 ++-- src/Concerns/Logging/WritesToConsole.php | 12 +++++ src/Concerns/RetrievesValues.php | 2 +- src/Console/Help.php | 12 ++++- src/Console/Thanks.php | 11 +++- src/Contracts/HasPrintableTestCaseName.php | 22 +++----- src/Contracts/Plugins/AddsOutput.php | 4 +- src/Contracts/Plugins/HandlesArguments.php | 8 +-- src/Emitters/DispatchingEmitter.php | 3 ++ src/Exceptions/AfterAllAlreadyExist.php | 2 +- src/Exceptions/AfterEachAlreadyExist.php | 2 +- src/Exceptions/AttributeNotSupportedYet.php | 2 +- src/Exceptions/BeforeEachAlreadyExist.php | 2 +- src/Exceptions/DatasetAlreadyExist.php | 2 +- src/Exceptions/DatasetDoesNotExist.php | 2 +- src/Exceptions/DatasetMissing.php | 12 ++--- src/Exceptions/FileOrFolderNotFound.php | 2 +- src/Exceptions/InvalidConsoleArgument.php | 2 +- src/Exceptions/InvalidPestCommand.php | 2 +- src/Exceptions/MissingDependency.php | 2 +- src/Exceptions/ShouldNotHappen.php | 2 +- src/Exceptions/TestAlreadyExist.php | 2 +- src/Exceptions/TestCaseAlreadyInUse.php | 2 +- .../TestCaseClassOrTraitNotFound.php | 2 +- src/Factories/TestCaseFactory.php | 18 ++----- src/{Console => }/Kernel.php | 8 ++- src/Plugins/Actions/AddsOutput.php | 30 +++++++++++ src/Plugins/Actions/HandleArguments.php | 33 ++++++++++++ src/Support/Arr.php | 13 ++--- 32 files changed, 151 insertions(+), 133 deletions(-) delete mode 100644 src/Actions/InteractsWithPlugins.php rename src/{Console => }/Kernel.php (85%) create mode 100644 src/Plugins/Actions/AddsOutput.php create mode 100644 src/Plugins/Actions/HandleArguments.php diff --git a/bin/pest b/bin/pest index badfc903..c83b6446 100755 --- a/bin/pest +++ b/bin/pest @@ -1,12 +1,10 @@ #!/usr/bin/env php $argv - * - * @return array - */ - public static function handleArguments(array $argv): array - { - $plugins = Loader::getPlugins(HandlesArguments::class); - - /** @var HandlesArguments $plugin */ - foreach ($plugins as $plugin) { - $argv = $plugin->handleArguments($argv); - } - - return $argv; - } - - /** - * Provides an opportunity for any plugins that want - * to provide additional output after test execution. - */ - public static function addOutput(int $result): int - { - $plugins = Loader::getPlugins(AddsOutput::class); - - /** @var AddsOutput $plugin */ - foreach ($plugins as $plugin) { - $result = $plugin->addOutput($result); - } - - return $result; - } -} diff --git a/src/Concerns/Expectable.php b/src/Concerns/Expectable.php index 981e443d..474a900f 100644 --- a/src/Concerns/Expectable.php +++ b/src/Concerns/Expectable.php @@ -14,13 +14,13 @@ trait Expectable /** * @template TValue * - * Creates a new expectation. + * Creates a new Expectation. * * @param TValue $value * * @return Expectation */ - public function expect($value): Expectation + public function expect(mixed $value): Expectation { return new Expectation($value); } diff --git a/src/Concerns/Extendable.php b/src/Concerns/Extendable.php index 0d689452..5cf3e79d 100644 --- a/src/Concerns/Extendable.php +++ b/src/Concerns/Extendable.php @@ -13,12 +13,14 @@ use Closure; trait Extendable { /** + * The list of extends. + * * @var array */ private static array $extends = []; /** - * Register a custom extend. + * Register a new extend. */ public static function extend(string $name, Closure $extend): void { @@ -26,7 +28,7 @@ trait Extendable } /** - * Checks if extend is registered. + * Checks if given extend name is registered. */ public static function hasExtend(string $name): bool { @@ -37,10 +39,8 @@ trait Extendable * Dynamically handle calls to the class. * * @param array $parameters - * - * @return mixed */ - public function __call(string $method, array $parameters) + public function __call(string $method, array $parameters): mixed { if (!static::hasExtend($method)) { throw new BadMethodCallException("$method is not a callable method name."); diff --git a/src/Concerns/Logging/WritesToConsole.php b/src/Concerns/Logging/WritesToConsole.php index a2965bcb..71c69754 100644 --- a/src/Concerns/Logging/WritesToConsole.php +++ b/src/Concerns/Logging/WritesToConsole.php @@ -9,21 +9,33 @@ namespace Pest\Concerns\Logging; */ trait WritesToConsole { + /** + * Writes the given success message to the console. + */ private function writeSuccess(string $message): void { $this->writePestTestOutput($message, 'fg-green, bold', '✓'); } + /** + * Writes the given error message to the console. + */ private function writeError(string $message): void { $this->writePestTestOutput($message, 'fg-red, bold', '⨯'); } + /** + * Writes the given warning message to the console. + */ private function writeWarning(string $message): void { $this->writePestTestOutput($message, 'fg-yellow, bold', '-'); } + /** + * Writes the give message to the console. + */ private function writePestTestOutput(string $message, string $color, string $symbol): void { $this->writeWithColor($color, "$symbol ", false); diff --git a/src/Concerns/RetrievesValues.php b/src/Concerns/RetrievesValues.php index a8d832b1..56f3d2c8 100644 --- a/src/Concerns/RetrievesValues.php +++ b/src/Concerns/RetrievesValues.php @@ -19,7 +19,7 @@ trait RetrievesValues * * @return TRetrievableValue|null */ - private function retrieve(string $key, $value, $default = null) + private function retrieve(string $key, mixed $value, mixed $default = null): mixed { if (is_array($value)) { return $value[$key] ?? $default; diff --git a/src/Console/Help.php b/src/Console/Help.php index 0bb43477..174625c5 100644 --- a/src/Console/Help.php +++ b/src/Console/Help.php @@ -11,7 +11,11 @@ use Symfony\Component\Console\Output\OutputInterface; */ final class Help { - /** @var array */ + /** + * The Command messages. + * + * @var array + */ private const HELP_MESSAGES = [ 'Pest Options:', ' --init Initialise a standard Pest configuration', @@ -20,11 +24,17 @@ final class Help ' --group= Only runs tests from the specified group(s)', ]; + /** + * Creates a new Console Command instance. + */ public function __construct(private OutputInterface $output) { // .. } + /** + * Executes the Console Command. + */ public function __invoke(): void { foreach (self::HELP_MESSAGES as $message) { diff --git a/src/Console/Thanks.php b/src/Console/Thanks.php index 5ae807ec..4e24b9ae 100644 --- a/src/Console/Thanks.php +++ b/src/Console/Thanks.php @@ -14,7 +14,11 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; */ final class Thanks { - /** @var array */ + /** + * The Command messages. + * + * @var array + */ private const FUNDING_MESSAGES = [ '', ' - Star or contribute to Pest:', @@ -25,13 +29,16 @@ final class Thanks ' https://github.com/sponsors/nunomaduro', ]; + /** + * Creates a new Console Command instance. + */ public function __construct(private OutputInterface $output) { // .. } /** - * Asks the user to support Pest. + * Executes the Console Command. */ public function __invoke(): void { diff --git a/src/Contracts/HasPrintableTestCaseName.php b/src/Contracts/HasPrintableTestCaseName.php index 0d95d15d..e46a634d 100644 --- a/src/Contracts/HasPrintableTestCaseName.php +++ b/src/Contracts/HasPrintableTestCaseName.php @@ -4,18 +4,12 @@ declare(strict_types=1); namespace Pest\Contracts; -if (interface_exists(\NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName::class)) { - /** - * @internal - */ - interface HasPrintableTestCaseName extends \NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName - { - } -} else { - /** - * @internal - */ - interface HasPrintableTestCaseName - { - } +use NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName as BaseHasPrintableTestCaseName; + +/** + * @internal + */ +interface HasPrintableTestCaseName extends BaseHasPrintableTestCaseName +{ + // .. } diff --git a/src/Contracts/Plugins/AddsOutput.php b/src/Contracts/Plugins/AddsOutput.php index a105e0d0..a2ab4aeb 100644 --- a/src/Contracts/Plugins/AddsOutput.php +++ b/src/Contracts/Plugins/AddsOutput.php @@ -10,7 +10,7 @@ namespace Pest\Contracts\Plugins; interface AddsOutput { /** - * Allows to add custom output after the test suite was executed. + * Adds output after the Test Suite execution. */ - public function addOutput(int $testReturnCode): int; + public function addOutput(int $exitCode): int; } diff --git a/src/Contracts/Plugins/HandlesArguments.php b/src/Contracts/Plugins/HandlesArguments.php index 3814150b..a3c69ac5 100644 --- a/src/Contracts/Plugins/HandlesArguments.php +++ b/src/Contracts/Plugins/HandlesArguments.php @@ -10,11 +10,11 @@ namespace Pest\Contracts\Plugins; interface HandlesArguments { /** - * Allows to handle custom command line arguments. + * Adds arguments before of the Test Suite execution. * - * @param array $arguments + * @param array $argv * - * @return array the updated list of arguments + * @return array */ - public function handleArguments(array $arguments): array; + public function handleArguments(array $argv): array; } diff --git a/src/Emitters/DispatchingEmitter.php b/src/Emitters/DispatchingEmitter.php index 8173d9ca..d8e82fda 100644 --- a/src/Emitters/DispatchingEmitter.php +++ b/src/Emitters/DispatchingEmitter.php @@ -19,6 +19,9 @@ use SebastianBergmann\GlobalState\Snapshot; */ final class DispatchingEmitter implements Emitter { + /** + * Creates a new Emitter instance. + */ public function __construct(private Emitter $baseEmitter) { // .. diff --git a/src/Exceptions/AfterAllAlreadyExist.php b/src/Exceptions/AfterAllAlreadyExist.php index b21de8f9..f74e8b51 100644 --- a/src/Exceptions/AfterAllAlreadyExist.php +++ b/src/Exceptions/AfterAllAlreadyExist.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class AfterAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of after all already exist exception. + * Creates a new Exception instance. */ public function __construct(string $filename) { diff --git a/src/Exceptions/AfterEachAlreadyExist.php b/src/Exceptions/AfterEachAlreadyExist.php index 118a4509..43faa817 100644 --- a/src/Exceptions/AfterEachAlreadyExist.php +++ b/src/Exceptions/AfterEachAlreadyExist.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class AfterEachAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of after each already exist exception. + * Creates a new Exception instance. */ public function __construct(string $filename) { diff --git a/src/Exceptions/AttributeNotSupportedYet.php b/src/Exceptions/AttributeNotSupportedYet.php index e30325e3..d34262f6 100644 --- a/src/Exceptions/AttributeNotSupportedYet.php +++ b/src/Exceptions/AttributeNotSupportedYet.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class AttributeNotSupportedYet extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of attribute not supported yet. + * Creates a new Exception instance. */ public function __construct(string $attribute, string $value) { diff --git a/src/Exceptions/BeforeEachAlreadyExist.php b/src/Exceptions/BeforeEachAlreadyExist.php index 32fa49f7..fba76297 100644 --- a/src/Exceptions/BeforeEachAlreadyExist.php +++ b/src/Exceptions/BeforeEachAlreadyExist.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class BeforeEachAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of before each already exist exception. + * Creates a new Exception instance. */ public function __construct(string $filename) { diff --git a/src/Exceptions/DatasetAlreadyExist.php b/src/Exceptions/DatasetAlreadyExist.php index 0eea6a32..b5276329 100644 --- a/src/Exceptions/DatasetAlreadyExist.php +++ b/src/Exceptions/DatasetAlreadyExist.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class DatasetAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of dataset already exist. + * Creates a new Exception instance. */ public function __construct(string $name) { diff --git a/src/Exceptions/DatasetDoesNotExist.php b/src/Exceptions/DatasetDoesNotExist.php index b18669f6..72126fb8 100644 --- a/src/Exceptions/DatasetDoesNotExist.php +++ b/src/Exceptions/DatasetDoesNotExist.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class DatasetDoesNotExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of dataset does not exist. + * Creates a new Exception instance. */ public function __construct(string $name) { diff --git a/src/Exceptions/DatasetMissing.php b/src/Exceptions/DatasetMissing.php index 023487e8..8d3a7cd9 100644 --- a/src/Exceptions/DatasetMissing.php +++ b/src/Exceptions/DatasetMissing.php @@ -10,24 +10,22 @@ use NunoMaduro\Collision\Contracts\RenderlessTrace; use Symfony\Component\Console\Exception\ExceptionInterface; /** - * Creates a new instance of dataset is not present for test that has arguments. - * * @internal */ final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Create new exception instance. + * Creates a new Exception instance. * - * @param array $args A map of argument names to their typee + * @param array $arguments */ - public function __construct(string $file, string $name, array $args) + public function __construct(string $file, string $name, array $arguments) { parent::__construct(sprintf( "A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s", $name, - count($args), - implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($args), $args)), + count($arguments), + implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($arguments), $arguments)), $file, )); } diff --git a/src/Exceptions/FileOrFolderNotFound.php b/src/Exceptions/FileOrFolderNotFound.php index 49163cc4..bd59ac2a 100644 --- a/src/Exceptions/FileOrFolderNotFound.php +++ b/src/Exceptions/FileOrFolderNotFound.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class FileOrFolderNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of file not found. + * Creates a new Exception instance. */ public function __construct(string $filename) { diff --git a/src/Exceptions/InvalidConsoleArgument.php b/src/Exceptions/InvalidConsoleArgument.php index 98774b02..a3e3ad30 100644 --- a/src/Exceptions/InvalidConsoleArgument.php +++ b/src/Exceptions/InvalidConsoleArgument.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class InvalidConsoleArgument extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of should not happen. + * Creates a new Exception instance. */ public function __construct(string $message) { diff --git a/src/Exceptions/InvalidPestCommand.php b/src/Exceptions/InvalidPestCommand.php index 269585db..5a5c153d 100644 --- a/src/Exceptions/InvalidPestCommand.php +++ b/src/Exceptions/InvalidPestCommand.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class InvalidPestCommand extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of invalid pest command exception. + * Creates a new Exception instance. */ public function __construct() { diff --git a/src/Exceptions/MissingDependency.php b/src/Exceptions/MissingDependency.php index e7d7940d..43dc29ef 100644 --- a/src/Exceptions/MissingDependency.php +++ b/src/Exceptions/MissingDependency.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class MissingDependency extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of missing dependency. + * Creates a new Exception instance. */ public function __construct(string $feature, string $dependency) { diff --git a/src/Exceptions/ShouldNotHappen.php b/src/Exceptions/ShouldNotHappen.php index dc44c509..c2bb424b 100644 --- a/src/Exceptions/ShouldNotHappen.php +++ b/src/Exceptions/ShouldNotHappen.php @@ -13,7 +13,7 @@ use RuntimeException; final class ShouldNotHappen extends RuntimeException { /** - * Creates a new instance of should not happen. + * Creates a new Exception instance. */ public function __construct(Exception $exception) { diff --git a/src/Exceptions/TestAlreadyExist.php b/src/Exceptions/TestAlreadyExist.php index 68c4be95..75f4a420 100644 --- a/src/Exceptions/TestAlreadyExist.php +++ b/src/Exceptions/TestAlreadyExist.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class TestAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of test already exist. + * Creates a new Exception instance. */ public function __construct(string $fileName, string $description) { diff --git a/src/Exceptions/TestCaseAlreadyInUse.php b/src/Exceptions/TestCaseAlreadyInUse.php index f407ed17..7ebf6232 100644 --- a/src/Exceptions/TestCaseAlreadyInUse.php +++ b/src/Exceptions/TestCaseAlreadyInUse.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class TestCaseAlreadyInUse extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of test case already in use. + * Creates a new Exception instance. */ public function __construct(string $inUse, string $newOne, string $folder) { diff --git a/src/Exceptions/TestCaseClassOrTraitNotFound.php b/src/Exceptions/TestCaseClassOrTraitNotFound.php index b5e8c547..78b73a1c 100644 --- a/src/Exceptions/TestCaseClassOrTraitNotFound.php +++ b/src/Exceptions/TestCaseClassOrTraitNotFound.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface; final class TestCaseClassOrTraitNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace { /** - * Creates a new instance of after each already exist exception. + * Creates a new Exception instance. */ public function __construct(string $testCaseClass) { diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index e9c288c1..d56d163f 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -24,15 +24,11 @@ final class TestCaseFactory { /** * Marks this test case as only. - * - * @readonly */ public bool $only = false; /** * Holds the test closure. - * - * @readonly */ public Closure $test; @@ -53,7 +49,7 @@ final class TestCaseFactory /** * An array of FQN of the class traits. * - * @var array + * @var array */ public array $traits = [ Concerns\Testable::class, @@ -61,12 +57,12 @@ final class TestCaseFactory ]; /** - * Holds the higher order messages for the factory that are proxyble. + * Holds the higher order messages for the factory that are proxyable. */ public HigherOrderMessageCollection $factoryProxies; /** - * Holds the higher order messages that are proxyble. + * Holds the higher order messages that are proxyable. */ public HigherOrderMessageCollection $proxies; @@ -80,14 +76,10 @@ final class TestCaseFactory */ public function __construct( public string $filename, - public ?string $description = null, + public ?string $description, Closure $closure = null) { - $this->test = $closure ?? function (): void { - if (Assert::getCount() === 0) { - self::markTestIncomplete(); // @phpstan-ignore-line - } - }; + $this->test = $closure ?? fn () => Assert::getCount() > 0 ?: self::markTestIncomplete(); $this->factoryProxies = new HigherOrderMessageCollection(); $this->proxies = new HigherOrderMessageCollection(); diff --git a/src/Console/Kernel.php b/src/Kernel.php similarity index 85% rename from src/Console/Kernel.php rename to src/Kernel.php index cb78e9bd..f507f7e0 100644 --- a/src/Console/Kernel.php +++ b/src/Kernel.php @@ -2,10 +2,8 @@ declare(strict_types=1); -namespace Pest\Console; +namespace Pest; -use Pest\Actions\InteractsWithPlugins; -use Pest\Bootstrappers; use PHPUnit\TextUI\Application; /** @@ -53,13 +51,13 @@ final class Kernel */ public function handle(array $argv): int { - $argv = InteractsWithPlugins::handleArguments($argv); + $argv = (new Plugins\Actions\HandleArguments())->__invoke($argv); $result = $this->application->run( $argv, false, ); - return InteractsWithPlugins::addOutput($result); + return (new Plugins\Actions\AddsOutput())->__invoke($result); } /** diff --git a/src/Plugins/Actions/AddsOutput.php b/src/Plugins/Actions/AddsOutput.php new file mode 100644 index 00000000..966affe8 --- /dev/null +++ b/src/Plugins/Actions/AddsOutput.php @@ -0,0 +1,30 @@ +addOutput($exitCode); + } + + return $exitCode; + } +} diff --git a/src/Plugins/Actions/HandleArguments.php b/src/Plugins/Actions/HandleArguments.php new file mode 100644 index 00000000..f0369d01 --- /dev/null +++ b/src/Plugins/Actions/HandleArguments.php @@ -0,0 +1,33 @@ + $argv + * + * @return array + */ + public function __invoke(array $argv): array + { + $plugins = Loader::getPlugins(Plugins\HandlesArguments::class); + + /** @var Plugins\HandlesArguments $plugin */ + foreach ($plugins as $plugin) { + $argv = $plugin->handleArguments($argv); + } + + return $argv; + } +} diff --git a/src/Support/Arr.php b/src/Support/Arr.php index a55764d5..3fbe0e61 100644 --- a/src/Support/Arr.php +++ b/src/Support/Arr.php @@ -5,16 +5,12 @@ declare(strict_types=1); namespace Pest\Support; /** - * Credits: most of this class methods and implementations - * belongs to the Arr helper of laravel/framework project - * (https://github.com/laravel/framework). - * * @internal */ final class Arr { /** - * @param array $array + * Checks if the given array has the given key. */ public static function has(array $array, string|int $key): bool { @@ -36,12 +32,9 @@ final class Arr } /** - * @param array $array - * @param null $default - * - * @return array|mixed|null + * Gets the given key value. */ - public static function get(array $array, string|int $key, $default = null) + public static function get(array $array, string|int $key, mixed $default = null): mixed { $key = (string) $key; From cd34f0ba81e33d0c2f1ddea01be7fc801d21b436 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 24 Oct 2021 22:39:35 +0100 Subject: [PATCH 10/17] refactor: comments --- src/Each.php | 4 +-- src/Factories/TestCaseFactory.php | 33 +++++++++---------- src/Functions.php | 8 ++--- src/Laravel/Commands/PestDatasetCommand.php | 4 +-- src/Laravel/PestServiceProvider.php | 2 +- .../AfterEachCall.php | 13 ++++---- .../BeforeEachCall.php | 8 ++--- .../TestCall.php | 15 +++------ .../UsesCall.php | 6 ++-- src/Plugins/Actions/AddsOutput.php | 3 +- src/Plugins/Coverage.php | 2 +- src/Repositories/TestRepository.php | 2 +- src/TestSuite.php | 9 +++-- tests/Features/PendingHigherOrderTests.php | 2 +- 14 files changed, 50 insertions(+), 61 deletions(-) rename src/{PendingObjects => PendingCalls}/AfterEachCall.php (82%) rename src/{PendingObjects => PendingCalls}/BeforeEachCall.php (89%) rename src/{PendingObjects => PendingCalls}/TestCall.php (94%) rename src/{PendingObjects => PendingCalls}/UsesCall.php (96%) diff --git a/src/Each.php b/src/Each.php index 08c8f753..e16933be 100644 --- a/src/Each.php +++ b/src/Each.php @@ -23,10 +23,8 @@ final class Each /** * Creates a new expectation. - * - * @param mixed $value */ - public function and($value): Expectation + public function and(mixed $value): Expectation { return $this->original->and($value); } diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index d56d163f..4863552a 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -23,31 +23,31 @@ use RuntimeException; final class TestCaseFactory { /** - * Marks this test case as only. + * Determines if the Test Case will be the "only" being run. */ public bool $only = false; /** - * Holds the test closure. + * The Test Case closure. */ public Closure $test; /** - * Holds the dataset, if any. + * The Test Case Dataset, if any. * * @var array|string> */ public array $datasets = []; /** - * The FQN of the test case class. + * The FQN of the Test Case class. * * @var class-string */ public string $class = TestCase::class; /** - * An array of FQN of the class traits. + * An array of FQN of the Test Case traits. * * @var array */ @@ -57,22 +57,22 @@ final class TestCaseFactory ]; /** - * Holds the higher order messages for the factory that are proxyable. + * The higher order messages for the factory that are proxyable. */ public HigherOrderMessageCollection $factoryProxies; /** - * Holds the higher order messages that are proxyable. + * The higher order messages that are proxyable. */ public HigherOrderMessageCollection $proxies; /** - * Holds the higher order messages that are chainable. + * The higher order messages that are chainable. */ public HigherOrderMessageCollection $chains; /** - * Creates a new anonymous test case pending object. + * Creates a new Factory instance. */ public function __construct( public string $filename, @@ -87,11 +87,11 @@ final class TestCaseFactory } /** - * Builds the anonymous test case. + * Makes the Test Case classes. * * @return array */ - public function build(TestSuite $testSuite): array + public function make(): array { if ($this->description === null) { throw ShouldNotHappen::fromMessage('Description can not be empty.'); @@ -101,10 +101,7 @@ final class TestCaseFactory $proxies = $this->proxies; $factoryTest = $this->test; - /** - * @return mixed - */ - $test = function () use ($chains, $proxies, $factoryTest) { + $testClosure = function () use ($chains, $proxies, $factoryTest): mixed { $proxies->proxy($this); $chains->chain($this); @@ -114,8 +111,8 @@ final class TestCaseFactory $className = $this->makeClassFromFilename($this->filename); - $createTest = function ($description, $data) use ($className, $test) { - $testCase = new $className($test, $description, $data); + $createTest = function ($description, $data) use ($className, $testClosure) { + $testCase = new $className($testClosure, $description, $data); $this->factoryProxies->proxy($testCase); return $testCase; @@ -127,7 +124,7 @@ final class TestCaseFactory } /** - * Makes a fully qualified class name from the given filename. + * Makes a Fully Qualified Class Name from the given filename. */ public function makeClassFromFilename(string $filename): string { diff --git a/src/Functions.php b/src/Functions.php index 855e415e..20a0ee6e 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -4,10 +4,10 @@ declare(strict_types=1); use Pest\Datasets; use Pest\Expectation; -use Pest\PendingObjects\AfterEachCall; -use Pest\PendingObjects\BeforeEachCall; -use Pest\PendingObjects\TestCall; -use Pest\PendingObjects\UsesCall; +use Pest\PendingCalls\AfterEachCall; +use Pest\PendingCalls\BeforeEachCall; +use Pest\PendingCalls\TestCall; +use Pest\PendingCalls\UsesCall; use Pest\Support\Backtrace; use Pest\Support\Extendable; use Pest\Support\HigherOrderTapProxy; diff --git a/src/Laravel/Commands/PestDatasetCommand.php b/src/Laravel/Commands/PestDatasetCommand.php index e5cab365..33ae8db8 100644 --- a/src/Laravel/Commands/PestDatasetCommand.php +++ b/src/Laravel/Commands/PestDatasetCommand.php @@ -17,7 +17,7 @@ use Pest\TestSuite; final class PestDatasetCommand extends Command { /** - * The console command name. + * The Console Command name. * * @var string */ @@ -25,7 +25,7 @@ final class PestDatasetCommand extends Command {--test-directory=tests : The name of the tests directory}'; /** - * The console command description. + * The Console Command description. * * @var string */ diff --git a/src/Laravel/PestServiceProvider.php b/src/Laravel/PestServiceProvider.php index ae3c939a..01505d33 100644 --- a/src/Laravel/PestServiceProvider.php +++ b/src/Laravel/PestServiceProvider.php @@ -14,7 +14,7 @@ use Pest\Laravel\Commands\PestTestCommand; final class PestServiceProvider extends ServiceProvider { /** - * Register artisan commands. + * Register Artisan Commands. */ public function register(): void { diff --git a/src/PendingObjects/AfterEachCall.php b/src/PendingCalls/AfterEachCall.php similarity index 82% rename from src/PendingObjects/AfterEachCall.php rename to src/PendingCalls/AfterEachCall.php index 6702dcb5..2bf0b16a 100644 --- a/src/PendingObjects/AfterEachCall.php +++ b/src/PendingCalls/AfterEachCall.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Pest\PendingObjects; +namespace Pest\PendingCalls; use Closure; use Pest\Support\Backtrace; @@ -17,21 +17,22 @@ use Pest\TestSuite; final class AfterEachCall { /** - * Holds the before each closure. + * The "afterEach" closure. */ private Closure $closure; /** - * Holds calls that should be proxied. + * The calls that should be proxied. */ private HigherOrderMessageCollection $proxies; /** - * Creates a new instance of before each call. + * Creates a new Pending Call. */ public function __construct( private TestSuite $testSuite, - private string $filename, Closure $closure = null + private string $filename, + Closure $closure = null ) { $this->closure = $closure instanceof Closure ? $closure : NullClosure::create(); @@ -39,7 +40,7 @@ final class AfterEachCall } /** - * Dispatch the creation of each call. + * Creates the Call. */ public function __destruct() { diff --git a/src/PendingObjects/BeforeEachCall.php b/src/PendingCalls/BeforeEachCall.php similarity index 89% rename from src/PendingObjects/BeforeEachCall.php rename to src/PendingCalls/BeforeEachCall.php index ff637d52..8fc5a08f 100644 --- a/src/PendingObjects/BeforeEachCall.php +++ b/src/PendingCalls/BeforeEachCall.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Pest\PendingObjects; +namespace Pest\PendingCalls; use Closure; use Pest\Support\Backtrace; @@ -22,12 +22,12 @@ final class BeforeEachCall private \Closure $closure; /** - * Holds calls that should be proxied. + * The calls that should be proxied. */ private HigherOrderMessageCollection $proxies; /** - * Creates a new instance of before each call. + * Creates a new Pending Call. */ public function __construct( private TestSuite $testSuite, @@ -40,7 +40,7 @@ final class BeforeEachCall } /** - * Dispatch the creation of each call. + * Creates the Call. */ public function __destruct() { diff --git a/src/PendingObjects/TestCall.php b/src/PendingCalls/TestCall.php similarity index 94% rename from src/PendingObjects/TestCall.php rename to src/PendingCalls/TestCall.php index 235c1750..f6c1300b 100644 --- a/src/PendingObjects/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Pest\PendingObjects; +namespace Pest\PendingCalls; use Closure; use Pest\Factories\TestCaseFactory; @@ -20,21 +20,17 @@ use SebastianBergmann\Exporter\Exporter; final class TestCall { /** - * Holds the test case factory. - * - * @readonly + * The Test Case Factory. */ private TestCaseFactory $testCaseFactory; /** * If test call is descriptionLess. - * - * @readonly */ - private bool $descriptionLess = false; + private bool $descriptionLess; /** - * Creates a new instance of a pending test call. + * Creates a new Pending Call. */ public function __construct( private TestSuite $testSuite, @@ -207,8 +203,7 @@ final class TestCall } /** - * Adds the current test case factory - * to the tests repository. + * Creates the Call. */ public function __destruct() { diff --git a/src/PendingObjects/UsesCall.php b/src/PendingCalls/UsesCall.php similarity index 96% rename from src/PendingObjects/UsesCall.php rename to src/PendingCalls/UsesCall.php index 8a954dbf..6609e8b2 100644 --- a/src/PendingObjects/UsesCall.php +++ b/src/PendingCalls/UsesCall.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Pest\PendingObjects; +namespace Pest\PendingCalls; use Closure; use Pest\TestSuite; @@ -41,7 +41,7 @@ final class UsesCall private array $groups = []; /** - * Creates a new instance of a pending test uses. + * Creates a new Pending Call. * * @param array $classAndTraits */ @@ -135,7 +135,7 @@ final class UsesCall } /** - * Dispatch the creation of uses. + * Creates the Call. */ public function __destruct() { diff --git a/src/Plugins/Actions/AddsOutput.php b/src/Plugins/Actions/AddsOutput.php index 966affe8..967ff0a2 100644 --- a/src/Plugins/Actions/AddsOutput.php +++ b/src/Plugins/Actions/AddsOutput.php @@ -13,8 +13,7 @@ final class AddsOutput /** * Executes the Plugin action. * - * Provides an opportunity for any plugins that want - * to provide additional output after test execution. + * Provides an opportunity for any plugins that want to provide additional output after test execution. */ public function __invoke(int $exitCode): int { diff --git a/src/Plugins/Coverage.php b/src/Plugins/Coverage.php index 667c0571..8febde52 100644 --- a/src/Plugins/Coverage.php +++ b/src/Plugins/Coverage.php @@ -28,7 +28,7 @@ final class Coverage implements AddsOutput, HandlesArguments private const MIN_OPTION = 'min'; /** - * Whether should show the coverage or not. + * Whether it should show the coverage or not. */ public bool $coverage = false; diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index 9eafa001..40c2972b 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -101,7 +101,7 @@ final class TestRepository foreach ($state as $testFactory) { /** @var TestCaseFactory $testFactory */ - $tests = $testFactory->build($testSuite); + $tests = $testFactory->make($testSuite); foreach ($tests as $test) { $each($test); } diff --git a/src/TestSuite.php b/src/TestSuite.php index 94797263..0df60394 100644 --- a/src/TestSuite.php +++ b/src/TestSuite.php @@ -60,10 +60,9 @@ final class TestSuite /** * Creates a new instance of the test suite. */ - public function __construct(string $rootPath, /** - * Holds the test path. - */ - public string $testPath) + public function __construct( + string $rootPath, + public string $testPath) { $this->beforeAll = new BeforeAllRepository(); $this->beforeEach = new BeforeEachRepository(); @@ -71,7 +70,7 @@ final class TestSuite $this->afterEach = new AfterEachRepository(); $this->afterAll = new AfterAllRepository(); - $this->rootPath = (string) realpath($rootPath); + $this->rootPath = (string) realpath($rootPath); } /** diff --git a/tests/Features/PendingHigherOrderTests.php b/tests/Features/PendingHigherOrderTests.php index dbe59216..a22cf027 100644 --- a/tests/Features/PendingHigherOrderTests.php +++ b/tests/Features/PendingHigherOrderTests.php @@ -1,6 +1,6 @@ Date: Fri, 5 Nov 2021 12:00:56 +0100 Subject: [PATCH 11/17] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a4e616fe..e0925077 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ We would like to extend our thanks to the following sponsors for funding Pest development. If you are interested in becoming a sponsor, please visit the Nuno Maduro's [Sponsors page](https://github.com/sponsors/nunomaduro). +### Platinum Sponsors + +- **[Worksome](https://www.worksome.com/)** + ### Premium Sponsors - **[Akaunting](https://akaunting.com)** From 408ae4cad882feec1631445dc84b616c390237bf Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 8 Nov 2021 18:31:48 +0000 Subject: [PATCH 12/17] docs: adds `spatie.be` as platinum sponsor --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0925077..e616d332 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ We would like to extend our thanks to the following sponsors for funding Pest de ### Platinum Sponsors +- **[Spatie](https://spatie.be)** - **[Worksome](https://www.worksome.com/)** ### Premium Sponsors @@ -31,6 +32,5 @@ We would like to extend our thanks to the following sponsors for funding Pest de - **[Fathom Analytics](https://usefathom.com/)** - **[Meema](https://meema.io)** - **[Scout APM](https://scoutapm.com)** -- **[Spatie](https://spatie.be)** Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**. From da5c21de8f89808f2bff825282f29df7df538d52 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 9 Nov 2021 01:42:10 +0000 Subject: [PATCH 13/17] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e616d332..3f710c49 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,11 @@ We would like to extend our thanks to the following sponsors for funding Pest de ### Premium Sponsors -- **[Akaunting](https://akaunting.com)** -- **[Auth0](https://auth0.com)** -- **[Codecourse](https://codecourse.com/)** -- **[Fathom Analytics](https://usefathom.com/)** -- **[Meema](https://meema.io)** -- **[Scout APM](https://scoutapm.com)** +- [Akaunting](https://akaunting.com) +- [Auth0](https://auth0.com) +- [Codecourse](https://codecourse.com/) +- [Fathom Analytics](https://usefathom.com/) +- [Meema](https://meema.io) +- [Scout APM](https://scoutapm.com) Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**. From 4b213d63bdcc45826f764e494c51bb1cc503e825 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 14 Nov 2021 19:58:25 +0000 Subject: [PATCH 14/17] feat: reworks evalution of Test Case --- .php-cs-fixer.dist.php | 1 + composer.json | 3 +- overrides/Runner/TestSuiteLoader.php | 122 ++++++--- src/Bootstrappers/BootEmitter.php | 29 -- src/Bootstrappers/BootExceptionHandler.php | 4 +- src/Bootstrappers/BootFiles.php | 3 +- src/Concerns/Testable.php | 81 +----- src/Datasets.php | 45 +++- src/Emitters/DispatchingEmitter.php | 251 ------------------ src/Factories/Annotations/Depends.php | 28 ++ src/Factories/Annotations/Groups.php | 25 ++ src/Factories/Concerns/HigherOrderable.php | 35 +++ src/Factories/TestCaseFactory.php | 205 ++++++++------ src/Factories/TestCaseMethodFactory.php | 102 +++++++ src/IgnorableTestCase.php | 15 ++ src/Kernel.php | 1 - src/Logging/TeamCity.php | 2 +- src/PendingCalls/TestCall.php | 42 +-- src/Pest.php | 2 +- src/Repositories/TestRepository.php | 157 +++++------ src/Subscribers/EnsureTestsAreLoaded.php | 3 + src/Support/HigherOrderMessage.php | 3 +- src/Support/Str.php | 10 + tests/Features/Datasets.php | 8 +- .../PHPUnit/CustomTestCase/CustomTestCase.php | 2 +- tests/Unit/TestSuite.php | 44 +-- 26 files changed, 603 insertions(+), 620 deletions(-) delete mode 100644 src/Bootstrappers/BootEmitter.php delete mode 100644 src/Emitters/DispatchingEmitter.php create mode 100644 src/Factories/Annotations/Depends.php create mode 100644 src/Factories/Annotations/Groups.php create mode 100644 src/Factories/Concerns/HigherOrderable.php create mode 100644 src/Factories/TestCaseMethodFactory.php create mode 100644 src/IgnorableTestCase.php diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index efa5d003..3454e972 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -3,6 +3,7 @@ $finder = PhpCsFixer\Finder::create() ->in(__DIR__ . DIRECTORY_SEPARATOR . 'tests') ->in(__DIR__ . DIRECTORY_SEPARATOR . 'bin') + ->in(__DIR__ . DIRECTORY_SEPARATOR . 'overrides') ->in(__DIR__ . DIRECTORY_SEPARATOR . 'stubs') ->in(__DIR__ . DIRECTORY_SEPARATOR . 'src') ->append(['.php-cs-fixer.dist.php']); diff --git a/composer.json b/composer.json index a4ef0856..1b8e7aaa 100644 --- a/composer.json +++ b/composer.json @@ -48,8 +48,7 @@ "illuminate/console": "^8.47.0", "illuminate/support": "^8.47.0", "laravel/dusk": "^6.15.0", - "pestphp/pest-dev-tools": "dev-master", - "pestphp/pest-plugin-mock": "^1.0" + "pestphp/pest-dev-tools": "dev-master" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/overrides/Runner/TestSuiteLoader.php b/overrides/Runner/TestSuiteLoader.php index 6535c0fa..19b37a82 100644 --- a/overrides/Runner/TestSuiteLoader.php +++ b/overrides/Runner/TestSuiteLoader.php @@ -1,22 +1,5 @@ . * All rights reserved. @@ -51,35 +34,82 @@ use PHPUnit\Framework\WarningTestCase; * POSSIBILITY OF SUCH DAMAGE. */ +declare(strict_types=1); + +namespace PHPUnit\Runner; + +use function array_diff; +use function array_values; +use function basename; +use function class_exists; +use function get_declared_classes; +use Pest\IgnorableTestCase; +use Pest\TestSuite; +use PHPUnit\Framework\TestCase; +use ReflectionClass; +use ReflectionException; +use function stripos; +use function strlen; +use function substr; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ final class TestSuiteLoader { /** - * Loads the test suite. + * @psalm-var list + */ + private static array $loadedClasses = []; + + /** + * @psalm-var list + */ + private static array $declaredClasses = []; + + public function __construct() + { + if (empty(self::$declaredClasses)) { + self::$declaredClasses = get_declared_classes(); + } + } + + /** + * @throws Exception */ public function load(string $suiteClassFile): ReflectionClass { - $suiteClassName = basename($suiteClassFile, '.php'); - $loadedClasses = get_declared_classes(); + $suiteClassName = $this->classNameFromFileName($suiteClassFile); if (!class_exists($suiteClassName, false)) { (static function () use ($suiteClassFile) { include_once $suiteClassFile; + + TestSuite::getInstance()->tests->makeIfExists($suiteClassFile); })(); $loadedClasses = array_values( - array_diff(get_declared_classes(), $loadedClasses) + array_diff( + get_declared_classes(), + array_merge( + self::$declaredClasses, + self::$loadedClasses + ) + ) ); - if (empty($loadedClasses)) { - return new ReflectionClass(WarningTestCase::class); + self::$loadedClasses = array_merge($loadedClasses, self::$loadedClasses); + + if (empty(self::$loadedClasses)) { + return $this->exceptionFor($suiteClassName, $suiteClassFile); } } if (!class_exists($suiteClassName, false)) { + // this block will handle namespaced classes $offset = 0 - strlen($suiteClassName); - foreach ($loadedClasses as $loadedClass) { - + foreach (self::$loadedClasses as $loadedClass) { if (stripos(substr($loadedClass, $offset - 1), '\\' . $suiteClassName) === 0) { $suiteClassName = $loadedClass; @@ -89,18 +119,16 @@ final class TestSuiteLoader } if (!class_exists($suiteClassName, false)) { - return new ReflectionClass(WarningTestCase::class); + return $this->exceptionFor($suiteClassName, $suiteClassFile); } try { $class = new ReflectionClass($suiteClassName); + // @codeCoverageIgnoreStart } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); } + // @codeCoverageIgnoreEnd if ($class->isSubclassOf(TestCase::class) && !$class->isAbstract()) { return $class; @@ -109,19 +137,39 @@ final class TestSuiteLoader if ($class->hasMethod('suite')) { try { $method = $class->getMethod('suite'); + // @codeCoverageIgnoreStart } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); } + // @codeCoverageIgnoreEnd if (!$method->isAbstract() && $method->isPublic() && $method->isStatic()) { return $class; } } - return new ReflectionClass(WarningTestCase::class); + return $this->exceptionFor($suiteClassName, $suiteClassFile); + } + + public function reload(ReflectionClass $aClass): ReflectionClass + { + return $aClass; + } + + private function classNameFromFileName(string $suiteClassFile): string + { + $className = basename($suiteClassFile, '.php'); + $dotPos = strpos($className, '.'); + + if ($dotPos !== false) { + $className = substr($className, 0, $dotPos); + } + + return $className; + } + + private function exceptionFor(string $className, string $filename): ReflectionClass + { + return new ReflectionClass(IgnorableTestCase::class); } } diff --git a/src/Bootstrappers/BootEmitter.php b/src/Bootstrappers/BootEmitter.php deleted file mode 100644 index 0e389baf..00000000 --- a/src/Bootstrappers/BootEmitter.php +++ /dev/null @@ -1,29 +0,0 @@ -setStaticPropertyValue('emitter', new DispatchingEmitter( - $baseEmitter, - )); - } - } -} diff --git a/src/Bootstrappers/BootExceptionHandler.php b/src/Bootstrappers/BootExceptionHandler.php index 2494dc46..a504ee7e 100644 --- a/src/Bootstrappers/BootExceptionHandler.php +++ b/src/Bootstrappers/BootExceptionHandler.php @@ -16,6 +16,8 @@ final class BootExceptionHandler */ public function __invoke(): void { - (new Collision\Provider())->register(); + $handler = new Collision\Provider(); + + $handler->register(); } } diff --git a/src/Bootstrappers/BootFiles.php b/src/Bootstrappers/BootFiles.php index 09958263..51781ff2 100644 --- a/src/Bootstrappers/BootFiles.php +++ b/src/Bootstrappers/BootFiles.php @@ -35,8 +35,7 @@ final class BootFiles */ public function __invoke(): void { - $rootPath = TestSuite::getInstance()->rootPath; - + $rootPath = TestSuite::getInstance()->rootPath; $testsPath = $rootPath . DIRECTORY_SEPARATOR . testDirectory(); foreach (self::STRUCTURE as $filename) { diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index d90229cd..76e8bc88 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -5,11 +5,9 @@ declare(strict_types=1); namespace Pest\Concerns; use Closure; -use Pest\Support\Backtrace; use Pest\Support\ChainableClosure; use Pest\Support\ExceptionTrace; use Pest\TestSuite; -use PHPUnit\Framework\ExecutionOrderDependency; use Throwable; /** @@ -17,11 +15,6 @@ use Throwable; */ trait Testable { - /** - * The Test Case description. - */ - private string $__description; - /** * The Test Case "test" closure. */ @@ -47,47 +40,23 @@ trait Testable */ private static ?Closure $__afterAll = null; + /** + * Resets the test case static properties. + */ + public static function flush(): void + { + self::$__beforeAll = null; + self::$__afterAll = null; + } + /** * Creates a new Test Case instance. */ - public function __construct(Closure $test, string $description, array $data) + public function __construct(string $name) { - $this->__test = $test; - $this->__description = $description; - self::$__beforeAll = null; - self::$__afterAll = null; + parent::__construct($name); - parent::__construct('__test'); - - $this->setData($description, $data); - } - - /** - * Adds groups to the Test Case. - */ - public function addGroups(array $groups): void - { - $groups = array_unique(array_merge($this->groups(), $groups)); - - $this->setGroups($groups); - } - - /** - * Adds dependencies to the Test Case. - */ - public function addDependencies(array $tests): void - { - $className = $this::class; - - $tests = array_map(static function (string $test) use ($className): ExecutionOrderDependency { - if (!str_contains($test, '::')) { - $test = "{$className}::{$test}"; - } - - return new ExecutionOrderDependency($test, '__test'); - }, $tests); - - $this->setDependencies($tests); + $this->__test = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($name)->getClosure($this); } /** @@ -148,16 +117,6 @@ trait Testable : $hook; } - /** - * Gets the Test Case name. - */ - public function getName(bool $withDataSet = true): string - { - return (str_ends_with(Backtrace::file(), 'TestRunner.php') || Backtrace::line() === 277) - ? '__test' - : $this->__description; - } - /** * Gets the Test Case filename. */ @@ -234,26 +193,14 @@ trait Testable TestSuite::getInstance()->test = null; } - /** - * Gets the Test Case filename and description. - */ - public function toString(): string - { - return \sprintf( - '%s::%s', - self::$__filename, - $this->__description - ); - } - /** * Executes the Test Case current test. * * @throws Throwable */ - public function __test(): mixed + private function __runTest(Closure $closure, ...$args): mixed { - return $this->__callClosure($this->__test, $this->__resolveTestArguments(func_get_args())); + return $this->__callClosure($closure, $this->__resolveTestArguments($args)); } /** diff --git a/src/Datasets.php b/src/Datasets.php index 0a245079..38cc6662 100644 --- a/src/Datasets.php +++ b/src/Datasets.php @@ -22,6 +22,13 @@ final class Datasets */ private static array $datasets = []; + /** + * Holds the withs. + * + * @var array + */ + private static array $withs = []; + /** * Sets the given. * @@ -36,35 +43,43 @@ final class Datasets self::$datasets[$name] = $data; } + /** + * Sets the given. + * + * @param Closure|iterable $data + */ + public static function with(string $filename, string $description, Closure|iterable|string $with): void + { + self::$withs[$filename . '>>>' . $description] = $with; + } + /** * @return Closure|iterable */ - public static function get(string $name): Closure|iterable + public static function get(string $filename, string $description): Closure|iterable { - if (!array_key_exists($name, self::$datasets)) { - throw new DatasetDoesNotExist($name); - } + $dataset = self::$withs[$filename . '>>>' . $description]; - return self::$datasets[$name]; + return self::resolve($description, $dataset); } /** * Resolves the current dataset to an array value. * - * @param array|string> $datasets + * @param array|string> $dataset * - * @return array + * @return array|null */ - public static function resolve(string $description, array $datasets): array + public static function resolve(string $description, array $dataset): array|null { /* @phpstan-ignore-next-line */ - if (empty($datasets)) { - return [$description => []]; + if (empty($dataset)) { + return null; } - $datasets = self::processDatasets($datasets); + $dataset = self::processDatasets($dataset); - $datasetCombinations = self::getDataSetsCombinations($datasets); + $datasetCombinations = self::getDataSetsCombinations($dataset); $dataSetDescriptions = []; $dataSetValues = []; @@ -114,7 +129,11 @@ final class Datasets $processedDataset = []; if (is_string($data)) { - $datasets[$index] = self::get($data); + if (!isset(self::$datasets[$data])) { + throw new DatasetDoesNotExist($data); + } + + $datasets[$index] = self::$datasets[$data]; } if (is_callable($datasets[$index])) { diff --git a/src/Emitters/DispatchingEmitter.php b/src/Emitters/DispatchingEmitter.php deleted file mode 100644 index d8e82fda..00000000 --- a/src/Emitters/DispatchingEmitter.php +++ /dev/null @@ -1,251 +0,0 @@ -baseEmitter->eventFacadeSealed(...func_get_args()); - } - - public function testRunnerStarted(): void - { - $this->baseEmitter->testRunnerStarted(...func_get_args()); - } - - public function testRunnerConfigured(Configuration $configuration): void - { - $this->baseEmitter->testRunnerConfigured($configuration); - } - - public function testRunnerFinished(): void - { - $this->baseEmitter->testRunnerFinished(...func_get_args()); - } - - public function assertionMade(mixed $value, Constraint\Constraint $constraint, string $message, bool $hasFailed): void - { - $this->baseEmitter->assertionMade($value, $constraint, $message, $hasFailed); - } - - public function bootstrapFinished(string $filename): void - { - $this->baseEmitter->bootstrapFinished($filename); - } - - public function comparatorRegistered(string $className): void - { - $this->baseEmitter->comparatorRegistered($className); - } - - public function extensionLoaded(string $name, string $version): void - { - $this->baseEmitter->extensionLoaded($name, $version); - } - - public function globalStateCaptured(Snapshot $snapshot): void - { - $this->baseEmitter->globalStateCaptured($snapshot); - } - - public function globalStateModified(Snapshot $snapshotBefore, Snapshot $snapshotAfter, string $diff): void - { - $this->baseEmitter->globalStateModified($snapshotBefore, $snapshotAfter, $diff); - } - - public function globalStateRestored(Snapshot $snapshot): void - { - $this->baseEmitter->globalStateRestored($snapshot); - } - - public function testErrored(Code\Test $test, Throwable $throwable): void - { - $this->baseEmitter->testErrored(...func_get_args()); - } - - public function testFailed(Code\Test $test, Throwable $throwable): void - { - $this->baseEmitter->testFailed(...func_get_args()); - } - - public function testFinished(Code\Test $test): void - { - $this->baseEmitter->testFinished(...func_get_args()); - } - - public function testOutputPrinted(Code\Test $test, string $output): void - { - $this->baseEmitter->testOutputPrinted(...func_get_args()); - } - - public function testPassed(Code\Test $test): void - { - $this->baseEmitter->testPassed(...func_get_args()); - } - - public function testPassedWithWarning(Code\Test $test, Throwable $throwable): void - { - $this->baseEmitter->testPassedWithWarning(...func_get_args()); - } - - public function testConsideredRisky(Code\Test $test, Throwable $throwable): void - { - $this->baseEmitter->testConsideredRisky(...func_get_args()); - } - - public function testAborted(Code\Test $test, Throwable $throwable): void - { - $this->baseEmitter->testAborted(...func_get_args()); - } - - public function testSkipped(Code\Test $test, string $message): void - { - $this->baseEmitter->testSkipped(...func_get_args()); - } - - public function testPrepared(Code\Test $test): void - { - $this->baseEmitter->testPrepared(...func_get_args()); - } - - public function testAfterTestMethodFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void - { - $this->baseEmitter->testAfterTestMethodFinished(...func_get_args()); - } - - public function testAfterLastTestMethodFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void - { - $this->baseEmitter->testAfterLastTestMethodFinished(...func_get_args()); - } - - public function testBeforeFirstTestMethodCalled(string $testClassName, Code\ClassMethod $calledMethod): void - { - $this->baseEmitter->testBeforeFirstTestMethodCalled(...func_get_args()); - } - - public function testBeforeFirstTestMethodFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void - { - $this->baseEmitter->testBeforeFirstTestMethodFinished(...func_get_args()); - } - - public function testBeforeTestMethodCalled(string $testClassName, Code\ClassMethod $calledMethod): void - { - $this->baseEmitter->testBeforeTestMethodCalled(...func_get_args()); - } - - public function testBeforeTestMethodFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void - { - $this->baseEmitter->testBeforeTestMethodFinished(...func_get_args()); - } - - public function testPreConditionCalled(string $testClassName, Code\ClassMethod $calledMethod): void - { - $this->baseEmitter->testPreConditionCalled(...func_get_args()); - } - - public function testPreConditionFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void - { - $this->baseEmitter->testPreConditionFinished(...func_get_args()); - } - - public function testPostConditionCalled(string $testClassName, Code\ClassMethod $calledMethod): void - { - $this->baseEmitter->testPostConditionCalled(...func_get_args()); - } - - public function testPostConditionFinished(string $testClassName, Code\ClassMethod ...$calledMethods): void - { - $this->baseEmitter->testPostConditionFinished(...func_get_args()); - } - - public function testAfterTestMethodCalled(string $testClassName, Code\ClassMethod $calledMethod): void - { - $this->baseEmitter->testAfterTestMethodCalled(...func_get_args()); - } - - public function testAfterLastTestMethodCalled(string $testClassName, Code\ClassMethod $calledMethod): void - { - $this->baseEmitter->testAfterLastTestMethodCalled(...func_get_args()); - } - - public function testMockObjectCreated(string $className): void - { - $this->baseEmitter->testMockObjectCreated(...func_get_args()); - } - - public function testMockObjectCreatedForTrait(string $traitName): void - { - $this->baseEmitter->testMockObjectCreatedForTrait(...func_get_args()); - } - - public function testMockObjectCreatedForAbstractClass(string $className): void - { - $this->baseEmitter->testMockObjectCreatedForAbstractClass(...func_get_args()); - } - - public function testMockObjectCreatedFromWsdl(string $wsdlFile, string $originalClassName, string $mockClassName, array $methods, bool $callOriginalConstructor, array $options): void - { - $this->baseEmitter->testMockObjectCreatedFromWsdl(...func_get_args()); - } - - public function testPartialMockObjectCreated(string $className, string ...$methodNames): void - { - $this->baseEmitter->testPartialMockObjectCreated(...func_get_args()); - } - - public function testTestProxyCreated(string $className, array $constructorArguments): void - { - $this->baseEmitter->testTestProxyCreated(...func_get_args()); - } - - public function testTestStubCreated(string $className): void - { - $this->baseEmitter->testTestStubCreated(...func_get_args()); - } - - public function testSuiteLoaded(TestSuite $testSuite): void - { - EnsureTestsAreLoaded::setTestSuite($testSuite); - - $this->baseEmitter->testSuiteLoaded(...func_get_args()); - } - - public function testSuiteSorted(int $executionOrder, int $executionOrderDefects, bool $resolveDependencies): void - { - $this->baseEmitter->testSuiteSorted(...func_get_args()); - } - - public function testSuiteStarted(TestSuite $testSuite): void - { - $this->baseEmitter->testSuiteStarted(...func_get_args()); - } - - public function testSuiteFinished(TestSuite $testSuite, TestResult $result): void - { - $this->baseEmitter->testSuiteFinished(...func_get_args()); - } -} diff --git a/src/Factories/Annotations/Depends.php b/src/Factories/Annotations/Depends.php new file mode 100644 index 00000000..15b29359 --- /dev/null +++ b/src/Factories/Annotations/Depends.php @@ -0,0 +1,28 @@ +depends as $depend) { + $depend = Str::evaluable($depend); + + $annotations[] = "@depends $depend"; + } + + return $annotations; + } +} diff --git a/src/Factories/Annotations/Groups.php b/src/Factories/Annotations/Groups.php new file mode 100644 index 00000000..96752d6e --- /dev/null +++ b/src/Factories/Annotations/Groups.php @@ -0,0 +1,25 @@ +groups as $group) { + $annotations[] = "@group $group"; + } + + return $annotations; + } +} diff --git a/src/Factories/Concerns/HigherOrderable.php b/src/Factories/Concerns/HigherOrderable.php new file mode 100644 index 00000000..160d8684 --- /dev/null +++ b/src/Factories/Concerns/HigherOrderable.php @@ -0,0 +1,35 @@ +chains = new HigherOrderMessageCollection(); + $this->factoryProxies = new HigherOrderMessageCollection(); + $this->proxies = new HigherOrderMessageCollection(); + } +} diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index 4863552a..8882a0da 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -4,16 +4,18 @@ declare(strict_types=1); namespace Pest\Factories; -use Closure; use ParseError; use Pest\Concerns; use Pest\Contracts\HasPrintableTestCaseName; use Pest\Datasets; +use Pest\Exceptions\DatasetMissing; use Pest\Exceptions\ShouldNotHappen; -use Pest\Support\HigherOrderMessageCollection; +use Pest\Exceptions\TestAlreadyExist; +use Pest\Factories\Concerns\HigherOrderable; +use Pest\Plugins\Environment; +use Pest\Support\Reflection; use Pest\Support\Str; use Pest\TestSuite; -use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -22,22 +24,17 @@ use RuntimeException; */ final class TestCaseFactory { - /** - * Determines if the Test Case will be the "only" being run. - */ - public bool $only = false; + use HigherOrderable; /** - * The Test Case closure. - */ - public Closure $test; - - /** - * The Test Case Dataset, if any. + * The list of annotations. * - * @var array|string> + * @var array */ - public array $datasets = []; + private static array $annotations = [ + Annotations\Depends::class, + Annotations\Groups::class, + ]; /** * The FQN of the Test Case class. @@ -47,7 +44,14 @@ final class TestCaseFactory public string $class = TestCase::class; /** - * An array of FQN of the Test Case traits. + * The list of class methods. + * + * @var array + */ + public array $methods = []; + + /** + * The list of class traits. * * @var array */ @@ -56,81 +60,48 @@ final class TestCaseFactory Concerns\Expectable::class, ]; - /** - * The higher order messages for the factory that are proxyable. - */ - public HigherOrderMessageCollection $factoryProxies; - - /** - * The higher order messages that are proxyable. - */ - public HigherOrderMessageCollection $proxies; - - /** - * The higher order messages that are chainable. - */ - public HigherOrderMessageCollection $chains; - /** * Creates a new Factory instance. */ public function __construct( - public string $filename, - public ?string $description, - Closure $closure = null) - { - $this->test = $closure ?? fn () => Assert::getCount() > 0 ?: self::markTestIncomplete(); + public string $filename + ) { + $this->bootHigherOrderable(); + } - $this->factoryProxies = new HigherOrderMessageCollection(); - $this->proxies = new HigherOrderMessageCollection(); - $this->chains = new HigherOrderMessageCollection(); + public function make(): void + { + $methods = array_filter($this->methods, function ($method) { + return count($onlyTestCases = $this->methodsUsingOnly()) === 0 || in_array($method, $onlyTestCases, true); + }); + + if (count($this->methods) > 0) { + $this->evaluate($this->filename, $methods); + } } /** - * Makes the Test Case classes. + * Returns all the "only" methods. * - * @return array + * @return array */ - public function make(): array + public function methodsUsingOnly(): array { - if ($this->description === null) { - throw ShouldNotHappen::fromMessage('Description can not be empty.'); + if (Environment::name() === Environment::CI) { + return []; } - $chains = $this->chains; - $proxies = $this->proxies; - $factoryTest = $this->test; - - $testClosure = function () use ($chains, $proxies, $factoryTest): mixed { - $proxies->proxy($this); - $chains->chain($this); - - /* @phpstan-ignore-next-line */ - return call_user_func(Closure::bind($factoryTest, $this, $this::class), ...func_get_args()); - }; - - $className = $this->makeClassFromFilename($this->filename); - - $createTest = function ($description, $data) use ($className, $testClosure) { - $testCase = new $className($testClosure, $description, $data); - $this->factoryProxies->proxy($testCase); - - return $testCase; - }; - - $datasets = Datasets::resolve($this->description, $this->datasets); - - return array_map($createTest, array_keys($datasets), $datasets); + return array_filter($this->methods, static fn ($method): bool => $method->only); } /** - * Makes a Fully Qualified Class Name from the given filename. + * Creates a Test Case class using a runtime evaluate. */ - public function makeClassFromFilename(string $filename): string + public function evaluate(string $filename, array $methods): string { if ('\\' === DIRECTORY_SEPARATOR) { // In case Windows, strtolower drive name, like in UsesCall. - $filename = (string) preg_replace_callback('~^(?P[a-z]+:\\\)~i', fn ($match): string => strtolower($match['drive']), $filename); + $filename = (string) preg_replace_callback('~^(?P[a-z]+:\\\)~i', static fn ($match): string => strtolower($match['drive']), $filename); } $filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename))); @@ -152,7 +123,9 @@ final class TestCaseFactory } $hasPrintableTestCaseClassFQN = sprintf('\%s', HasPrintableTestCaseName::class); - $traitsCode = sprintf('use %s;', implode(', ', array_map(fn ($trait): string => sprintf('\%s', $trait), $this->traits))); + $traitsCode = sprintf('use %s;', implode(', ', array_map( + static fn ($trait): string => sprintf('\%s', $trait), $this->traits)) + ); $partsFQN = explode('\\', $classFQN); $className = array_pop($partsFQN); @@ -164,14 +137,65 @@ final class TestCaseFactory $classFQN .= $className; } + $methodsCode = implode('', array_map(static function (TestCaseMethodFactory $method): string { + $methodName = Str::evaluable($method->description); + + $datasetsCode = ''; + $annotations = ['@test']; + + foreach (self::$annotations as $annotation) { + $annotations = (new $annotation())->add($method, $annotations); + } + + if (!empty($method->datasets)) { + $dataProviderName = $methodName . '_dataset'; + $annotations[] = "@dataProvider $dataProviderName"; + + Datasets::with($method->filename, $methodName, $method->datasets); + + $datasetsCode = << sprintf("\n * %s", $annotation), $annotations, + )); + + return <<__runTest( + \$this->__test, + ...func_get_args(), + ); + } + + $datasetsCode +EOF; + }, $methods)); + try { eval(" namespace $namespace; + use Pest\Datasets as __PestDatasets; + use Pest\TestSuite as __PestTestSuite; + final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN { $traitsCode private static \$__filename = '$filename'; + + $methodsCode } "); } catch (ParseError $caught) { @@ -182,11 +206,40 @@ final class TestCaseFactory } /** - * Determine if the test case will receive argument input from Pest, or not. + * Adds the given Method to the Test Case. */ - public function __receivesArguments(): bool + public function addMethod(TestCaseMethodFactory $method): void { - return count($this->datasets) > 0 - || $this->factoryProxies->count('addDependencies') > 0; + if ($method->description === null) { + throw ShouldNotHappen::fromMessage('The test description may not be empty.'); + } + + if (isset($this->methods[$method->description])) { + throw new TestAlreadyExist($method->filename, $method->description); + } + + if (!$method->receivesArguments()) { + $arguments = Reflection::getFunctionArguments($method->closure); + + if (count($arguments) > 0) { + throw new DatasetMissing($method->filename, $method->description, $arguments); + } + } + + $this->methods[$method->description] = $method; + } + + /** + * Gets a Method by the given name. + */ + public function getMethod(string $methodName): TestCaseMethodFactory + { + foreach ($this->methods as $method) { + if (Str::evaluable($method->description) === $methodName) { + return $method; + } + } + + throw ShouldNotHappen::fromMessage(sprintf('Method %s not found.', $methodName)); } } diff --git a/src/Factories/TestCaseMethodFactory.php b/src/Factories/TestCaseMethodFactory.php new file mode 100644 index 00000000..dd32fceb --- /dev/null +++ b/src/Factories/TestCaseMethodFactory.php @@ -0,0 +1,102 @@ +|string> + */ + public array $datasets = []; + + /** + * The Test Case depends, if any. + * + * @var array + */ + public array $depends = []; + + /** + * The Test Case groups, if any. + * + * @var array + */ + public array $groups = []; + + /** + * Creates a new Factory instance. + */ + public function __construct( + public string $filename, + public ?string $description, + public ?Closure $closure, + ) { + if ($this->closure === null) { + $this->closure = function () { + Assert::getCount() > 0 ?: self::markTestIncomplete(); + }; + } + + $this->bootHigherOrderable(); + } + + /** + * Makes the Test Case classes. + */ + public function getClosure(TestCase $concrete): Closure + { + $concrete::flush(); + + if ($this->description === null) { + throw ShouldNotHappen::fromMessage('Description can not be empty.'); + } + + $closure = $this->closure; + + $testCase = TestSuite::getInstance()->tests->get($this->filename); + + $testCase->factoryProxies->proxy($concrete); + $this->factoryProxies->proxy($concrete); + + $method = $this; + + return function () use ($testCase, $method, $closure): mixed { + $testCase->proxies->proxy($this); + $method->proxies->proxy($this); + + $testCase->chains->chain($this); + $method->chains->chain($this); + + return call_user_func(Closure::bind($closure, $this, $this::class), ...func_get_args()); + }; + } + + /** + * Determine if the test case will receive argument input from Pest, or not. + */ + public function receivesArguments(): bool + { + return count($this->datasets) > 0 || count($this->depends) > 0; + } +} diff --git a/src/IgnorableTestCase.php b/src/IgnorableTestCase.php new file mode 100644 index 00000000..ba4a4bf1 --- /dev/null +++ b/src/IgnorableTestCase.php @@ -0,0 +1,15 @@ +phpunitTeamCity = new BaseTeamCity($out, $verbose, $colors); diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index f6c1300b..25c6426e 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Pest\PendingCalls; use Closure; -use Pest\Factories\TestCaseFactory; +use Pest\Factories\TestCaseMethodFactory; use Pest\Support\Backtrace; use Pest\Support\HigherOrderCallables; use Pest\Support\NullClosure; @@ -22,7 +22,7 @@ final class TestCall /** * The Test Case Factory. */ - private TestCaseFactory $testCaseFactory; + private TestCaseMethodFactory $testCaseMethod; /** * If test call is descriptionLess. @@ -38,7 +38,7 @@ final class TestCall string $description = null, Closure $closure = null ) { - $this->testCaseFactory = new TestCaseFactory($filename, $description, $closure); + $this->testCaseMethod = new TestCaseMethodFactory($filename, $description, $closure); $this->descriptionLess = $description === null; } @@ -48,7 +48,7 @@ final class TestCall public function throws(string $exception, string $exceptionMessage = null): TestCall { if (class_exists($exception)) { - $this->testCaseFactory + $this->testCaseMethod ->proxies ->add(Backtrace::file(), Backtrace::line(), 'expectException', [$exception]); } else { @@ -56,7 +56,7 @@ final class TestCall } if (is_string($exceptionMessage)) { - $this->testCaseFactory + $this->testCaseMethod ->proxies ->add(Backtrace::file(), Backtrace::line(), 'expectExceptionMessage', [$exceptionMessage]); } @@ -90,10 +90,10 @@ final class TestCall * * @param array<\Closure|iterable|string> $data */ - public function with(...$data): TestCall + public function with(Closure|iterable|string ...$data): TestCall { foreach ($data as $dataset) { - $this->testCaseFactory->datasets[] = $dataset; + $this->testCaseMethod->datasets[] = $dataset; } return $this; @@ -102,11 +102,11 @@ final class TestCall /** * Sets the test depends. */ - public function depends(string ...$tests): TestCall + public function depends(string ...$depends): TestCall { - $this->testCaseFactory - ->factoryProxies - ->add(Backtrace::file(), Backtrace::line(), 'addDependencies', [$tests]); + foreach ($depends as $depend) { + $this->testCaseMethod->depends[] = $depend; + } return $this; } @@ -116,7 +116,7 @@ final class TestCall */ public function only(): TestCall { - $this->testCaseFactory->only = true; + $this->testCaseMethod->only = true; return $this; } @@ -126,9 +126,9 @@ final class TestCall */ public function group(string ...$groups): TestCall { - $this->testCaseFactory - ->factoryProxies - ->add(Backtrace::file(), Backtrace::line(), 'addGroups', [$groups]); + foreach ($groups as $group) { + $this->testCaseMethod->groups[] = $group; + } return $this; } @@ -153,7 +153,7 @@ final class TestCall /** @var callable(): bool $condition */ $condition = $condition->bindTo(null); - $this->testCaseFactory + $this->testCaseMethod ->chains ->addWhen($condition, Backtrace::file(), Backtrace::line(), 'markTestSkipped', [$message]); @@ -185,16 +185,16 @@ final class TestCall */ private function addChain(string $name, array $arguments = null): self { - $this->testCaseFactory + $this->testCaseMethod ->chains ->add(Backtrace::file(), Backtrace::line(), $name, $arguments); if ($this->descriptionLess) { $exporter = new Exporter(); - if ($this->testCaseFactory->description !== null) { - $this->testCaseFactory->description .= ' → '; + if ($this->testCaseMethod->description !== null) { + $this->testCaseMethod->description .= ' → '; } - $this->testCaseFactory->description .= $arguments === null + $this->testCaseMethod->description .= $arguments === null ? $name : sprintf('%s %s', $name, $exporter->shortenedRecursiveExport($arguments)); } @@ -207,6 +207,6 @@ final class TestCall */ public function __destruct() { - $this->testSuite->tests->set($this->testCaseFactory); + $this->testSuite->tests->set($this->testCaseMethod); } } diff --git a/src/Pest.php b/src/Pest.php index 56a43e84..e6b0e1f7 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '1.20.0'; + return '2.x-dev'; } function testDirectory(string $file = ''): string diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index 40c2972b..57d8e12c 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -5,16 +5,11 @@ declare(strict_types=1); namespace Pest\Repositories; use Closure; -use Pest\Exceptions\DatasetMissing; -use Pest\Exceptions\ShouldNotHappen; -use Pest\Exceptions\TestAlreadyExist; use Pest\Exceptions\TestCaseAlreadyInUse; use Pest\Exceptions\TestCaseClassOrTraitNotFound; use Pest\Factories\TestCaseFactory; -use Pest\Plugins\Environment; -use Pest\Support\Reflection; +use Pest\Factories\TestCaseMethodFactory; use Pest\Support\Str; -use Pest\TestSuite; use PHPUnit\Framework\TestCase; /** @@ -22,15 +17,10 @@ use PHPUnit\Framework\TestCase; */ final class TestRepository { - /** - * @var non-empty-string - */ - private const SEPARATOR = '>>>'; - /** * @var array */ - private array $state = []; + private array $testCases = []; /** * @var array>> @@ -42,7 +32,7 @@ final class TestRepository */ public function count(): int { - return count($this->state); + return count($this->testCases); } /** @@ -52,74 +42,13 @@ final class TestRepository */ public function getFilenames(): array { - $testsWithOnly = $this->testsUsingOnly(); + $testCases = array_filter($this->testCases, static fn (TestCaseFactory $testCase) => count($testCase->methodsUsingOnly()) > 0); - return array_values(array_map(fn (TestCaseFactory $factory): string => $factory->filename, count($testsWithOnly) > 0 ? $testsWithOnly : $this->state)); - } - - /** - * Calls the given callable foreach test case. - */ - public function build(TestSuite $testSuite, callable $each): void - { - $startsWith = fn (string $target, string $directory): bool => Str::startsWith($target, $directory . DIRECTORY_SEPARATOR); - - foreach ($this->uses as $path => $uses) { - [$classOrTraits, $groups, $hooks] = $uses; - - $setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith, $hooks): void { - [$filename] = explode(self::SEPARATOR, $key); - - if ((!is_dir($path) && $filename === $path) || (is_dir($path) && $startsWith($filename, $path))) { - foreach ($classOrTraits as $class) { /** @var string $class */ - if (class_exists($class)) { - if ($testCase->class !== TestCase::class) { - throw new TestCaseAlreadyInUse($testCase->class, $class, $filename); - } - $testCase->class = $class; - } elseif (trait_exists($class)) { - $testCase->traits[] = $class; - } - } - - $testCase->factoryProxies->add($filename, 0, 'addGroups', [$groups]); - $testCase->factoryProxies->add($filename, 0, '__addBeforeAll', [$hooks[0] ?? null]); - $testCase->factoryProxies->add($filename, 0, '__addBeforeEach', [$hooks[1] ?? null]); - $testCase->factoryProxies->add($filename, 0, '__addAfterEach', [$hooks[2] ?? null]); - $testCase->factoryProxies->add($filename, 0, '__addAfterAll', [$hooks[3] ?? null]); - } - }; - - foreach ($this->state as $key => $test) { - $setClassName($test, $key); - } + if (count($testCases) === 0) { + $testCases = $this->testCases; } - $onlyState = $this->testsUsingOnly(); - - $state = count($onlyState) > 0 ? $onlyState : $this->state; - - foreach ($state as $testFactory) { - /** @var TestCaseFactory $testFactory */ - $tests = $testFactory->make($testSuite); - foreach ($tests as $test) { - $each($test); - } - } - } - - /** - * Return all tests that have called the only method. - * - * @return array - */ - private function testsUsingOnly(): array - { - if (Environment::name() === Environment::CI) { - return []; - } - - return array_filter($this->state, fn ($testFactory): bool => $testFactory->only); + return array_values(array_map(static fn (TestCaseFactory $factory): string => $factory->filename, $testCases)); } /** @@ -151,27 +80,73 @@ final class TestRepository } } - /** - * Sets a test case by the given filename and description. - */ - public function set(TestCaseFactory $test): void + public function get($filename): TestCaseFactory { - if ($test->description === null) { - throw ShouldNotHappen::fromMessage('Trying to create a test without description.'); + return $this->testCases[$filename]; + } + + /** + * Sets a new test case method. + */ + public function set(TestCaseMethodFactory $method): void + { + if (!isset($this->testCases[$method->filename])) { + $this->testCases[$method->filename] = new TestCaseFactory($method->filename); } - if (array_key_exists(sprintf('%s%s%s', $test->filename, self::SEPARATOR, $test->description), $this->state)) { - throw new TestAlreadyExist($test->filename, $test->description); + $this->testCases[$method->filename]->addMethod($method); + } + + /** + * Makes a Test Case from the given filename, if exists. + */ + public function makeIfExists(string $filename): void + { + if (isset($this->testCases[$filename])) { + $this->make($this->testCases[$filename]); } + } - if (!$test->__receivesArguments()) { - $arguments = Reflection::getFunctionArguments($test->test); + /** + * Makes a Test Case using the given factory. + */ + private function make(TestCaseFactory $testCase): void + { + $startsWith = static fn (string $target, string $directory): bool => Str::startsWith($target, $directory . DIRECTORY_SEPARATOR); - if (count($arguments) > 0) { - throw new DatasetMissing($test->filename, $test->description, $arguments); + foreach ($this->uses as $path => $uses) { + [$classOrTraits, $groups, $hooks] = $uses; + + if ((!is_dir($path) && $testCase->filename === $path) || (is_dir($path) && $startsWith($testCase->filename, $path))) { + foreach ($classOrTraits as $class) { + /** @var string $class */ + if (class_exists($class)) { + if ($testCase->class !== TestCase::class) { + throw new TestCaseAlreadyInUse($testCase->class, $class, $testCase->filename); + } + $testCase->class = $class; + } elseif (trait_exists($class)) { + $testCase->traits[] = $class; + } + } + + foreach ($testCase->methods as $method) { + foreach ($groups as $group) { + $method->groups[] = $group; + } + } + + foreach ($testCase->methods as $method) { + $method->groups = array_merge($groups, $method->groups); + } + + $testCase->factoryProxies->add($testCase->filename, 0, '__addBeforeAll', [$hooks[0] ?? null]); + $testCase->factoryProxies->add($testCase->filename, 0, '__addBeforeEach', [$hooks[1] ?? null]); + $testCase->factoryProxies->add($testCase->filename, 0, '__addAfterEach', [$hooks[2] ?? null]); + $testCase->factoryProxies->add($testCase->filename, 0, '__addAfterAll', [$hooks[3] ?? null]); } } - $this->state[sprintf('%s%s%s', $test->filename, self::SEPARATOR, $test->description)] = $test; + $testCase->make(); } } diff --git a/src/Subscribers/EnsureTestsAreLoaded.php b/src/Subscribers/EnsureTestsAreLoaded.php index 12396941..23954c24 100644 --- a/src/Subscribers/EnsureTestsAreLoaded.php +++ b/src/Subscribers/EnsureTestsAreLoaded.php @@ -25,6 +25,8 @@ final class EnsureTestsAreLoaded implements LoadedSubscriber */ public function notify(Loaded $event): void { + /* + $this->removeWarnings(self::$testSuite); $testSuites = []; @@ -47,6 +49,7 @@ final class EnsureTestsAreLoaded implements LoadedSubscriber } self::$testSuite->addTestSuite($testTestSuite); } + */ } /** diff --git a/src/Support/HigherOrderMessage.php b/src/Support/HigherOrderMessage.php index 99f444f0..1df45e4e 100644 --- a/src/Support/HigherOrderMessage.php +++ b/src/Support/HigherOrderMessage.php @@ -54,8 +54,7 @@ final class HigherOrderMessage try { return is_array($this->arguments) ? Reflection::call($target, $this->name, $this->arguments) - : $target->{$this->name}; - /* @phpstan-ignore-line */ + : $target->{$this->name}; /* @phpstan-ignore-line */ } catch (Throwable $throwable) { Reflection::setPropertyValue($throwable, 'file', $this->filename); Reflection::setPropertyValue($throwable, 'line', $this->line); diff --git a/src/Support/Str.php b/src/Support/Str.php index a3b7a3ab..a7217704 100644 --- a/src/Support/Str.php +++ b/src/Support/Str.php @@ -48,4 +48,14 @@ final class Str return substr($target, -$length) === $search; } + + /** + * Makes the given string evaluable by an `eval`. + */ + public static function evaluable(string $code): string + { + $code = str_replace(' ', '_', $code); + + return (string) preg_replace('/[^A-Z_a-z0-9\\\\]/', '', $code); + } } diff --git a/tests/Features/Datasets.php b/tests/Features/Datasets.php index be94e596..753c4eaf 100644 --- a/tests/Features/Datasets.php +++ b/tests/Features/Datasets.php @@ -12,7 +12,8 @@ beforeEach(function () { it('throws exception if dataset does not exist', function () { $this->expectException(DatasetDoesNotExist::class); $this->expectExceptionMessage("A dataset with the name `first` does not exist. You can create it using `dataset('first', ['a', 'b']);`."); - Datasets::get('first'); + + Datasets::resolve('foo', ['first']); }); it('throws exception if dataset already exist', function () { @@ -27,13 +28,13 @@ it('sets closures', function () { yield [1]; }); - expect(iterator_to_array(Datasets::get('foo')()))->toBe([[1]]); + expect(Datasets::resolve('foo', ['foo']))->toBe(['foo with (1)' => [1]]); }); it('sets arrays', function () { Datasets::set('bar', [[2]]); - expect(Datasets::get('bar'))->toBe([[2]]); + expect(Datasets::resolve('bar', ['bar']))->toBe(['bar with (2)' => [2]]); }); it('gets bound to test case object', function () { @@ -52,6 +53,7 @@ $datasets = [[1], [2]]; test('lazy datasets', function ($text) use ($state, $datasets) { $state->text .= $text; + expect(in_array([$text], $datasets))->toBe(true); })->with($datasets); diff --git a/tests/PHPUnit/CustomTestCase/CustomTestCase.php b/tests/PHPUnit/CustomTestCase/CustomTestCase.php index 2eb0ad4e..8a71327b 100644 --- a/tests/PHPUnit/CustomTestCase/CustomTestCase.php +++ b/tests/PHPUnit/CustomTestCase/CustomTestCase.php @@ -7,7 +7,7 @@ namespace Tests\CustomTestCase; use function PHPUnit\Framework\assertTrue; use PHPUnit\Framework\TestCase; -class CustomTestCase extends TestCase +abstract class CustomTestCase extends TestCase { public function assertCustomTrue() { diff --git a/tests/Unit/TestSuite.php b/tests/Unit/TestSuite.php index 84212879..a15ad631 100644 --- a/tests/Unit/TestSuite.php +++ b/tests/Unit/TestSuite.php @@ -2,53 +2,55 @@ use Pest\Exceptions\DatasetMissing; use Pest\Exceptions\TestAlreadyExist; -use Pest\Factories\TestCaseFactory; +use Pest\Factories\TestCaseMethodFactory; use Pest\Plugins\Environment; use Pest\TestSuite; it('does not allow to add the same test description twice', function () { $testSuite = new TestSuite(getcwd(), 'tests'); - $test = function () {}; - $testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test)); - $testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test)); + $method = new TestCaseMethodFactory('foo', 'bar', null); + + $testSuite->tests->set($method); + $testSuite->tests->set($method); })->throws( TestAlreadyExist::class, - sprintf('A test with the description `%s` already exist in the filename `%s`.', 'foo', __FILE__), + sprintf('A test with the description `%s` already exist in the filename `%s`.', 'bar', 'foo'), ); it('alerts users about tests with arguments but no input', function () { $testSuite = new TestSuite(getcwd(), 'tests'); - $test = function (int $arg) {}; - $testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test)); + + $method = new TestCaseMethodFactory('foo', 'bar', function (int $arg) {}); + + $testSuite->tests->set($method); })->throws( DatasetMissing::class, - sprintf("A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s", 'foo', 1, 'int $arg', __FILE__), + sprintf("A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s", 'bar', 1, 'int $arg', 'foo'), ); it('can return an array of all test suite filenames', function () { $testSuite = TestSuite::getInstance(getcwd(), 'tests'); - $test = function () {}; - $testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test)); - $testSuite->tests->set(new TestCaseFactory(__FILE__, 'bar', $test)); + + $testSuite->tests->set(new TestCaseMethodFactory('a', 'b', null)); + $testSuite->tests->set(new TestCaseMethodFactory('c', 'd', null)); expect($testSuite->tests->getFilenames())->toEqual([ - __FILE__, - __FILE__, + 'a', + 'c', ]); }); it('can filter the test suite filenames to those with the only method', function () { $testSuite = new TestSuite(getcwd(), 'tests'); - $test = function () {}; - $testWithOnly = new TestCaseFactory(__FILE__, 'foo', $test); + $testWithOnly = new TestCaseMethodFactory('a', 'b', null); $testWithOnly->only = true; $testSuite->tests->set($testWithOnly); - $testSuite->tests->set(new TestCaseFactory('Baz/Bar/Boo.php', 'bar', $test)); + $testSuite->tests->set(new TestCaseMethodFactory('c', 'd', null)); expect($testSuite->tests->getFilenames())->toEqual([ - __FILE__, + 'a', ]); }); @@ -59,15 +61,15 @@ it('does not filter the test suite filenames to those with the only method when $test = function () {}; - $testWithOnly = new TestCaseFactory(__FILE__, 'foo', $test); + $testWithOnly = new TestCaseMethodFactory('a', 'b', null); $testWithOnly->only = true; $testSuite->tests->set($testWithOnly); - $testSuite->tests->set(new TestCaseFactory('Baz/Bar/Boo.php', 'bar', $test)); + $testSuite->tests->set(new TestCaseMethodFactory('c', 'd', null)); expect($testSuite->tests->getFilenames())->toEqual([ - __FILE__, - 'Baz/Bar/Boo.php', + 'a', + 'c', ]); Environment::name($previousEnvironment); From 183f97516678c528a5af936623df1c97435f5f49 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 14 Nov 2021 21:23:02 +0000 Subject: [PATCH 15/17] chore: phpstan level 5 --- .github/workflows/tests.yml | 5 + composer.json | 10 +- phpstan.neon | 13 +- src/Bootstrappers/BootSubscribers.php | 1 - src/Datasets.php | 4 +- src/Factories/TestCaseMethodFactory.php | 9 +- src/Functions.php | 24 +- src/Kernel.php | 2 +- src/Logging/JUnit.php | 373 +------------------ src/Logging/TeamCity.php | 282 +------------- src/OppositeExpectation.php | 12 +- src/PendingCalls/TestCall.php | 2 +- src/Plugins/Actions/AddsOutput.php | 4 +- src/Plugins/Actions/HandleArguments.php | 2 + src/Repositories/AfterEachRepository.php | 1 - src/Repositories/TestRepository.php | 4 +- src/Subscribers/EnsureTestsAreLoaded.php | 82 ---- src/Support/ChainableClosure.php | 10 +- src/Support/Container.php | 1 - src/Support/HigherOrderCallables.php | 2 +- src/Support/HigherOrderMessage.php | 7 +- src/Support/HigherOrderMessageCollection.php | 4 +- src/Support/HigherOrderTapProxy.php | 7 +- 23 files changed, 65 insertions(+), 796 deletions(-) delete mode 100644 src/Subscribers/EnsureTestsAreLoaded.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 41654677..fce3d953 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,8 +38,13 @@ jobs: - name: Install PHP dependencies 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 run: php bin/pest --colors=always --exclude-group=integration ${{ matrix.parallel }} + if: ${{ false }} # 2.x-dev is under development - name: Integration Tests run: php bin/pest --colors=always --group=integration + if: ${{ false }} # 2.x-dev is under development diff --git a/composer.json b/composer.json index 1b8e7aaa..c8a602ee 100644 --- a/composer.json +++ b/composer.json @@ -64,15 +64,13 @@ "test:lint": "php-cs-fixer fix -v --dry-run", "test:types": "phpstan analyse --ansi --memory-limit=-1", "test:unit": "php bin/pest --colors=always --exclude-group=integration", - "test:parallel": "php bin/pest -p --colors=always --exclude-group=integration", - "test:integration": "php bin/pest --colors=always --group=integration", - "update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always", + "test:parallel": "exit 1", + "test:integration": "exit 1", + "update:snapshots": "exit 1", "test": [ "@test:lint", "@test:types", - "@test:unit", - "@test:parallel", - "@test:integration" + "@test:unit" ] }, "extra": { diff --git a/phpstan.neon b/phpstan.neon index a32cf76a..d5dc4ef6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -13,6 +13,7 @@ parameters: reportUnmatchedIgnoredErrors: true ignoreErrors: + - "#with a nullable type declaration#" - "#type mixed is not subtype of native#" - "#is not allowed to extend#" - "#Language construct eval#" @@ -20,15 +21,3 @@ parameters: - "#has parameter \\$closure with default value.#" - "#has parameter \\$description with default value.#" - "#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 diff --git a/src/Bootstrappers/BootSubscribers.php b/src/Bootstrappers/BootSubscribers.php index a3ef3dc5..c23e9bf0 100644 --- a/src/Bootstrappers/BootSubscribers.php +++ b/src/Bootstrappers/BootSubscribers.php @@ -18,7 +18,6 @@ final class BootSubscribers * @var array */ private static array $subscribers = [ - Subscribers\EnsureTestsAreLoaded::class, Subscribers\EnsureConfigurationIsValid::class, Subscribers\EnsureConfigurationDefaults::class, ]; diff --git a/src/Datasets.php b/src/Datasets.php index 38cc6662..0cf59d60 100644 --- a/src/Datasets.php +++ b/src/Datasets.php @@ -46,7 +46,7 @@ final class Datasets /** * Sets the given. * - * @param Closure|iterable $data + * @param Closure|iterable|string $with */ public static function with(string $filename, string $description, Closure|iterable|string $with): void { @@ -129,7 +129,7 @@ final class Datasets $processedDataset = []; if (is_string($data)) { - if (!isset(self::$datasets[$data])) { + if (! array_key_exists($data, self::$datasets)) { throw new DatasetDoesNotExist($data); } diff --git a/src/Factories/TestCaseMethodFactory.php b/src/Factories/TestCaseMethodFactory.php index dd32fceb..1c16d5f6 100644 --- a/src/Factories/TestCaseMethodFactory.php +++ b/src/Factories/TestCaseMethodFactory.php @@ -17,7 +17,6 @@ use PHPUnit\Framework\TestCase; final class TestCaseMethodFactory { use HigherOrderable; - /** * Determines if the Test Case will be the "only" being run. */ @@ -54,7 +53,7 @@ final class TestCaseMethodFactory ) { if ($this->closure === null) { $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 { - $concrete::flush(); + $concrete::flush(); // @phpstan-ignore-line if ($this->description === null) { throw ShouldNotHappen::fromMessage('Description can not be empty.'); @@ -81,7 +80,9 @@ final class TestCaseMethodFactory $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); $method->proxies->proxy($this); diff --git a/src/Functions.php b/src/Functions.php index 20a0ee6e..1e306999 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -14,18 +14,20 @@ use Pest\Support\HigherOrderTapProxy; use Pest\TestSuite; use PHPUnit\Framework\TestCase; -/** - * Creates a new expectation. - * - * @param mixed $value the Value - */ -function expect($value = null): Expectation|Extendable -{ - if (func_num_args() === 0) { - return new Extendable(Expectation::class); - } +if (!function_exists('expect')) { + /** + * Creates a new expectation. + * + * @param mixed $value the Value + */ + function expect($value = null): Expectation|Extendable + { + if (func_num_args() === 0) { + return new Extendable(Expectation::class); + } - return new Expectation($value); + return new Expectation($value); + } } if (!function_exists('beforeAll')) { diff --git a/src/Kernel.php b/src/Kernel.php index ff4eba3a..af2d82e6 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -64,6 +64,6 @@ final class Kernel */ public function shutdown(): void { - // TODO + // .. } } diff --git a/src/Logging/JUnit.php b/src/Logging/JUnit.php index 14595bbc..91921b31 100644 --- a/src/Logging/JUnit.php +++ b/src/Logging/JUnit.php @@ -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 - */ - 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 } diff --git a/src/Logging/TeamCity.php b/src/Logging/TeamCity.php index 76093f38..005cbd24 100644 --- a/src/Logging/TeamCity.php +++ b/src/Logging/TeamCity.php @@ -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 $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 $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 $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 $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 } diff --git a/src/OppositeExpectation.php b/src/OppositeExpectation.php index 7a17162c..e56eabd3 100644 --- a/src/OppositeExpectation.php +++ b/src/OppositeExpectation.php @@ -46,6 +46,8 @@ final class OppositeExpectation * Handle dynamic method calls into the original expectation. * * @param array $arguments + * + * @return Expectation|never */ public function __call(string $name, array $arguments): Expectation { @@ -56,23 +58,23 @@ final class OppositeExpectation return $this->original; } - // @phpstan-ignore-next-line $this->throwExpectationFailedException($name, $arguments); } /** * Handle dynamic properties gets into the original expectation. + * + * @return Expectation|never */ public function __get(string $name): Expectation { try { - /* @phpstan-ignore-next-line */ - $this->original->{$name}; + /** @throws ExpectationFailedException */ + $this->original->{$name}; // @phpstan-ignore-line } catch (ExpectationFailedException) { return $this->original; } - // @phpstan-ignore-next-line $this->throwExpectationFailedException($name); } @@ -80,6 +82,8 @@ final class OppositeExpectation * Creates a new expectation failed exception with a nice readable message. * * @param array $arguments + * + * @return never */ private function throwExpectationFailedException(string $name, array $arguments = []): void { diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 25c6426e..0d1cd17c 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -74,7 +74,7 @@ final class TestCall $condition = is_callable($condition) ? $condition : static function () use ($condition): bool { - return $condition; // @phpstan-ignore-line + return $condition; }; if ($condition()) { diff --git a/src/Plugins/Actions/AddsOutput.php b/src/Plugins/Actions/AddsOutput.php index 967ff0a2..fefac940 100644 --- a/src/Plugins/Actions/AddsOutput.php +++ b/src/Plugins/Actions/AddsOutput.php @@ -1,5 +1,7 @@ addOutput($exitCode); } diff --git a/src/Plugins/Actions/HandleArguments.php b/src/Plugins/Actions/HandleArguments.php index f0369d01..e26a9baa 100644 --- a/src/Plugins/Actions/HandleArguments.php +++ b/src/Plugins/Actions/HandleArguments.php @@ -1,5 +1,7 @@ addToAssertionCount($container->mockery_getExpectationCount()); diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index 57d8e12c..5ab98700 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -90,7 +90,7 @@ final class TestRepository */ 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); } @@ -102,7 +102,7 @@ final class TestRepository */ public function makeIfExists(string $filename): void { - if (isset($this->testCases[$filename])) { + if (array_key_exists($filename, $this->testCases)) { $this->make($this->testCases[$filename]); } } diff --git a/src/Subscribers/EnsureTestsAreLoaded.php b/src/Subscribers/EnsureTestsAreLoaded.php deleted file mode 100644 index 23954c24..00000000 --- a/src/Subscribers/EnsureTestsAreLoaded.php +++ /dev/null @@ -1,82 +0,0 @@ -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)); - } -} diff --git a/src/Support/ChainableClosure.php b/src/Support/ChainableClosure.php index 5ce0f756..b37e4f5f 100644 --- a/src/Support/ChainableClosure.php +++ b/src/Support/ChainableClosure.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace Pest\Support; use Closure; +use Pest\Exceptions\ShouldNotHappen; +use PHPUnit\Framework\TestCase; /** * @internal @@ -17,9 +19,11 @@ final class ChainableClosure public static function from(Closure $closure, Closure $next): Closure { 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()); - /* @phpstan-ignore-next-line */ 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 { return static function () use ($closure, $next): void { - /* @phpstan-ignore-next-line */ 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()); }; } diff --git a/src/Support/Container.php b/src/Support/Container.php index 6be1a4e1..6390484b 100644 --- a/src/Support/Container.php +++ b/src/Support/Container.php @@ -63,7 +63,6 @@ final class Container */ private function build(string $id): object { - /** @phpstan-ignore-next-line */ $reflectionClass = new ReflectionClass($id); if ($reflectionClass->isInstantiable()) { diff --git a/src/Support/HigherOrderCallables.php b/src/Support/HigherOrderCallables.php index ea032a88..1637ccb2 100644 --- a/src/Support/HigherOrderCallables.php +++ b/src/Support/HigherOrderCallables.php @@ -25,7 +25,7 @@ final class HigherOrderCallables * * 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 */ diff --git a/src/Support/HigherOrderMessage.php b/src/Support/HigherOrderMessage.php index 1df45e4e..f756c64d 100644 --- a/src/Support/HigherOrderMessage.php +++ b/src/Support/HigherOrderMessage.php @@ -18,14 +18,14 @@ final class HigherOrderMessage /** * 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. * - * @param array|null $arguments + * @param array $arguments */ public function __construct( public string $filename, @@ -41,7 +41,6 @@ final class HigherOrderMessage */ public function call(object $target): mixed { - /* @phpstan-ignore-next-line */ if (is_callable($this->condition) && call_user_func(Closure::bind($this->condition, $target)) === false) { return $target; } diff --git a/src/Support/HigherOrderMessageCollection.php b/src/Support/HigherOrderMessageCollection.php index b0adf7b4..cb69a3a9 100644 --- a/src/Support/HigherOrderMessageCollection.php +++ b/src/Support/HigherOrderMessageCollection.php @@ -19,7 +19,7 @@ final class HigherOrderMessageCollection * * @param array|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); } @@ -29,7 +29,7 @@ final class HigherOrderMessageCollection * * @param array|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); } diff --git a/src/Support/HigherOrderTapProxy.php b/src/Support/HigherOrderTapProxy.php index f76026ae..bfce21c8 100644 --- a/src/Support/HigherOrderTapProxy.php +++ b/src/Support/HigherOrderTapProxy.php @@ -31,8 +31,7 @@ final class HigherOrderTapProxy */ public function __set(string $property, $value): void { - // @phpstan-ignore-next-line - $this->target->{$property} = $value; + $this->target->{$property} = $value; // @phpstan-ignore-line } /** @@ -43,8 +42,8 @@ final class HigherOrderTapProxy public function __get(string $property) { try { - // @phpstan-ignore-next-line - return $this->target->{$property}; + /** @throws Throwable */ + return $this->target->{$property}; // @phpstan-ignore-line } catch (Throwable $throwable) { Reflection::setPropertyValue($throwable, 'file', Backtrace::file()); Reflection::setPropertyValue($throwable, 'line', Backtrace::line()); From b2cd60395f8c0c75335ed769975b499e7f83beb7 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 14 Nov 2021 21:39:24 +0000 Subject: [PATCH 16/17] chore: phpstan level 5 --- composer.json | 2 +- phpstan.neon | 2 +- src/Datasets.php | 2 +- src/Expectation.php | 16 +++++++--------- src/Factories/TestCaseFactory.php | 14 ++++++++------ src/Factories/TestCaseMethodFactory.php | 2 +- src/Logging/JUnit.php | 22 ---------------------- src/OppositeExpectation.php | 3 +-- src/Repositories/TestRepository.php | 2 +- src/Support/ChainableClosure.php | 3 +-- src/Support/HigherOrderTapProxy.php | 5 ++--- 11 files changed, 24 insertions(+), 49 deletions(-) diff --git a/composer.json b/composer.json index c8a602ee..8b4bdf03 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,7 @@ "scripts": { "lint": "php-cs-fixer fix -v", "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 --debug", "test:unit": "php bin/pest --colors=always --exclude-group=integration", "test:parallel": "exit 1", "test:integration": "exit 1", diff --git a/phpstan.neon b/phpstan.neon index d5dc4ef6..c1a0e56e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,7 +4,7 @@ includes: - vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon parameters: - level: max + level: 5 paths: - src diff --git a/src/Datasets.php b/src/Datasets.php index 0cf59d60..429de157 100644 --- a/src/Datasets.php +++ b/src/Datasets.php @@ -129,7 +129,7 @@ final class Datasets $processedDataset = []; if (is_string($data)) { - if (! array_key_exists($data, self::$datasets)) { + if (!array_key_exists($data, self::$datasets)) { throw new DatasetDoesNotExist($data); } diff --git a/src/Expectation.php b/src/Expectation.php index 28ea26af..920a5f70 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -74,11 +74,9 @@ final class Expectation /** * Dump the expectation value and end the script. * - * @param mixed $arguments - * * @return never */ - public function dd(...$arguments): void + public function dd(mixed ...$arguments): void { if (function_exists('dd')) { dd($this->value, ...$arguments); @@ -91,13 +89,10 @@ final class Expectation /** * Send the expectation value to Ray along with all given arguments. - * - * @param ...mixed $arguments */ public function ray(mixed ...$arguments): self { if (function_exists('ray')) { - // @phpstan-ignore-next-line ray($this->value, ...$arguments); } @@ -224,7 +219,7 @@ final class Expectation $condition = is_callable($condition) ? $condition : static function () use ($condition): bool { - return $condition; // @phpstan-ignore-line + return $condition; }; return $this->when(!$condition(), $callback); @@ -241,7 +236,7 @@ final class Expectation $condition = is_callable($condition) ? $condition : static function () use ($condition): bool { - return $condition; // @phpstan-ignore-line + return $condition; }; if ($condition()) { @@ -371,6 +366,8 @@ final class Expectation /** * Asserts that the value starts with $expected. + * + * @param non-empty-string $expected */ public function toStartWith(string $expected): Expectation { @@ -381,6 +378,8 @@ final class Expectation /** * Asserts that the value ends with $expected. + * + * @param non-empty-string $expected */ public function toEndWith(string $expected): Expectation { @@ -526,7 +525,6 @@ final class Expectation */ public function toBeInstanceOf(string $class): Expectation { - /* @phpstan-ignore-next-line */ Assert::assertInstanceOf($class, $this->value); return $this; diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index 8882a0da..d21b5513 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -71,11 +71,13 @@ final class TestCaseFactory public function make(): void { - $methods = array_filter($this->methods, function ($method) { - return count($onlyTestCases = $this->methodsUsingOnly()) === 0 || in_array($method, $onlyTestCases, true); + $methodsUsingOnly = $this->methodsUsingOnly(); + + $methods = array_filter($this->methods, function ($method) use ($methodsUsingOnly) { + return count($methodsUsingOnly) === 0 || in_array($method, $methodsUsingOnly, true); }); - if (count($this->methods) > 0) { + if (count($methods) > 0) { $this->evaluate($this->filename, $methods); } } @@ -91,7 +93,7 @@ final class TestCaseFactory return []; } - return array_filter($this->methods, static fn ($method): bool => $method->only); + return array_values(array_filter($this->methods, static fn ($method): bool => $method->only)); } /** @@ -147,7 +149,7 @@ final class TestCaseFactory $annotations = (new $annotation())->add($method, $annotations); } - if (!empty($method->datasets)) { + if (count($method->datasets) > 0) { $dataProviderName = $methodName . '_dataset'; $annotations[] = "@dataProvider $dataProviderName"; @@ -214,7 +216,7 @@ EOF; throw ShouldNotHappen::fromMessage('The test description may not be empty.'); } - if (isset($this->methods[$method->description])) { + if (array_key_exists($method->description, $this->methods)) { throw new TestAlreadyExist($method->filename, $method->description); } diff --git a/src/Factories/TestCaseMethodFactory.php b/src/Factories/TestCaseMethodFactory.php index 1c16d5f6..ffa4cb4b 100644 --- a/src/Factories/TestCaseMethodFactory.php +++ b/src/Factories/TestCaseMethodFactory.php @@ -81,7 +81,7 @@ final class TestCaseMethodFactory $method = $this; return function () use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line - /** @var TestCase $this */ + /* @var TestCase $this */ $testCase->proxies->proxy($this); $method->proxies->proxy($this); diff --git a/src/Logging/JUnit.php b/src/Logging/JUnit.php index 91921b31..207912ac 100644 --- a/src/Logging/JUnit.php +++ b/src/Logging/JUnit.php @@ -12,29 +12,7 @@ declare(strict_types=1); namespace Pest\Logging; -use function class_exists; -use DOMDocument; -use DOMElement; -use Exception; -use function method_exists; -use Pest\Concerns\Testable; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\ExceptionWrapper; -use PHPUnit\Framework\SelfDescribing; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Util\Filter; use PHPUnit\Util\Printer; -use PHPUnit\Util\Xml; -use ReflectionClass; -use ReflectionException; -use function sprintf; -use function str_replace; -use Throwable; -use function trim; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit diff --git a/src/OppositeExpectation.php b/src/OppositeExpectation.php index e56eabd3..e4ec3d85 100644 --- a/src/OppositeExpectation.php +++ b/src/OppositeExpectation.php @@ -69,9 +69,8 @@ final class OppositeExpectation public function __get(string $name): Expectation { try { - /** @throws ExpectationFailedException */ $this->original->{$name}; // @phpstan-ignore-line - } catch (ExpectationFailedException) { + } catch (ExpectationFailedException) { // @phpstan-ignore-line return $this->original; } diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index 5ab98700..b8e2b53e 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -90,7 +90,7 @@ final class TestRepository */ public function set(TestCaseMethodFactory $method): void { - if (! array_key_exists($method->filename, $this->testCases)) { + if (!array_key_exists($method->filename, $this->testCases)) { $this->testCases[$method->filename] = new TestCaseFactory($method->filename); } diff --git a/src/Support/ChainableClosure.php b/src/Support/ChainableClosure.php index b37e4f5f..8a136682 100644 --- a/src/Support/ChainableClosure.php +++ b/src/Support/ChainableClosure.php @@ -6,7 +6,6 @@ namespace Pest\Support; use Closure; use Pest\Exceptions\ShouldNotHappen; -use PHPUnit\Framework\TestCase; /** * @internal @@ -19,7 +18,7 @@ final class ChainableClosure public static function from(Closure $closure, Closure $next): Closure { return function () use ($closure, $next): void { - if (! is_object($this)) { // @phpstan-ignore-line + if (!is_object($this)) { // @phpstan-ignore-line throw ShouldNotHappen::fromMessage('$this not bound to chainable closure.'); } diff --git a/src/Support/HigherOrderTapProxy.php b/src/Support/HigherOrderTapProxy.php index bfce21c8..9bcfb479 100644 --- a/src/Support/HigherOrderTapProxy.php +++ b/src/Support/HigherOrderTapProxy.php @@ -13,7 +13,7 @@ use Throwable; */ final class HigherOrderTapProxy { - private const UNDEFINED_PROPERTY = 'Undefined property: P\\'; + private const UNDEFINED_PROPERTY = 'Undefined property: P\\'; // @phpstan-ignore-line /** * Create a new tap proxy instance. @@ -42,9 +42,8 @@ final class HigherOrderTapProxy public function __get(string $property) { try { - /** @throws Throwable */ return $this->target->{$property}; // @phpstan-ignore-line - } catch (Throwable $throwable) { + } catch (Throwable $throwable) { // @phpstan-ignore-line Reflection::setPropertyValue($throwable, 'file', Backtrace::file()); Reflection::setPropertyValue($throwable, 'line', Backtrace::line()); From f6004e07c1175711cdaf60af4f5988708b1b909f Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 14 Nov 2021 21:45:13 +0000 Subject: [PATCH 17/17] chore: ignores windows builds --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fce3d953..9245e846 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest] # (macos-latest, windows-latest) 2.x-dev is under development php: ['8.0', '8.1'] dependency-version: [prefer-lowest, prefer-stable] parallel: ['', '--parallel']