diff --git a/.psalm/baseline.xml b/.psalm/baseline.xml index 3476b5bded9..5fb77ddafe9 100644 --- a/.psalm/baseline.xml +++ b/.psalm/baseline.xml @@ -1044,18 +1044,13 @@ - - LegacyTestResult - LegacyTestResult - LegacyTestResult - LegacyTestResult - LegacyTestResult - LegacyTestResult - LegacyTestResult - resourceUsageSinceStartOfRequest + + assert($test instanceof TestMethod) + assert($test instanceof TestMethod) + diff --git a/src/Framework/TestSuite.php b/src/Framework/TestSuite.php index 0313f96f3d0..acc78800ac8 100644 --- a/src/Framework/TestSuite.php +++ b/src/Framework/TestSuite.php @@ -103,18 +103,10 @@ public static function fromClassReflector(ReflectionClass $class): static $constructor = $class->getConstructor(); if ($constructor !== null && !$constructor->isPublic()) { - $message = sprintf( - 'Class "%s" has no public constructor.', - $class->getName() - ); - - Event\Facade::emitter()->testRunnerTriggeredWarning($message); - - $testSuite->addTest( - new WarningTestCase( - $class->getName(), - '', - $message + Event\Facade::emitter()->testRunnerTriggeredWarning( + sprintf( + 'Class "%s" has no public constructor.', + $class->getName() ) ); @@ -138,18 +130,10 @@ public static function fromClassReflector(ReflectionClass $class): static } if (count($testSuite) === 0) { - $message = sprintf( - 'No tests found in class "%s".', - $class->getName() - ); - - Event\Facade::emitter()->testRunnerTriggeredWarning($message); - - $testSuite->addTest( - new WarningTestCase( - $class->getName(), - '', - $message + Event\Facade::emitter()->testRunnerTriggeredWarning( + sprintf( + 'No tests found in class "%s".', + $class->getName() ) ); } diff --git a/src/Runner/TestResult/Collector.php b/src/Runner/TestResult/Collector.php index 73493afb241..914d827f2fc 100644 --- a/src/Runner/TestResult/Collector.php +++ b/src/Runner/TestResult/Collector.php @@ -235,6 +235,19 @@ public function hasTestMarkedIncompleteEvents(): bool return !empty($this->testMarkedIncompleteEvents); } + public function hasTestRunnerTriggeredWarningEvents(): bool + { + return !empty($this->testRunnerTriggeredWarningEvents); + } + + /** + * @psalm-return list + */ + public function testRunnerTriggeredWarningEvents(): array + { + return $this->testRunnerTriggeredWarningEvents; + } + /** * @psalm-return list */ diff --git a/src/TextUI/Application.php b/src/TextUI/Application.php index 203648d2506..1bf6241ccbf 100644 --- a/src/TextUI/Application.php +++ b/src/TextUI/Application.php @@ -249,6 +249,15 @@ private function handleArguments(array $argv): TestSuite exit(self::EXCEPTION_EXIT); } + if ($testSuite->isEmpty() && + ($arguments->hasArgument() || $this->xmlConfiguration->phpunit()->hasDefaultTestSuite())) { + $this->printVersionString(); + + print 'No tests found.' . PHP_EOL; + + exit(self::EXCEPTION_EXIT); + } + if ($configuration->hasCoverageReport() || $arguments->hasWarmCoverageCache()) { CodeCoverageFilterRegistry::init($arguments, $this->xmlConfiguration); } diff --git a/src/TextUI/ResultPrinter.php b/src/TextUI/ResultPrinter.php index a6515188e8a..fd8363562f5 100644 --- a/src/TextUI/ResultPrinter.php +++ b/src/TextUI/ResultPrinter.php @@ -10,14 +10,19 @@ namespace PHPUnit\TextUI; use const PHP_EOL; -use PHPUnit\Framework\RiskyTest; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestResult as LegacyTestResult; +use function assert; +use function count; +use function sprintf; +use function str_starts_with; +use function strlen; +use function substr; +use function trim; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; use PHPUnit\TestRunner\TestResult\TestResult; use PHPUnit\Util\Color; use PHPUnit\Util\Printer; -use ReflectionMethod; use SebastianBergmann\Timer\ResourceUsageFormatter; /** @@ -26,36 +31,36 @@ final class ResultPrinter { private Printer $printer; - private bool $colors; + private bool $colorizeOutput; private bool $displayDetailsOnIncompleteTests; private bool $displayDetailsOnSkippedTests; - private bool $reverse; - private bool $defectListPrinted = false; + private bool $displayDefectsInReverseOrder; + private bool $listPrinted = false; - public function __construct(Printer $printer, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $colors, bool $reverse) + public function __construct(Printer $printer, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $colorizeOutput, bool $displayDefectsInReverseOrder) { $this->printer = $printer; $this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests; $this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests; - $this->colors = $colors; - $this->reverse = $reverse; + $this->colorizeOutput = $colorizeOutput; + $this->displayDefectsInReverseOrder = $displayDefectsInReverseOrder; } - public function printResult(TestResult $result, LegacyTestResult $legacyResult): void + public function printResult(TestResult $result): void { $this->printHeader($result); - $this->printTestsWithErrors($result, $legacyResult); - $this->printTestsWithWarnings($result, $legacyResult); - $this->printTestsWithFailedAssertions($result, $legacyResult); - $this->printRiskyTests($result, $legacyResult); + $this->printTestsWithErrors($result); + $this->printTestsWithWarnings($result); + $this->printTestsWithFailedAssertions($result); + $this->printRiskyTests($result); if ($this->displayDetailsOnIncompleteTests) { - $this->printIncompleteTests($result, $legacyResult); + $this->printIncompleteTests($result); } if ($this->displayDetailsOnSkippedTests) { - $this->printSkippedTests($result, $legacyResult); + $this->printSkippedTests($result); } $this->printFooter($result); @@ -73,48 +78,140 @@ private function printHeader(TestResult $result): void } } - private function printTestsWithErrors(TestResult $result, LegacyTestResult $legacyResult): void + private function printTestsWithErrors(TestResult $result): void { - $this->printDefects($legacyResult->errors(), 'error'); - } + if (!$result->hasTestErroredEvents()) { + return; + } - private function printTestsWithFailedAssertions(TestResult $result, LegacyTestResult $legacyResult): void - { - $this->printDefects($legacyResult->failures(), 'failure'); + $elements = []; + + foreach ($result->testErroredEvents() as $event) { + if ($event instanceof BeforeFirstTestMethodErrored) { + $title = $event->testClassName(); + } else { + $title = $this->name($event->test()); + } + + $elements[] = [ + 'title' => $title, + 'body' => $event->throwable()->asString(), + ]; + } + + $this->printList(count($elements), $elements, 'error'); } - private function printTestsWithWarnings(TestResult $result, LegacyTestResult $legacyResult): void + private function printTestsWithFailedAssertions(TestResult $result): void { - $this->printDefects($legacyResult->warnings(), 'warning'); + if (!$result->hasTestFailedEvents()) { + return; + } + + $elements = []; + + foreach ($result->testFailedEvents() as $event) { + $body = $event->throwable()->asString(); + + if (str_starts_with($body, 'AssertionError: ')) { + $body = substr($body, strlen('AssertionError: ')); + } + + $elements[] = [ + 'title' => $this->name($event->test()), + 'body' => $body, + ]; + } + + $this->printList(count($elements), $elements, 'failure'); } - private function printRiskyTests(TestResult $result, LegacyTestResult $legacyResult): void + private function printTestsWithWarnings(TestResult $result): void { - $this->printDefects($legacyResult->risky(), 'risky test'); } - private function printIncompleteTests(TestResult $result, LegacyTestResult $legacyResult): void + private function printRiskyTests(TestResult $result): void { - $this->printDefects($legacyResult->notImplemented(), 'incomplete test'); + if (!$result->hasTestConsideredRiskyEvents()) { + return; + } + + $elements = []; + + foreach ($result->testConsideredRiskyEvents() as $reasons) { + foreach ($reasons as $reason) { + $body = $reason->message() . PHP_EOL; + $test = $reason->test(); + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $body .= sprintf( + '%s%s:%d%s', + PHP_EOL, + $test->file(), + $test->line(), + PHP_EOL + ); + } + + $elements[] = [ + 'title' => $this->name($test), + 'body' => $body, + ]; + } + } + + $this->printList($result->numberOfTestsWithTestConsideredRiskyEvents(), $elements, 'risky test'); } - private function printSkippedTests(TestResult $result, LegacyTestResult $legacyResult): void + private function printIncompleteTests(TestResult $result): void { - $this->printDefects($legacyResult->skipped(), 'skipped test'); + if (!$result->hasTestMarkedIncompleteEvents()) { + return; + } + + $elements = []; + + foreach ($result->testMarkedIncompleteEvents() as $event) { + $elements[] = [ + 'title' => $this->name($event->test()), + 'body' => $event->throwable()->asString(), + ]; + } + + $this->printList(count($elements), $elements, 'incomplete test'); } - private function printDefects(array $defects, string $type): void + private function printSkippedTests(TestResult $result): void { - $count = count($defects); - - if ($count === 0) { + if (!$result->hasTestSkippedEvents()) { return; } - if ($this->defectListPrinted) { + $elements = []; + + foreach ($result->testSkippedEvents() as $event) { + $elements[] = [ + 'title' => $this->name($event->test()), + 'body' => $event->message(), + ]; + } + + $this->printList(count($elements), $elements, 'skipped test'); + } + + /** + * @psalm-param list $elements + */ + private function printList(int $count, array $elements, string $type): void + { + if ($this->listPrinted) { $this->printer->print("\n--\n\n"); } + $this->listPrinted = true; + $this->printer->print( sprintf( "There %s %d %s%s:\n", @@ -127,64 +224,27 @@ private function printDefects(array $defects, string $type): void $i = 1; - if ($this->reverse) { - $defects = array_reverse($defects); + if ($this->displayDefectsInReverseOrder) { + $elements = array_reverse($elements); } - foreach ($defects as $defect) { - $this->printDefect($defect, $i++); + foreach ($elements as $element) { + $this->printListElement($i++, $element['title'], $element['body']); } - - $this->defectListPrinted = true; } - private function printDefect(TestFailure $defect, int $index): void - { - $this->printDefectHeader($index, $defect->getTestName()); - $this->printDefectTrace($defect); - } - - private function printDefectHeader(int $index, string $testName): void + private function printListElement(int $number, string $title, string $body): void { $this->printer->print( sprintf( - "\n%d) %s\n", - $index, - $testName + "\n%d) %s\n%s\n", + $number, + $title, + trim($body) ) ); } - private function printDefectTrace(TestFailure $defect): void - { - $e = $defect->thrownException(); - - $this->printer->print((string) $e); - - if ($defect->thrownException() instanceof RiskyTest) { - $test = $defect->failedTest(); - - assert($test instanceof TestCase); - - /** @noinspection PhpUnhandledExceptionInspection */ - $reflector = new ReflectionMethod($test::class, $test->getName(false)); - - $this->printer->print( - sprintf( - '%s%s:%d%s', - PHP_EOL, - $reflector->getFileName(), - $reflector->getStartLine(), - PHP_EOL - ) - ); - } else { - while ($e = $e->getPrevious()) { - $this->printer->print("\nCaused by\n" . $e); - } - } - } - private function printFooter(TestResult $result): void { if ($result->numberOfTestsRun() === 0) { @@ -280,7 +340,7 @@ private function printCountString(int $count, string $name, string $color, bool private function printWithColor(string $color, string $buffer, bool $lf = true): void { - if ($this->colors) { + if ($this->colorizeOutput) { $buffer = Color::colorizeTextBox($color, $buffer); } @@ -290,4 +350,15 @@ private function printWithColor(string $color, string $buffer, bool $lf = true): $this->printer->print(PHP_EOL); } } + + private function name(Test $test): string + { + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + return $test->nameWithClass(); + } + + return $test->name(); + } } diff --git a/src/TextUI/TestRunner.php b/src/TextUI/TestRunner.php index 1005d9f9225..be2e5b1e1d0 100644 --- a/src/TextUI/TestRunner.php +++ b/src/TextUI/TestRunner.php @@ -393,7 +393,7 @@ public function run(TestSuite $suite): TestResult $result = Facade::result(); if (isset($resultPrinter)) { - $resultPrinter->printResult($result, $legacyResult); + $resultPrinter->printResult($result); } if (isset($junitXmlLogger)) { diff --git a/tests/_files/NotVoidTestCase.php b/tests/_files/NotVoidTestCase.php deleted file mode 100644 index e246f11085d..00000000000 --- a/tests/_files/NotVoidTestCase.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NotVoidTestCase extends TestCase -{ -} diff --git a/tests/end-to-end/event/_files/PrivateTest.php b/tests/end-to-end/event/_files/PrivateTest.php deleted file mode 100644 index 0d9517422d2..00000000000 --- a/tests/end-to-end/event/_files/PrivateTest.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\Event; - -use PHPUnit\Framework\TestCase; - -final class PrivateTest extends TestCase -{ - private function __construct() - { - } -} diff --git a/tests/end-to-end/event/test-class-with-private-constructor.phpt b/tests/end-to-end/event/test-class-with-private-constructor.phpt deleted file mode 100644 index 7cb46be096f..00000000000 --- a/tests/end-to-end/event/test-class-with-private-constructor.phpt +++ /dev/null @@ -1,42 +0,0 @@ ---TEST-- -The right events are emitted in the right order for a test class that has a non-public constructor ---SKIPIF-- - %sIssue765Test.php:%d diff --git a/tests/end-to-end/risky-tests/global-state.phpt b/tests/end-to-end/risky-tests/global-state.phpt index d4666a1db2c..00fad354ae8 100644 --- a/tests/end-to-end/risky-tests/global-state.phpt +++ b/tests/end-to-end/risky-tests/global-state.phpt @@ -22,6 +22,7 @@ Time: %s, Memory: %s There was 1 risky test: 1) PHPUnit\TestFixture\RiskyTests\GlobalStateManipulatorTest::testManipulatesGlobalState +This test modified global state but was not expected to do so --- Global variables before the test +++ Global variables after the test @@ @@ @@ -29,5 +30,7 @@ There was 1 risky test: + 'foo' => 'bar' %A +%s:%d + OK, but incomplete, skipped, or risky tests! Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/unit/Framework/TestSuiteTest.php b/tests/unit/Framework/TestSuiteTest.php index faae1ea6ff0..f30d6d5eb4a 100644 --- a/tests/unit/Framework/TestSuiteTest.php +++ b/tests/unit/Framework/TestSuiteTest.php @@ -19,7 +19,6 @@ use PHPUnit\TestFixture\DoubleTestCase; use PHPUnit\TestFixture\MultiDependencyTest; use PHPUnit\TestFixture\NotPublicTestCase; -use PHPUnit\TestFixture\NotVoidTestCase; use PHPUnit\TestFixture\PreConditionAndPostConditionTest; use PHPUnit\TestFixture\Success; use ReflectionClass; @@ -35,13 +34,6 @@ public function testNotPublicTestCase(): void $this->assertCount(1, $suite); } - public function testNotVoidTestCase(): void - { - $suite = TestSuite::fromClassName(NotVoidTestCase::class); - - $this->assertCount(1, $suite); - } - public function testBeforeAndAfterAnnotations(): void { $test = TestSuite::fromClassName(BeforeAndAfterTest::class); diff --git a/tests/unit/Runner/TestSuiteSorterTest.php b/tests/unit/Runner/TestSuiteSorterTest.php index a4055abd1f2..cdd93ded2c3 100644 --- a/tests/unit/Runner/TestSuiteSorterTest.php +++ b/tests/unit/Runner/TestSuiteSorterTest.php @@ -17,7 +17,6 @@ use PHPUnit\Framework\TestStatus\TestStatus; use PHPUnit\Framework\TestSuite; use PHPUnit\Runner\ResultCache\DefaultResultCache; -use PHPUnit\TestFixture\EmptyTestCaseTest; use PHPUnit\TestFixture\FailureTest; use PHPUnit\TestFixture\MultiDependencyTest; use PHPUnit\TestFixture\NotReorderableTest; @@ -577,28 +576,6 @@ public function defectsSorterOptionsProvider(): array ]; } - /** - * @see https://github.com/lstrojny/phpunit-clever-and-smart/issues/38 - */ - public function testCanHandleSuiteWithEmptyTestCase(): void - { - $suite = TestSuite::empty(); - $suite->addTestSuite(new ReflectionClass(EmptyTestCaseTest::class)); - - $sorter = new TestSuiteSorter; - - $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_DEFAULT, false, TestSuiteSorter::ORDER_DEFAULT); - - $this->assertSame(EmptyTestCaseTest::class, $suite->tests()[0]->getName()); - $this->assertSame( - sprintf( - 'No tests found in class "%s".', - EmptyTestCaseTest::class - ), - $suite->tests()[0]->tests()[0]->getMessage() - ); - } - public function suiteSorterOptionPermutationsProvider(): array { $orderValues = [TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_REVERSED, TestSuiteSorter::ORDER_RANDOMIZED];