*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI\Output\Default;
use const PHP_EOL;
use function array_keys;
use function array_merge;
use function array_reverse;
use function array_unique;
use function assert;
use function count;
use function explode;
use function ksort;
use function range;
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\AfterLastTestMethodErrored;
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
use PHPUnit\Event\Test\ConsideredRisky;
use PHPUnit\Event\Test\DeprecationTriggered;
use PHPUnit\Event\Test\ErrorTriggered;
use PHPUnit\Event\Test\NoticeTriggered;
use PHPUnit\Event\Test\PhpDeprecationTriggered;
use PHPUnit\Event\Test\PhpNoticeTriggered;
use PHPUnit\Event\Test\PhpunitDeprecationTriggered;
use PHPUnit\Event\Test\PhpunitErrorTriggered;
use PHPUnit\Event\Test\PhpunitWarningTriggered;
use PHPUnit\Event\Test\PhpWarningTriggered;
use PHPUnit\Event\Test\WarningTriggered;
use PHPUnit\TestRunner\TestResult\Issues\Issue;
use PHPUnit\TestRunner\TestResult\TestResult;
use PHPUnit\TextUI\Output\Printer;
/**
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class ResultPrinter
{
private readonly Printer $printer;
private readonly bool $displayPhpunitErrors;
private readonly bool $displayPhpunitWarnings;
private readonly bool $displayTestsWithErrors;
private readonly bool $displayTestsWithFailedAssertions;
private readonly bool $displayRiskyTests;
private readonly bool $displayPhpunitDeprecations;
private readonly bool $displayDetailsOnIncompleteTests;
private readonly bool $displayDetailsOnSkippedTests;
private readonly bool $displayDetailsOnTestsThatTriggerDeprecations;
private readonly bool $displayDetailsOnTestsThatTriggerErrors;
private readonly bool $displayDetailsOnTestsThatTriggerNotices;
private readonly bool $displayDetailsOnTestsThatTriggerWarnings;
private readonly bool $displayDefectsInReverseOrder;
private bool $listPrinted = false;
public function __construct(Printer $printer, bool $displayPhpunitErrors, bool $displayPhpunitWarnings, bool $displayPhpunitDeprecations, bool $displayTestsWithErrors, bool $displayTestsWithFailedAssertions, bool $displayRiskyTests, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $displayDefectsInReverseOrder)
{
$this->printer = $printer;
$this->displayPhpunitErrors = $displayPhpunitErrors;
$this->displayPhpunitWarnings = $displayPhpunitWarnings;
$this->displayPhpunitDeprecations = $displayPhpunitDeprecations;
$this->displayTestsWithErrors = $displayTestsWithErrors;
$this->displayTestsWithFailedAssertions = $displayTestsWithFailedAssertions;
$this->displayRiskyTests = $displayRiskyTests;
$this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests;
$this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests;
$this->displayDetailsOnTestsThatTriggerDeprecations = $displayDetailsOnTestsThatTriggerDeprecations;
$this->displayDetailsOnTestsThatTriggerErrors = $displayDetailsOnTestsThatTriggerErrors;
$this->displayDetailsOnTestsThatTriggerNotices = $displayDetailsOnTestsThatTriggerNotices;
$this->displayDetailsOnTestsThatTriggerWarnings = $displayDetailsOnTestsThatTriggerWarnings;
$this->displayDefectsInReverseOrder = $displayDefectsInReverseOrder;
}
public function print(TestResult $result): void
{
if ($this->displayPhpunitErrors) {
$this->printPhpunitErrors($result);
}
if ($this->displayPhpunitWarnings) {
$this->printTestRunnerWarnings($result);
}
if ($this->displayPhpunitDeprecations) {
$this->printTestRunnerDeprecations($result);
}
if ($this->displayTestsWithErrors) {
$this->printTestsWithErrors($result);
}
if ($this->displayTestsWithFailedAssertions) {
$this->printTestsWithFailedAssertions($result);
}
if ($this->displayPhpunitWarnings) {
$this->printDetailsOnTestsThatTriggeredPhpunitWarnings($result);
}
if ($this->displayPhpunitDeprecations) {
$this->printDetailsOnTestsThatTriggeredPhpunitDeprecations($result);
}
if ($this->displayRiskyTests) {
$this->printRiskyTests($result);
}
if ($this->displayDetailsOnIncompleteTests) {
$this->printIncompleteTests($result);
}
if ($this->displayDetailsOnSkippedTests) {
$this->printSkippedTestSuites($result);
$this->printSkippedTests($result);
}
if ($this->displayDetailsOnTestsThatTriggerErrors) {
$this->printIssueList('error', $result->errors());
}
if ($this->displayDetailsOnTestsThatTriggerWarnings) {
$this->printIssueList('PHP warning', $result->phpWarnings());
$this->printIssueList('warning', $result->warnings());
}
if ($this->displayDetailsOnTestsThatTriggerNotices) {
$this->printIssueList('PHP notice', $result->phpNotices());
$this->printIssueList('notice', $result->notices());
}
if ($this->displayDetailsOnTestsThatTriggerDeprecations) {
$this->printIssueList('PHP deprecation', $result->phpDeprecations());
$this->printIssueList('deprecation', $result->deprecations());
}
}
private function printPhpunitErrors(TestResult $result): void
{
if (!$result->hasTestTriggeredPhpunitErrorEvents()) {
return;
}
$elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitErrorEvents());
$this->printListHeaderWithNumber($elements['numberOfTestsWithIssues'], 'PHPUnit error');
$this->printList($elements['elements']);
}
private function printDetailsOnTestsThatTriggeredPhpunitDeprecations(TestResult $result): void
{
if (!$result->hasTestTriggeredPhpunitDeprecationEvents()) {
return;
}
$elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitDeprecationEvents());
$this->printListHeaderWithNumberOfTestsAndNumberOfIssues(
$elements['numberOfTestsWithIssues'],
$elements['numberOfIssues'],
'PHPUnit deprecation',
);
$this->printList($elements['elements']);
}
private function printTestRunnerWarnings(TestResult $result): void
{
if (!$result->hasTestRunnerTriggeredWarningEvents()) {
return;
}
$elements = [];
$messages = [];
foreach ($result->testRunnerTriggeredWarningEvents() as $event) {
if (isset($messages[$event->message()])) {
continue;
}
$elements[] = [
'title' => $event->message(),
'body' => '',
];
$messages[$event->message()] = true;
}
$this->printListHeaderWithNumber(count($elements), 'PHPUnit test runner warning');
$this->printList($elements);
}
private function printTestRunnerDeprecations(TestResult $result): void
{
if (!$result->hasTestRunnerTriggeredDeprecationEvents()) {
return;
}
$elements = [];
foreach ($result->testRunnerTriggeredDeprecationEvents() as $event) {
$elements[] = [
'title' => $event->message(),
'body' => '',
];
}
$this->printListHeaderWithNumber(count($elements), 'PHPUnit test runner deprecation');
$this->printList($elements);
}
private function printDetailsOnTestsThatTriggeredPhpunitWarnings(TestResult $result): void
{
if (!$result->hasTestTriggeredPhpunitWarningEvents()) {
return;
}
$elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitWarningEvents());
$this->printListHeaderWithNumberOfTestsAndNumberOfIssues(
$elements['numberOfTestsWithIssues'],
$elements['numberOfIssues'],
'PHPUnit warning',
);
$this->printList($elements['elements']);
}
private function printTestsWithErrors(TestResult $result): void
{
if (!$result->hasTestErroredEvents()) {
return;
}
$elements = [];
foreach ($result->testErroredEvents() as $event) {
if ($event instanceof AfterLastTestMethodErrored || $event instanceof BeforeFirstTestMethodErrored) {
$title = $event->testClassName();
} else {
$title = $this->name($event->test());
}
$elements[] = [
'title' => $title,
'body' => $event->throwable()->asString(),
];
}
$this->printListHeaderWithNumber(count($elements), 'error');
$this->printList($elements);
}
private function printTestsWithFailedAssertions(TestResult $result): void
{
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->printListHeaderWithNumber(count($elements), 'failure');
$this->printList($elements);
}
private function printRiskyTests(TestResult $result): void
{
if (!$result->hasTestConsideredRiskyEvents()) {
return;
}
$elements = $this->mapTestsWithIssuesEventsToElements($result->testConsideredRiskyEvents());
$this->printListHeaderWithNumber($elements['numberOfTestsWithIssues'], 'risky test');
$this->printList($elements['elements']);
}
private function printIncompleteTests(TestResult $result): void
{
if (!$result->hasTestMarkedIncompleteEvents()) {
return;
}
$elements = [];
foreach ($result->testMarkedIncompleteEvents() as $event) {
$elements[] = [
'title' => $this->name($event->test()),
'body' => $event->throwable()->asString(),
];
}
$this->printListHeaderWithNumber(count($elements), 'incomplete test');
$this->printList($elements);
}
private function printSkippedTestSuites(TestResult $result): void
{
if (!$result->hasTestSuiteSkippedEvents()) {
return;
}
$elements = [];
foreach ($result->testSuiteSkippedEvents() as $event) {
$elements[] = [
'title' => $event->testSuite()->name(),
'body' => $event->message(),
];
}
$this->printListHeaderWithNumber(count($elements), 'skipped test suite');
$this->printList($elements);
}
private function printSkippedTests(TestResult $result): void
{
if (!$result->hasTestSkippedEvents()) {
return;
}
$elements = [];
foreach ($result->testSkippedEvents() as $event) {
$elements[] = [
'title' => $this->name($event->test()),
'body' => $event->message(),
];
}
$this->printListHeaderWithNumber(count($elements), 'skipped test');
$this->printList($elements);
}
/**
* @psalm-param non-empty-string $type
* @psalm-param list