diff --git a/README.md b/README.md index eb08e57f..b63608fe 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,5 @@ We would like to extend our thanks to the following sponsors for funding Pest de - [Hyvor](https://hyvor.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)**. diff --git a/bin/pest b/bin/pest index e99c09bb..9f51fa08 100755 --- a/bin/pest +++ b/bin/pest @@ -22,12 +22,12 @@ use Symfony\Component\Console\Output\OutputInterface; $todo = false; foreach ($args as $key => $value) { - if (str_contains($value, '--compact')) { + if ($value === '--compact') { $_SERVER['COLLISION_PRINTER_COMPACT'] = 'true'; unset($args[$key]); } - if (str_contains($value, '--profile')) { + if ($value === '--profile') { $_SERVER['COLLISION_PRINTER_PROFILE'] = 'true'; unset($args[$key]); } @@ -36,12 +36,12 @@ use Symfony\Component\Console\Output\OutputInterface; unset($args[$key]); } - if (str_contains($value, '--dirty')) { + if ($value === '--dirty') { $dirty = true; unset($args[$key]); } - if (str_contains($value, '--todo')) { + if ($value === '--todo') { $todo = true; unset($args[$key]); } diff --git a/composer.json b/composer.json index 98bc3522..aef95ae8 100644 --- a/composer.json +++ b/composer.json @@ -18,10 +18,10 @@ ], "require": { "php": "^8.1.0", - "nunomaduro/collision": "^7.0.5", + "nunomaduro/collision": "^7.1.0", "nunomaduro/termwind": "^1.15.1", "pestphp/pest-plugin": "^2.0.0", - "phpunit/phpunit": "^10.0.12" + "phpunit/phpunit": "^10.0.15" }, "conflict": { "brianium/paratest": "<7.0.6" @@ -46,10 +46,10 @@ ] }, "require-dev": { - "brianium/paratest": "^7.0.6", + "brianium/paratest": "^7.1.0", "pestphp/pest-dev-tools": "^2.4.0", "pestphp/pest-plugin-arch": "^2.0.0", - "symfony/process": "^6.2.5" + "symfony/process": "^6.2.7" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/overrides/Runner/TestSuiteLoader.php b/overrides/Runner/TestSuiteLoader.php index efc50b04..d997aeb0 100644 --- a/overrides/Runner/TestSuiteLoader.php +++ b/overrides/Runner/TestSuiteLoader.php @@ -128,6 +128,27 @@ final class TestSuiteLoader } } + if (! $testCaseFound) { + foreach (array_reverse($loadedClasses) as $loadedClass) { + $offset = 0 - strlen($suiteClassName); + + if (stripos(substr($loadedClass, $offset - 1), '\\'.$suiteClassName) === 0 || + stripos(substr($loadedClass, $offset - 1), '_'.$suiteClassName) === 0) { + try { + $class = new ReflectionClass($loadedClass); + // @codeCoverageIgnoreStart + } catch (ReflectionException) { + continue; + } + + $suiteClassName = $loadedClass; + $testCaseFound = true; + + break; + } + } + } + if (! $testCaseFound) { return $this->exceptionFor($suiteClassName, $suiteClassFile); } diff --git a/overrides/TextUI/Command/WarmCodeCoverageCacheCommand.php b/overrides/TextUI/Command/WarmCodeCoverageCacheCommand.php new file mode 100644 index 00000000..7137b5b8 --- /dev/null +++ b/overrides/TextUI/Command/WarmCodeCoverageCacheCommand.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PHPUnit\TextUI\Command; + +use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\NoCoverageCacheDirectoryException; +use SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer; +use SebastianBergmann\Timer\NoActiveTimerException; +use SebastianBergmann\Timer\Timer; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class WarmCodeCoverageCacheCommand implements Command +{ + private readonly Configuration $configuration; + + private readonly CodeCoverageFilterRegistry $codeCoverageFilterRegistry; + + public function __construct(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry) + { + $this->configuration = $configuration; + $this->codeCoverageFilterRegistry = $codeCoverageFilterRegistry; + } + + /** + * @throws NoActiveTimerException + * @throws NoCoverageCacheDirectoryException + */ + public function execute(): Result + { + if (! $this->configuration->hasCoverageCacheDirectory()) { + return Result::from( + 'Cache for static analysis has not been configured'.PHP_EOL, + Result::FAILURE + ); + } + + $this->codeCoverageFilterRegistry->init($this->configuration); + + if (! $this->codeCoverageFilterRegistry->configured()) { + return Result::from( + 'Filter for code coverage has not been configured'.PHP_EOL, + Result::FAILURE + ); + } + + $timer = new Timer; + $timer->start(); + + (new CacheWarmer)->warmCache( + $this->configuration->coverageCacheDirectory(), + ! $this->configuration->disableCodeCoverageIgnore(), + $this->configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(), + $this->codeCoverageFilterRegistry->get() + ); + + return Result::from(); + } +} diff --git a/overrides/TextUI/Output/Default/ProgressPrinter/TestSkippedSubscriber.php b/overrides/TextUI/Output/Default/ProgressPrinter/TestSkippedSubscriber.php index 4e0521a3..80de0225 100644 --- a/overrides/TextUI/Output/Default/ProgressPrinter/TestSkippedSubscriber.php +++ b/overrides/TextUI/Output/Default/ProgressPrinter/TestSkippedSubscriber.php @@ -61,9 +61,11 @@ final class TestSkippedSubscriber extends Subscriber implements SkippedSubscribe */ public function notify(Skipped $event): void { - str_contains($event->message(), '__TODO__') - ? $this->printTodoItem() - : $this->printer()->testSkipped(); + if (str_contains($event->message(), '__TODO__')) { + $this->printTodoItem(); + } + + $this->printer()->testSkipped(); } /** diff --git a/phpunit.xml b/phpunit.xml index 13e5b2af..c4d4343e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,17 +7,11 @@ bootstrap="vendor/autoload.php" colors="true" failOnRisky="true" - failOnWarning="true" + failOnWarning="false" processIsolation="false" stopOnError="false" stopOnFailure="false" backupStaticProperties="false" - displayDetailsOnIncompleteTests="true" - displayDetailsOnSkippedTests="true" - displayDetailsOnTestsThatTriggerDeprecations="true" - displayDetailsOnTestsThatTriggerErrors="true" - displayDetailsOnTestsThatTriggerNotices="true" - displayDetailsOnTestsThatTriggerWarnings="true" > diff --git a/src/Bootstrappers/BootExceptionHandler.php b/src/Bootstrappers/BootExceptionHandler.php deleted file mode 100644 index bcebf5a6..00000000 --- a/src/Bootstrappers/BootExceptionHandler.php +++ /dev/null @@ -1,24 +0,0 @@ -register(); - } -} diff --git a/src/Bootstrappers/BootFiles.php b/src/Bootstrappers/BootFiles.php index e4fbbe26..3add2a70 100644 --- a/src/Bootstrappers/BootFiles.php +++ b/src/Bootstrappers/BootFiles.php @@ -69,11 +69,9 @@ final class BootFiles implements Bootstrapper if (! Str::endsWith($filename, '.php')) { return; } - if (! file_exists($filename)) { return; } - include_once $filename; } diff --git a/src/Bootstrappers/BootOverrides.php b/src/Bootstrappers/BootOverrides.php index 7b3d2307..d25f935d 100644 --- a/src/Bootstrappers/BootOverrides.php +++ b/src/Bootstrappers/BootOverrides.php @@ -21,6 +21,7 @@ final class BootOverrides implements Bootstrapper 'Runner/Filter/NameFilterIterator.php', 'Runner/ResultCache/DefaultResultCache.php', 'Runner/TestSuiteLoader.php', + 'TextUI/Command/WarmCodeCoverageCacheCommand.php', 'TextUI/Output/Default/ProgressPrinter/TestSkippedSubscriber.php', ]; diff --git a/src/Kernel.php b/src/Kernel.php index debf1097..25f2aadd 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -27,7 +27,6 @@ final class Kernel */ private const BOOTSTRAPPERS = [ Bootstrappers\BootOverrides::class, - Bootstrappers\BootExceptionHandler::class, Bootstrappers\BootSubscribers::class, Bootstrappers\BootFiles::class, Bootstrappers\BootView::class, diff --git a/src/Logging/TeamCity/Converter.php b/src/Logging/TeamCity/Converter.php index 1f0de446..120e2f82 100644 --- a/src/Logging/TeamCity/Converter.php +++ b/src/Logging/TeamCity/Converter.php @@ -180,6 +180,13 @@ final class Converter */ public function getStateFromResult(PhpUnitTestResult $result): State { - return $this->stateGenerator->fromPhpUnitTestResult($result); + $numberOfPassedTests = $result->numberOfTestsRun() + - $result->numberOfTestErroredEvents() + - $result->numberOfTestFailedEvents() + - $result->numberOfTestSkippedEvents() + - $result->numberOfTestsWithTestConsideredRiskyEvents() + - $result->numberOfTestMarkedIncompleteEvents(); + + return $this->stateGenerator->fromPhpUnitTestResult($numberOfPassedTests, $result); } } diff --git a/src/Logging/TeamCity/TeamCityLogger.php b/src/Logging/TeamCity/TeamCityLogger.php index 9e5d6312..85994a84 100644 --- a/src/Logging/TeamCity/TeamCityLogger.php +++ b/src/Logging/TeamCity/TeamCityLogger.php @@ -91,7 +91,6 @@ final class TeamCityLogger public function testMarkedIncomplete(MarkedIncomplete $event): never { - // TODO: when does this trigger? throw ShouldNotHappen::fromMessage('testMarkedIncomplete not implemented.'); } diff --git a/src/Plugins/Cache.php b/src/Plugins/Cache.php index f1929b9d..f731e883 100644 --- a/src/Plugins/Cache.php +++ b/src/Plugins/Cache.php @@ -30,13 +30,13 @@ final class Cache implements HandlesArguments */ public function handleArguments(array $arguments): array { - if (! $this->hasArgument('--parallel', $arguments)) { - $arguments = $this->pushArgument( - sprintf('--cache-directory=%s', realpath(self::TEMPORARY_FOLDER)), - $arguments - ); + $arguments = $this->pushArgument( + sprintf('--cache-directory=%s', realpath(self::TEMPORARY_FOLDER)), + $arguments + ); - $arguments = $this->pushArgument('--cache-result', $arguments); + if (! $this->hasArgument('--parallel', $arguments)) { + return $this->pushArgument('--cache-result', $arguments); } return $arguments; diff --git a/src/Plugins/Parallel/Paratest/ResultPrinter.php b/src/Plugins/Parallel/Paratest/ResultPrinter.php index 71134d1f..62297e1c 100644 --- a/src/Plugins/Parallel/Paratest/ResultPrinter.php +++ b/src/Plugins/Parallel/Paratest/ResultPrinter.php @@ -12,6 +12,7 @@ use function fread; use function fseek; use function ftell; use function fwrite; +use NunoMaduro\Collision\Adapters\Phpunit\State; use ParaTest\Options; use Pest\Plugins\Parallel\Support\CompactPrinter; use Pest\Support\StateGenerator; @@ -27,11 +28,21 @@ use Symfony\Component\Console\Output\OutputInterface; /** @internal */ final class ResultPrinter { + /** + * If the test should be marked as todo. + */ + public bool $lastWasTodo = false; + /** * The "native" printer. */ public readonly Printer $printer; + /** + * The state. + */ + public int $passedTests = 0; + /** * The "compact" printer. */ @@ -56,6 +67,13 @@ final class ResultPrinter public function print(string $buffer): void { + $buffer = OutputFormatter::escape($buffer); + if (str_starts_with($buffer, "\nGenerating code coverage report")) { + return; + } + if (str_starts_with($buffer, 'done [')) { + return; + } $this->output->write(OutputFormatter::escape($buffer)); } @@ -140,7 +158,7 @@ final class ResultPrinter return; } - $state = (new StateGenerator())->fromPhpUnitTestResult($testResult); + $state = (new StateGenerator())->fromPhpUnitTestResult($this->passedTests, $testResult); $this->compactPrinter->errors($state); $this->compactPrinter->recap($state, $testResult, $duration, $this->options); @@ -148,6 +166,20 @@ final class ResultPrinter private function printFeedbackItem(string $item): void { + if ($this->lastWasTodo) { + $this->lastWasTodo = false; + + return; + } + + if ($item === 'T') { + $this->lastWasTodo = true; + } + + if ($item === '.') { + $this->passedTests++; + } + $this->compactPrinter->descriptionItem($item); } diff --git a/src/Plugins/Parallel/Support/CompactPrinter.php b/src/Plugins/Parallel/Support/CompactPrinter.php index ed25be77..95aec81d 100644 --- a/src/Plugins/Parallel/Support/CompactPrinter.php +++ b/src/Plugins/Parallel/Support/CompactPrinter.php @@ -36,8 +36,9 @@ final class CompactPrinter '.' => ['gray', '.'], 'S' => ['yellow', 's'], 'T' => ['cyan', 't'], - 'I' => ['yellow', 'i'], - 'N' => ['yellow', 'i'], + 'I' => ['yellow', '!'], + 'N' => ['yellow', '!'], + 'D' => ['yellow', '!'], 'R' => ['yellow', '!'], 'W' => ['yellow', '!'], 'E' => ['red', '⨯'], @@ -105,7 +106,9 @@ final class CompactPrinter */ public function errors(State $state): void { - $this->style->writeErrorsSummary($state, false); + $this->output->writeln(''); + + $this->style->writeErrorsSummary($state); } /** diff --git a/src/Support/HigherOrderTapProxy.php b/src/Support/HigherOrderTapProxy.php index 1b5f12cf..151b2b80 100644 --- a/src/Support/HigherOrderTapProxy.php +++ b/src/Support/HigherOrderTapProxy.php @@ -6,15 +6,12 @@ namespace Pest\Support; use PHPUnit\Framework\TestCase; use ReflectionClass; -use Throwable; /** * @internal */ final class HigherOrderTapProxy { - private const UNDEFINED_PROPERTY = 'Undefined property: P\\'; // @phpstan-ignore-line - /** * Create a new tap proxy instance. */ @@ -39,20 +36,19 @@ final class HigherOrderTapProxy */ public function __get(string $property) { - try { + if (property_exists($this->target, $property)) { return $this->target->{$property}; // @phpstan-ignore-line - } catch (Throwable $throwable) { // @phpstan-ignore-line - Reflection::setPropertyValue($throwable, 'file', Backtrace::file()); - Reflection::setPropertyValue($throwable, 'line', Backtrace::line()); - - if (Str::startsWith($message = $throwable->getMessage(), self::UNDEFINED_PROPERTY)) { - /** @var ReflectionClass $reflection */ - $reflection = (new ReflectionClass($this->target))->getParentClass(); - Reflection::setPropertyValue($throwable, 'message', sprintf('Undefined property %s::$%s', $reflection->getName(), $property)); - } - - throw $throwable; } + + $className = (new ReflectionClass($this->target))->getName(); + + if (str_starts_with($className, 'P\\')) { + $className = substr($className, 2); + } + + trigger_error(sprintf('Undefined property %s::$%s', $className, $property), E_USER_WARNING); + + return null; } /** diff --git a/src/Support/StateGenerator.php b/src/Support/StateGenerator.php index 7d06b7b4..23bf025a 100644 --- a/src/Support/StateGenerator.php +++ b/src/Support/StateGenerator.php @@ -6,19 +6,19 @@ namespace Pest\Support; use NunoMaduro\Collision\Adapters\Phpunit\State; use NunoMaduro\Collision\Adapters\Phpunit\TestResult; +use NunoMaduro\Collision\Exceptions\TestOutcome; use PHPUnit\Event\Code\TestDox; use PHPUnit\Event\Code\TestMethod; use PHPUnit\Event\Code\Throwable; use PHPUnit\Event\Test\Errored; use PHPUnit\Event\TestData\TestDataCollection; -use PHPUnit\Framework\IncompleteTestError; use PHPUnit\Framework\SkippedWithMessageException; use PHPUnit\Metadata\MetadataCollection; use PHPUnit\TestRunner\TestResult\TestResult as PHPUnitTestResult; final class StateGenerator { - public function fromPhpUnitTestResult(PHPUnitTestResult $testResult): State + public function fromPhpUnitTestResult(int $passedTests, PHPUnitTestResult $testResult): State { $state = new State(); @@ -55,7 +55,7 @@ final class StateGenerator $state->add(TestResult::fromTestCase( $riskyEvent->test(), TestResult::RISKY, - Throwable::from(new IncompleteTestError($riskyEvent->message())) + Throwable::from(new TestOutcome($riskyEvent->message())) )); } } @@ -74,16 +74,69 @@ final class StateGenerator )); } - $numberOfPassedTests = $testResult->numberOfTestsRun() - - $testResult->numberOfTestErroredEvents() - - $testResult->numberOfTestFailedEvents() - - $testResult->numberOfTestSkippedEvents() - - $testResult->numberOfTestsWithTestConsideredRiskyEvents() - - $testResult->numberOfTestMarkedIncompleteEvents(); + foreach ($testResult->testTriggeredDeprecationEvents() as $testResultEvent) { + $testResultEvent = $testResultEvent[0]; - for ($i = 0; $i < $numberOfPassedTests; $i++) { $state->add(TestResult::fromTestCase( + $testResultEvent->test(), + TestResult::DEPRECATED, + Throwable::from(new TestOutcome($testResultEvent->message())) + )); + } + foreach ($testResult->testTriggeredPhpDeprecationEvents() as $testResultEvent) { + $testResultEvent = $testResultEvent[0]; + + $state->add(TestResult::fromTestCase( + $testResultEvent->test(), + TestResult::DEPRECATED, + Throwable::from(new TestOutcome($testResultEvent->message())) + )); + } + + foreach ($testResult->testTriggeredNoticeEvents() as $testResultEvent) { + $testResultEvent = $testResultEvent[0]; + + $state->add(TestResult::fromTestCase( + $testResultEvent->test(), + TestResult::NOTICE, + Throwable::from(new TestOutcome($testResultEvent->message())) + )); + } + + foreach ($testResult->testTriggeredPhpNoticeEvents() as $testResultEvent) { + $testResultEvent = $testResultEvent[0]; + + $state->add(TestResult::fromTestCase( + $testResultEvent->test(), + TestResult::NOTICE, + Throwable::from(new TestOutcome($testResultEvent->message())) + )); + } + + foreach ($testResult->testTriggeredWarningEvents() as $testResultEvent) { + $testResultEvent = $testResultEvent[0]; + + $state->add(TestResult::fromTestCase( + $testResultEvent->test(), + TestResult::WARN, + Throwable::from(new TestOutcome($testResultEvent->message())) + )); + } + + foreach ($testResult->testTriggeredPhpWarningEvents() as $testResultEvent) { + $testResultEvent = $testResultEvent[0]; + + $state->add(TestResult::fromTestCase( + $testResultEvent->test(), + TestResult::WARN, + Throwable::from(new TestOutcome($testResultEvent->message())) + )); + } + + // for each test that passed, we need to add it to the state + for ($i = 0; $i < $passedTests; $i++) { + $state->add(TestResult::fromTestCase( new TestMethod( /** @phpstan-ignore-next-line */ "$i", diff --git a/tests/.snapshots/Failure.php.inc b/tests/.snapshots/Failure.php.inc index 727211aa..cde5f942 100644 --- a/tests/.snapshots/Failure.php.inc +++ b/tests/.snapshots/Failure.php.inc @@ -1,6 +1,6 @@ ##teamcity[testSuiteStarted name='Tests/tests/Failure' locationHint='file://tests/.tests/Failure.php' flowId='1234'] ##teamcity[testStarted name='it can fail with comparison' locationHint='pest_qn://tests/.tests/Failure.php::it can fail with comparison' flowId='1234'] -##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at src/Mixins/Expectation.php:342|nat src/Support/ExpectationPipeline.php:75|nat src/Support/ExpectationPipeline.php:79|nat src/Expectation.php:300|nat tests/.tests/Failure.php:6|nat src/Factories/TestCaseMethodFactory.php:100|nat src/Concerns/Testable.php:262|nat src/Support/ExceptionTrace.php:28|nat src/Concerns/Testable.php:262|nat src/Concerns/Testable.php:217|nat src/Kernel.php:91' type='comparisonFailure' actual='true' expected='false' flowId='1234'] +##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at src/Mixins/Expectation.php:342|nat src/Support/ExpectationPipeline.php:75|nat src/Support/ExpectationPipeline.php:79|nat src/Expectation.php:300|nat tests/.tests/Failure.php:6|nat src/Factories/TestCaseMethodFactory.php:100|nat src/Concerns/Testable.php:262|nat src/Support/ExceptionTrace.php:28|nat src/Concerns/Testable.php:262|nat src/Concerns/Testable.php:217|nat src/Kernel.php:90' type='comparisonFailure' actual='true' expected='false' flowId='1234'] ##teamcity[testFinished name='it can fail with comparison' duration='100000' flowId='1234'] ##teamcity[testStarted name='it can be ignored because of no assertions' locationHint='pest_qn://tests/.tests/Failure.php::it can be ignored because of no assertions' flowId='1234'] ##teamcity[testIgnored name='it can be ignored because of no assertions' message='This test did not perform any assertions' details='' flowId='1234'] @@ -9,7 +9,7 @@ ##teamcity[testIgnored name='it can be ignored because it is skipped' message='This test was ignored.' details='' flowId='1234'] ##teamcity[testFinished name='it can be ignored because it is skipped' duration='100000' flowId='1234'] ##teamcity[testStarted name='it can fail' locationHint='pest_qn://tests/.tests/Failure.php::it can fail' flowId='1234'] -##teamcity[testFailed name='it can fail' message='oh noo' details='at tests/.tests/Failure.php:18|nat src/Factories/TestCaseMethodFactory.php:100|nat src/Concerns/Testable.php:262|nat src/Support/ExceptionTrace.php:28|nat src/Concerns/Testable.php:262|nat src/Concerns/Testable.php:217|nat src/Kernel.php:91' flowId='1234'] +##teamcity[testFailed name='it can fail' message='oh noo' details='at tests/.tests/Failure.php:18|nat src/Factories/TestCaseMethodFactory.php:100|nat src/Concerns/Testable.php:262|nat src/Support/ExceptionTrace.php:28|nat src/Concerns/Testable.php:262|nat src/Concerns/Testable.php:217|nat src/Kernel.php:90' flowId='1234'] ##teamcity[testFinished name='it can fail' duration='100000' flowId='1234'] ##teamcity[testStarted name='it is not done yet' locationHint='pest_qn://tests/.tests/Failure.php::it is not done yet' flowId='1234'] ##teamcity[testIgnored name='it is not done yet' message='This test was ignored.' details='' flowId='1234'] diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 81ae5e23..1812c562 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -139,6 +139,10 @@ ✓ it is a test ✓ it uses correct parent class + DEPR Tests\Features\Deprecated + ! deprecated → str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated + ! user deprecated → Since foo 1.0: This is a deprecation description + PASS Tests\Features\Exceptions ✓ it gives access the the underlying expectException ✓ it catch exceptions @@ -646,9 +650,9 @@ ✓ it skips with falsy closure condition ✓ it can be used in higher order tests - PASS Tests\Features\Helpers + WARN Tests\Features\Helpers ✓ it can set/get properties on $this - ✓ it throws error if property do not exist + ! it gets null if property do not exist → Undefined property Tests\Features\Helpers::$wqdwqdqw ✓ it allows to call underlying protected/private methods ✓ it throws error if method do not exist ✓ it can forward unexpected calls to any global function @@ -678,6 +682,9 @@ ✓ it is a test ✓ it is a higher order message test + NOTI Tests\Features\Notices + ! notice → This is a notice description + PASS Tests\Features\PendingHigherOrderTests ✓ get 'foo' ✓ get 'foo' → get 'bar' → expect true → toBeTrue @@ -750,6 +757,10 @@ ↓ something todo later chained and with function body ✓ it does something within a file with a todo + WARN Tests\Features\Warnings + ! warning → Undefined property: P\Tests\Features\Warnings::$fooqwdfwqdfqw + ! user warning → This is a warning description + PASS Tests\Fixtures\DirectoryWithTests\ExampleTest ✓ it example 1 @@ -913,8 +924,9 @@ PASS Tests\Visual\Todo ✓ todo + ✓ todo in parallel PASS Tests\Visual\Version ✓ visual snapshot of help command output - Tests: 4 incomplete, 4 todos, 18 skipped, 634 passed (1560 assertions) \ No newline at end of file + Tests: 2 deprecated, 3 warnings, 4 incomplete, 1 notice, 4 todos, 18 skipped, 634 passed (1567 assertions) \ No newline at end of file diff --git a/tests/Autoload.php b/tests/Autoload.php index 1d53a325..2d3ce530 100644 --- a/tests/Autoload.php +++ b/tests/Autoload.php @@ -1,9 +1,5 @@ register(); -} - trait PluginTrait { public function assertPluginTraitGotRegistered(): void diff --git a/tests/Features/Deprecated.php b/tests/Features/Deprecated.php new file mode 100644 index 00000000..bd25c1e1 --- /dev/null +++ b/tests/Features/Deprecated.php @@ -0,0 +1,15 @@ +toBeTrue(); +}); + +test('user deprecated', function () { + trigger_deprecation('foo', '1.0', 'This is a deprecation description'); + + expect(true)->toBeTrue(); +}); diff --git a/tests/Features/Helpers.php b/tests/Features/Helpers.php index ee34702e..90663554 100644 --- a/tests/Features/Helpers.php +++ b/tests/Features/Helpers.php @@ -10,9 +10,9 @@ it('can set/get properties on $this', function () { expect($this->user)->toBe('nuno'); }); -it('throws error if property do not exist', function () { - test()->user; -})->throws(\Whoops\Exception\ErrorException::class, 'Undefined property PHPUnit\Framework\TestCase::$user'); +it('gets null if property do not exist', function () { + expect(test()->wqdwqdqw)->toBe(null); +}); class User { diff --git a/tests/Features/Notices.php b/tests/Features/Notices.php new file mode 100644 index 00000000..dd245450 --- /dev/null +++ b/tests/Features/Notices.php @@ -0,0 +1,7 @@ +toBeTrue(); +}); diff --git a/tests/Features/Warnings.php b/tests/Features/Warnings.php new file mode 100644 index 00000000..e638b3f0 --- /dev/null +++ b/tests/Features/Warnings.php @@ -0,0 +1,13 @@ +fooqwdfwqdfqw; + + expect(true)->toBeTrue(); +}); + +test('user warning', function () { + trigger_error('This is a warning description', E_USER_WARNING); + + expect(true)->toBeTrue(); +}); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 1fbe0ed3..66726d7b 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -15,6 +15,6 @@ $run = function () { }; test('parallel', function () use ($run) { - expect($run())->toContain('Tests: 4 incomplete, 4 todos, 15 skipped, 626 passed (1550 assertions)') + expect($run())->toContain('Tests: 2 deprecated, 3 warnings, 4 incomplete, 1 notice, 4 todos, 15 skipped, 626 passed (1555 assertions)') ->toContain('Parallel: 3 processes'); }); diff --git a/tests/Visual/Todo.php b/tests/Visual/Todo.php index ac4f3ba5..3a396a21 100644 --- a/tests/Visual/Todo.php +++ b/tests/Visual/Todo.php @@ -2,14 +2,16 @@ use Symfony\Component\Process\Process; -$run = function (string $target, $decorated = false) { - $process = new Process(['php', 'bin/pest', $target, '--colors=always'], dirname(__DIR__, 2), +$run = function (string $target, bool $parallel) { + $process = new Process(['php', 'bin/pest', $target, $parallel ? '--parallel' : '', '--colors=always'], dirname(__DIR__, 2), ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'], ); $process->run(); - return $decorated ? $process->getOutput() : preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $process->getOutput()); + expect($process->getExitCode())->toBe(0); + + return preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $process->getOutput()); }; $snapshot = function ($name) { @@ -23,5 +25,9 @@ $snapshot = function ($name) { }; test('todo', function () use ($run, $snapshot) { - expect($run('--todo'))->toContain($snapshot('todo')); + expect($run('--todo', false))->toContain($snapshot('todo')); +})->skip(PHP_OS_FAMILY === 'Windows'); + +test('todo in parallel', function () use ($run, $snapshot) { + expect($run('--todo', true))->toContain($snapshot('todo')); })->skip(PHP_OS_FAMILY === 'Windows');