Skip to content

Commit

Permalink
Enhancement: Extract SlowTestList
Browse files Browse the repository at this point in the history
  • Loading branch information
localheinz committed Feb 17, 2025
1 parent 82f1335 commit 5f97eb6
Show file tree
Hide file tree
Showing 17 changed files with 574 additions and 123 deletions.
66 changes: 63 additions & 3 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ parameters:
count: 1
path: src/Collector/DefaultCollector.php

-
message: "#^Property Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Reporter\\\\DefaultReporter\\:\\:\\$maximumDuration is never read, only written\\.$#"
count: 1
path: src/Reporter/DefaultReporter.php

-
message: "#^Offset 'major' does not exist on array\\{0\\?\\: string, major\\?\\: numeric\\-string, 1\\?\\: numeric\\-string, 2\\?\\: '0'\\|\\(non\\-falsy\\-string&numeric\\-string\\), minor\\?\\: ''\\|numeric\\-string, 3\\?\\: ''\\|numeric\\-string, 4\\?\\: '0'\\|\\(non\\-falsy\\-string&numeric\\-string\\)\\}\\.$#"
count: 1
Expand Down Expand Up @@ -110,6 +115,16 @@ parameters:
count: 1
path: test/Unit/Console/ColorTest.php

-
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\CountTest\\:\\:testEqualsReturnsFalseWhenValueIsDifferent\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/CountTest.php

-
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\CountTest\\:\\:testEqualsReturnsTrueWhenValueIsSame\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/CountTest.php

-
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\CountTest\\:\\:testFromIntRejectsInvalidValue\\(\\) has no return type specified\\.$#"
count: 1
Expand Down Expand Up @@ -206,7 +221,7 @@ parameters:
path: test/Unit/DurationTest.php

-
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\Exception\\\\InvalidCountTest\\:\\:testNotGreaterThanZeroReturnsException\\(\\) has no return type specified\\.$#"
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\Exception\\\\InvalidCountTest\\:\\:testNotGreaterThanOrEqualToZeroReturnsException\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/Exception/InvalidCountTest.php

Expand Down Expand Up @@ -260,6 +275,11 @@ parameters:
count: 1
path: test/Unit/Exception/PhaseNotStartedTest.php

-
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\Exception\\\\SlowTestListIsEmptyTest\\:\\:testFromPhaseIdentifierReturnsException\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/Exception/SlowTestListIsEmptyTest.php

-
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\Formatter\\\\DefaultDurationFormatterTest\\:\\:testFormatFormats\\(\\) has no return type specified\\.$#"
count: 1
Expand All @@ -286,15 +306,55 @@ parameters:
path: test/Unit/PhaseTest.php

-
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\Reporter\\\\DefaultReporterTest\\:\\:testReportReturnsEmptyStringWhenThereAreNoSlowTests\\(\\) has no return type specified\\.$#"
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\Reporter\\\\DefaultReporterTest\\:\\:testReportReturnsEmptyStringWhenSlowTestListIsEmpty\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/Reporter/DefaultReporterTest.php

-
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\Reporter\\\\DefaultReporterTest\\:\\:testReportReturnsReportWhenThereAreFewerSlowTestsThanMaximumCount\\(\\) has no return type specified\\.$#"
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\Reporter\\\\DefaultReporterTest\\:\\:testReportReturnsReportWhenSlowTestListHasFewerSlowTestsThanMaximumCount\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/Reporter/DefaultReporterTest.php

-
message: "#^Method SlowTestListTest\\:\\:testCountReturnsNumberOfSlowTests\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/SlowTestListTest.php

-
message: "#^Method SlowTestListTest\\:\\:testCreateReturnsSlowTestList\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/SlowTestListTest.php

-
message: "#^Method SlowTestListTest\\:\\:testIsEmptyReturnsFalseWhenSlowTestListIsNotEmpty\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/SlowTestListTest.php

-
message: "#^Method SlowTestListTest\\:\\:testIsEmptyReturnsTrueWhenSlowTestListIsEmpty\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/SlowTestListTest.php

-
message: "#^Method SlowTestListTest\\:\\:testLimitReturnsSlowTestListWhenSlowTestListHasFewerSlowTests\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/SlowTestListTest.php

-
message: "#^Method SlowTestListTest\\:\\:testLimitReturnsSlowTestListWhenSlowTestListHasMoreSlowTests\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/SlowTestListTest.php

-
message: "#^Method SlowTestListTest\\:\\:testSliceReturnsTestListWhenSlowTestListIsEmpty\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/SlowTestListTest.php

-
message: "#^Method SlowTestListTest\\:\\:testSortReturnsSlowTestListWhereSlowTestsAreSorted\\(\\) has no return type specified\\.$#"
count: 1
path: test/Unit/SlowTestListTest.php

-
message: "#^Method Ergebnis\\\\PHPUnit\\\\SlowTestDetector\\\\Test\\\\Unit\\\\SlowTestTest\\:\\:testCreateReturnsSlowTest\\(\\) has no return type specified\\.$#"
count: 1
Expand Down
8 changes: 2 additions & 6 deletions src/Collector/Collector.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Ergebnis\PHPUnit\SlowTestDetector\Collector;

use Ergebnis\PHPUnit\SlowTestDetector\SlowTest;
use Ergebnis\PHPUnit\SlowTestDetector\SlowTestList;

/**
* @internal
Expand All @@ -22,10 +23,5 @@ interface Collector
{
public function collectSlowTest(SlowTest $slowTest);

/**
* @phpstan-return list<SlowTest>
*
* @return list<SlowTest>
*/
public function collected(): array;
public function slowTestList(): SlowTestList;
}
10 changes: 3 additions & 7 deletions src/Collector/DefaultCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Ergebnis\PHPUnit\SlowTestDetector\Collector;

use Ergebnis\PHPUnit\SlowTestDetector\SlowTest;
use Ergebnis\PHPUnit\SlowTestDetector\SlowTestList;

/**
* @internal
Expand Down Expand Up @@ -44,13 +45,8 @@ public function collectSlowTest(SlowTest $slowTest)
$this->slowTests[$key] = $slowTest;
}

/**
* @phpstan-return list<SlowTest>
*
* @return list<SlowTest>
*/
public function collected(): array
public function slowTestList(): SlowTestList
{
return \array_values($this->slowTests);
return SlowTestList::create(...\array_values($this->slowTests));
}
}
9 changes: 7 additions & 2 deletions src/Count.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,18 @@ private function __construct(int $value)
*/
public static function fromInt(int $value): self
{
if (0 >= $value) {
throw Exception\InvalidCount::notGreaterThanZero($value);
if (0 > $value) {
throw Exception\InvalidCount::notGreaterThanOrEqualToZero($value);
}

return new self($value);
}

public function equals(self $other): bool
{
return $this->value === $other->value;
}

public function toInt(): int
{
return $this->value;
Expand Down
4 changes: 2 additions & 2 deletions src/Exception/InvalidCount.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
*/
final class InvalidCount extends \InvalidArgumentException
{
public static function notGreaterThanZero(int $value): self
public static function notGreaterThanOrEqualToZero(int $value): self
{
return new self(\sprintf(
'Value should be greater than 0, but %d is not.',
'Value should be greater than or equal to 0, but %d is not.',
$value
));
}
Expand Down
25 changes: 25 additions & 0 deletions src/Exception/SlowTestListIsEmpty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2021-2025 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/phpunit-slow-test-detector
*/

namespace Ergebnis\PHPUnit\SlowTestDetector\Exception;

/**
* @internal
*/
final class SlowTestListIsEmpty extends \RuntimeException
{
public static function create(): self
{
return new self('Slow test list is empty.');
}
}
12 changes: 6 additions & 6 deletions src/Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ public function endTestSuite(Framework\TestSuite $suite)
return;
}

$slowTests = $this->collector->collected();
$slowTestList = $this->collector->slowTestList();

Check warning on line 132 in src/Extension.php

View check run for this annotation

Codecov / codecov/patch

src/Extension.php#L132

Added line #L132 was not covered by tests

if ([] === $slowTests) {
if ($slowTestList->isEmpty()) {

Check warning on line 134 in src/Extension.php

View check run for this annotation

Codecov / codecov/patch

src/Extension.php#L134

Added line #L134 was not covered by tests
return;
}

$report = $this->reporter->report(...$slowTests);
$report = $this->reporter->report($slowTestList);

Check warning on line 138 in src/Extension.php

View check run for this annotation

Codecov / codecov/patch

src/Extension.php#L138

Added line #L138 was not covered by tests

if ('' === $report) {
return;
Expand Down Expand Up @@ -342,13 +342,13 @@ public function executeAfterLastTest(): void
return;
}

$slowTests = $this->collector->collected();
$slowTestList = $this->collector->slowTestList();

Check warning on line 345 in src/Extension.php

View check run for this annotation

Codecov / codecov/patch

src/Extension.php#L345

Added line #L345 was not covered by tests

if ([] === $slowTests) {
if ($slowTestList->isEmpty()) {

Check warning on line 347 in src/Extension.php

View check run for this annotation

Codecov / codecov/patch

src/Extension.php#L347

Added line #L347 was not covered by tests
return;
}

$report = $this->reporter->report(...$slowTests);
$report = $this->reporter->report($slowTestList);

Check warning on line 351 in src/Extension.php

View check run for this annotation

Codecov / codecov/patch

src/Extension.php#L351

Added line #L351 was not covered by tests

if ('' === $report) {
return;
Expand Down
89 changes: 43 additions & 46 deletions src/Reporter/DefaultReporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Ergebnis\PHPUnit\SlowTestDetector\Duration;
use Ergebnis\PHPUnit\SlowTestDetector\Formatter;
use Ergebnis\PHPUnit\SlowTestDetector\SlowTest;
use Ergebnis\PHPUnit\SlowTestDetector\SlowTestList;

/**
* @internal
Expand Down Expand Up @@ -55,15 +56,15 @@ public function __construct(
$this->durationComparator = new Comparator\DurationComparator();
}

public function report(SlowTest ...$slowTests): string
public function report(SlowTestList $slowTestList): string
{
if ([] === $slowTests) {
if ($slowTestList->isEmpty()) {
return '';
}

$header = $this->header(...$slowTests);
$list = $this->list(...$slowTests);
$footer = $this->footer(...$slowTests);
$header = $this->header($slowTestList);
$list = $this->list($slowTestList);
$footer = $this->footer($slowTestList);

if ('' === $footer) {
return <<<TXT
Expand All @@ -79,60 +80,56 @@ public function report(SlowTest ...$slowTests): string
TXT;
}

private function header(SlowTest ...$slowTests): string
private function header(SlowTestList $slowTestList): string
{
$count = \count($slowTests);
$count = $slowTestList->count();

if (1 === $count) {
return <<<TXT
Detected {$count} test where the duration exceeded the maximum duration.
if ($count->equals(Count::fromInt(1))) {
return <<<'TXT'
Detected 1 test where the duration exceeded the maximum duration.

TXT;
}

return <<<TXT
Detected {$count} tests where the duration exceeded the maximum duration.
return \sprintf(
<<<'TXT'
Detected %d tests where the duration exceeded the maximum duration.

TXT;
TXT
,
$count->toInt()
);
}

private function list(SlowTest ...$slowTests): string
private function list(SlowTestList $slowTestList): string
{
$durationComparator = $this->durationComparator;

\usort($slowTests, static function (SlowTest $one, SlowTest $two) use ($durationComparator): int {
return $durationComparator->compare(
$two->duration(),
$one->duration()
);
});

$slowTestsToReport = \array_slice(
$slowTests,
0,
$this->maximumCount->toInt()
);

/** @var SlowTest $slowTestWithLongestDuration */
$slowTestWithLongestDuration = \reset($slowTestsToReport);

$longestMaximumDuration = \array_reduce(
$slowTestsToReport,
static function (Duration $maximumDuration, SlowTest $slowTest): Duration {
if ($maximumDuration->isLessThan($slowTest->maximumDuration())) {
return $slowTest->maximumDuration();
}

return $maximumDuration;
},
$this->maximumDuration
);
$slowTestListThatWillBeReported = $slowTestList
->sort(static function (SlowTest $one, SlowTest $two) use ($durationComparator): int {
return $durationComparator->compare(
$two->duration(),
$one->duration()
);
})
->limit($this->maximumCount);

$slowTestWithLongestDuration = $slowTestListThatWillBeReported->first();

$slowTestWithLongestMaximumDuration = $slowTestListThatWillBeReported
->sort(static function (SlowTest $one, SlowTest $two) use ($durationComparator): int {
return $durationComparator->compare(
$two->maximumDuration(),
$one->maximumDuration()
);
})
->first();

$durationFormatter = $this->durationFormatter;

$numberWidth = \strlen((string) \count($slowTestsToReport));
$numberWidth = \strlen((string) $slowTestListThatWillBeReported->count()->toInt());
$durationWidth = \strlen($durationFormatter->format($slowTestWithLongestDuration->duration()));
$maximumDurationWidth = \strlen($durationFormatter->format($longestMaximumDuration));
$maximumDurationWidth = \strlen($durationFormatter->format($slowTestWithLongestMaximumDuration->maximumDuration()));

$items = \array_map(static function (int $number, SlowTest $slowTest) use ($numberWidth, $durationFormatter, $durationWidth, $maximumDurationWidth): string {
$formattedNumber = \str_pad(
Expand Down Expand Up @@ -164,18 +161,18 @@ static function (Duration $maximumDuration, SlowTest $slowTest): Duration {
return <<<TXT
{$formattedNumber}. {$formattedDuration} {$formattedMaximumDuration} {$testDescription}
TXT;
}, \range(1, \count($slowTestsToReport)), $slowTestsToReport);
}, \range(1, $slowTestListThatWillBeReported->count()->toInt()), $slowTestListThatWillBeReported->toArray());

return \implode(
"\n",
$items
);
}

private function footer(SlowTest ...$slowTests): string
private function footer(SlowTestList $slowTestList): string
{
$additionalSlowTestCount = \max(
\count($slowTests) - $this->maximumCount->toInt(),
$slowTestList->count()->toInt() - $this->maximumCount->toInt(),
0
);

Expand Down
Loading

0 comments on commit 5f97eb6

Please sign in to comment.