You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
8.0 KiB
267 lines
8.0 KiB
<?php declare(strict_types=1);
|
|
/*
|
|
* This file is part of PHPUnit.
|
|
*
|
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
namespace PHPUnit\Logging\TestDox;
|
|
|
|
use function array_keys;
|
|
use function array_merge;
|
|
use function assert;
|
|
use function is_subclass_of;
|
|
use function ksort;
|
|
use function uksort;
|
|
use function usort;
|
|
use PHPUnit\Event\Code\TestMethod;
|
|
use PHPUnit\Event\Code\Throwable;
|
|
use PHPUnit\Event\EventFacadeIsSealedException;
|
|
use PHPUnit\Event\Facade;
|
|
use PHPUnit\Event\InvalidArgumentException;
|
|
use PHPUnit\Event\Telemetry\HRTime;
|
|
use PHPUnit\Event\Test\ConsideredRisky;
|
|
use PHPUnit\Event\Test\Errored;
|
|
use PHPUnit\Event\Test\Failed;
|
|
use PHPUnit\Event\Test\Finished;
|
|
use PHPUnit\Event\Test\MarkedIncomplete;
|
|
use PHPUnit\Event\Test\MockObjectCreated;
|
|
use PHPUnit\Event\Test\MockObjectForAbstractClassCreated;
|
|
use PHPUnit\Event\Test\MockObjectForTraitCreated;
|
|
use PHPUnit\Event\Test\MockObjectFromWsdlCreated;
|
|
use PHPUnit\Event\Test\PartialMockObjectCreated;
|
|
use PHPUnit\Event\Test\Passed;
|
|
use PHPUnit\Event\Test\Prepared;
|
|
use PHPUnit\Event\Test\Skipped;
|
|
use PHPUnit\Event\Test\TestProxyCreated;
|
|
use PHPUnit\Event\Test\TestStubCreated;
|
|
use PHPUnit\Event\UnknownSubscriberTypeException;
|
|
use PHPUnit\Framework\TestStatus\TestStatus;
|
|
use PHPUnit\Logging\TestDox\TestResult as TestDoxTestMethod;
|
|
use ReflectionMethod;
|
|
use SoapClient;
|
|
|
|
/**
|
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
|
*/
|
|
final class TestResultCollector
|
|
{
|
|
/**
|
|
* @psalm-var array<string, list<TestDoxTestMethod>>
|
|
*/
|
|
private array $tests = [];
|
|
private ?HRTime $time = null;
|
|
private ?TestStatus $status = null;
|
|
private ?Throwable $throwable = null;
|
|
|
|
/**
|
|
* @psalm-var list<class-string|trait-string>
|
|
*/
|
|
private array $testDoubles = [];
|
|
|
|
/**
|
|
* @throws EventFacadeIsSealedException
|
|
* @throws UnknownSubscriberTypeException
|
|
*/
|
|
public function __construct(Facade $facade)
|
|
{
|
|
$this->registerSubscribers($facade);
|
|
}
|
|
|
|
/**
|
|
* @psalm-return array<string, TestResultCollection>
|
|
*/
|
|
public function testMethodsGroupedByClass(): array
|
|
{
|
|
$result = [];
|
|
|
|
foreach ($this->tests as $prettifiedClassName => $tests) {
|
|
$testsByDeclaringClass = [];
|
|
|
|
foreach ($tests as $test) {
|
|
$declaringClassName = (new ReflectionMethod($test->test()->className(), $test->test()->methodName()))->getDeclaringClass()->getName();
|
|
|
|
if (!isset($testsByDeclaringClass[$declaringClassName])) {
|
|
$testsByDeclaringClass[$declaringClassName] = [];
|
|
}
|
|
|
|
$testsByDeclaringClass[$declaringClassName][] = $test;
|
|
}
|
|
|
|
foreach (array_keys($testsByDeclaringClass) as $declaringClassName) {
|
|
usort(
|
|
$testsByDeclaringClass[$declaringClassName],
|
|
static function (TestDoxTestMethod $a, TestDoxTestMethod $b): int
|
|
{
|
|
return $a->test()->line() <=> $b->test()->line();
|
|
},
|
|
);
|
|
}
|
|
|
|
uksort(
|
|
$testsByDeclaringClass,
|
|
/**
|
|
* @psalm-param class-string $a
|
|
* @psalm-param class-string $b
|
|
*/
|
|
static function (string $a, string $b): int
|
|
{
|
|
if (is_subclass_of($b, $a)) {
|
|
return -1;
|
|
}
|
|
|
|
if (is_subclass_of($a, $b)) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
);
|
|
|
|
$tests = [];
|
|
|
|
foreach ($testsByDeclaringClass as $_tests) {
|
|
$tests = array_merge($tests, $_tests);
|
|
}
|
|
|
|
$result[$prettifiedClassName] = TestResultCollection::fromArray($tests);
|
|
}
|
|
|
|
ksort($result);
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function testPrepared(Prepared $event): void
|
|
{
|
|
if (!$event->test()->isTestMethod()) {
|
|
return;
|
|
}
|
|
|
|
$this->time = $event->telemetryInfo()->time();
|
|
$this->status = TestStatus::unknown();
|
|
$this->throwable = null;
|
|
$this->testDoubles = [];
|
|
}
|
|
|
|
public function testErrored(Errored $event): void
|
|
{
|
|
if (!$event->test()->isTestMethod()) {
|
|
return;
|
|
}
|
|
|
|
$this->status = TestStatus::error($event->throwable()->message());
|
|
$this->throwable = $event->throwable();
|
|
}
|
|
|
|
public function testFailed(Failed $event): void
|
|
{
|
|
if (!$event->test()->isTestMethod()) {
|
|
return;
|
|
}
|
|
|
|
$this->status = TestStatus::failure($event->throwable()->message());
|
|
$this->throwable = $event->throwable();
|
|
}
|
|
|
|
public function testPassed(Passed $event): void
|
|
{
|
|
if (!$event->test()->isTestMethod()) {
|
|
return;
|
|
}
|
|
|
|
$this->status = TestStatus::success();
|
|
}
|
|
|
|
public function testSkipped(Skipped $event): void
|
|
{
|
|
$this->status = TestStatus::skipped($event->message());
|
|
}
|
|
|
|
public function testMarkedIncomplete(MarkedIncomplete $event): void
|
|
{
|
|
$this->status = TestStatus::incomplete($event->throwable()->message());
|
|
$this->throwable = $event->throwable();
|
|
}
|
|
|
|
public function testConsideredRisky(ConsideredRisky $event): void
|
|
{
|
|
$this->status = TestStatus::risky($event->message());
|
|
}
|
|
|
|
public function testCreatedTestDouble(MockObjectCreated|MockObjectForAbstractClassCreated|MockObjectForTraitCreated|MockObjectFromWsdlCreated|PartialMockObjectCreated|TestProxyCreated|TestStubCreated $event): void
|
|
{
|
|
if ($event instanceof MockObjectForTraitCreated) {
|
|
$this->testDoubles[] = $event->traitName();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($event instanceof MockObjectFromWsdlCreated) {
|
|
$this->testDoubles[] = SoapClient::class;
|
|
|
|
return;
|
|
}
|
|
|
|
$this->testDoubles[] = $event->className();
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function testFinished(Finished $event): void
|
|
{
|
|
if (!$event->test()->isTestMethod()) {
|
|
return;
|
|
}
|
|
|
|
$test = $event->test();
|
|
|
|
assert($test instanceof TestMethod);
|
|
|
|
if (!isset($this->tests[$test->testDox()->prettifiedClassName()])) {
|
|
$this->tests[$test->testDox()->prettifiedClassName()] = [];
|
|
}
|
|
|
|
$this->tests[$test->testDox()->prettifiedClassName()][] = new TestDoxTestMethod(
|
|
$test,
|
|
$event->telemetryInfo()->time()->duration($this->time),
|
|
$this->status,
|
|
$this->throwable,
|
|
$this->testDoubles,
|
|
);
|
|
|
|
$this->time = null;
|
|
$this->status = null;
|
|
$this->throwable = null;
|
|
$this->testDoubles = [];
|
|
}
|
|
|
|
/**
|
|
* @throws EventFacadeIsSealedException
|
|
* @throws UnknownSubscriberTypeException
|
|
*/
|
|
private function registerSubscribers(Facade $facade): void
|
|
{
|
|
$facade->registerSubscribers(
|
|
new TestConsideredRiskySubscriber($this),
|
|
new TestCreatedMockObjectForAbstractClassSubscriber($this),
|
|
new TestCreatedMockObjectForTraitSubscriber($this),
|
|
new TestCreatedMockObjectFromWsdlSubscriber($this),
|
|
new TestCreatedMockObjectSubscriber($this),
|
|
new TestCreatedPartialMockObjectSubscriber($this),
|
|
new TestCreatedTestProxySubscriber($this),
|
|
new TestCreatedTestStubSubscriber($this),
|
|
new TestErroredSubscriber($this),
|
|
new TestFailedSubscriber($this),
|
|
new TestFinishedSubscriber($this),
|
|
new TestMarkedIncompleteSubscriber($this),
|
|
new TestPassedSubscriber($this),
|
|
new TestPreparedSubscriber($this),
|
|
new TestSkippedSubscriber($this),
|
|
);
|
|
}
|
|
}
|