From 3a89f512af3291247037792828e091d71eb1bd66 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Wed, 30 Aug 2023 16:36:59 +0800 Subject: [PATCH] Add limit metadata --- CHANGELOG.md | 1 + src/Metadata/Limit.php | 28 +++++++ src/Metadata/LimitCollection.php | 94 ++++++++++++++++++++++++ src/Metadata/LimitCollectionIterator.php | 59 +++++++++++++++ src/Metadata/NoTimeLimitForClass.php | 41 +++++++++++ src/Metadata/NoTimeLimitForMethod.php | 41 +++++++++++ src/Metadata/TimeLimitForClass.php | 50 +++++++++++++ src/Metadata/TimeLimitForMethod.php | 50 +++++++++++++ tests/unit/Metadata/LimitTest.php | 89 ++++++++++++++++++++++ 9 files changed, 453 insertions(+) create mode 100644 src/Metadata/Limit.php create mode 100644 src/Metadata/LimitCollection.php create mode 100644 src/Metadata/LimitCollectionIterator.php create mode 100644 src/Metadata/NoTimeLimitForClass.php create mode 100644 src/Metadata/NoTimeLimitForMethod.php create mode 100644 src/Metadata/TimeLimitForClass.php create mode 100644 src/Metadata/TimeLimitForMethod.php create mode 100644 tests/unit/Metadata/LimitTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d1a6f..fc71de2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Added `DurationFormatter` class - Added parameter value objects: `Limit`, `Precision`, `ReportCount` +- Added `Limit` metadata objects - Added `SlowTest` object and its collection - Added `Stopwatch` class diff --git a/src/Metadata/Limit.php b/src/Metadata/Limit.php new file mode 100644 index 0000000..b25a9fd --- /dev/null +++ b/src/Metadata/Limit.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Nexus\PHPUnit\Tachycardia\Metadata; + +use PHPUnit\Event\Telemetry\Duration; + +/** + * @internal + */ +interface Limit +{ + public function hasTimeLimit(): bool; + + public function getTimeLimit(): Duration; + + public function isMoreImportantThan(self $other): bool; +} diff --git a/src/Metadata/LimitCollection.php b/src/Metadata/LimitCollection.php new file mode 100644 index 0000000..b5bc9aa --- /dev/null +++ b/src/Metadata/LimitCollection.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Nexus\PHPUnit\Tachycardia\Metadata; + +use Nexus\PHPUnit\Tachycardia\Parameter\Limit as LimitParameter; + +/** + * @internal + * + * @immutable + * + * @implements \IteratorAggregate + */ +final class LimitCollection implements \Countable, \IteratorAggregate +{ + /** + * @var array + */ + private readonly array $limits; + + private function __construct(Limit ...$limits) + { + $this->limits = array_values($limits); + } + + /** + * @param array $limits + */ + public static function fromArray(array $limits): self + { + return new self(...$limits); + } + + /** + * @return array + */ + public function asArray(): array + { + return $this->limits; + } + + public function count(): int + { + return \count($this->limits); + } + + public function empty(): bool + { + return [] === $this->limits; + } + + public function getIterator(): LimitCollectionIterator + { + return new LimitCollectionIterator($this); + } + + public function mergeWith(self $other): self + { + return new self(...[ + ...$this->asArray(), + ...$other->asArray(), + ]); + } + + public function reduce(LimitParameter $limitParameter): Limit + { + return array_reduce( + $this->limits, + static function (?Limit $initial, Limit $limit): Limit { + if ($initial === null) { + return $limit; + } + + if ($limit->isMoreImportantThan($initial)) { + return $limit; + } + + return $initial; + }, + null + ) ?? new TimeLimitForMethod($limitParameter->duration()->asFloat()); + } +} diff --git a/src/Metadata/LimitCollectionIterator.php b/src/Metadata/LimitCollectionIterator.php new file mode 100644 index 0000000..871499e --- /dev/null +++ b/src/Metadata/LimitCollectionIterator.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Nexus\PHPUnit\Tachycardia\Metadata; + +/** + * @internal + * + * @implements \Iterator + */ +final class LimitCollectionIterator implements \Iterator +{ + /** + * @var array + */ + private readonly array $limits; + + private int $position = 0; + + public function __construct(LimitCollection $limitCollection) + { + $this->limits = $limitCollection->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < \count($this->limits); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Limit + { + return $this->limits[$this->position]; + } + + public function next(): void + { + ++$this->position; + } +} diff --git a/src/Metadata/NoTimeLimitForClass.php b/src/Metadata/NoTimeLimitForClass.php new file mode 100644 index 0000000..da5ce4b --- /dev/null +++ b/src/Metadata/NoTimeLimitForClass.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Nexus\PHPUnit\Tachycardia\Metadata; + +use PHPUnit\Event\Telemetry\Duration; + +/** + * @internal + */ +final class NoTimeLimitForClass implements Limit +{ + public function hasTimeLimit(): bool + { + return false; + } + + public function getTimeLimit(): Duration + { + return Duration::fromSecondsAndNanoseconds(0, 0); + } + + public function isMoreImportantThan(Limit $other): bool + { + if ($other->hasTimeLimit()) { + return true; + } + + return ! $other instanceof NoTimeLimitForMethod; + } +} diff --git a/src/Metadata/NoTimeLimitForMethod.php b/src/Metadata/NoTimeLimitForMethod.php new file mode 100644 index 0000000..f72575e --- /dev/null +++ b/src/Metadata/NoTimeLimitForMethod.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Nexus\PHPUnit\Tachycardia\Metadata; + +use PHPUnit\Event\Telemetry\Duration; + +/** + * @internal + */ +final class NoTimeLimitForMethod implements Limit +{ + public function hasTimeLimit(): bool + { + return false; + } + + public function getTimeLimit(): Duration + { + return Duration::fromSecondsAndNanoseconds(0, 0); + } + + public function isMoreImportantThan(Limit $other): bool + { + if ($other->hasTimeLimit()) { + return true; + } + + return $other instanceof NoTimeLimitForClass; + } +} diff --git a/src/Metadata/TimeLimitForClass.php b/src/Metadata/TimeLimitForClass.php new file mode 100644 index 0000000..cff101e --- /dev/null +++ b/src/Metadata/TimeLimitForClass.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Nexus\PHPUnit\Tachycardia\Metadata; + +use Nexus\PHPUnit\Tachycardia\Parameter\Limit as LimitParameter; +use PHPUnit\Event\Telemetry\Duration; + +/** + * @internal + */ +final class TimeLimitForClass implements Limit +{ + public function __construct( + private readonly float $seconds, + ) {} + + public function hasTimeLimit(): bool + { + return true; + } + + public function getTimeLimit(): Duration + { + return LimitParameter::fromSeconds($this->seconds)->duration(); + } + + public function isMoreImportantThan(Limit $other): bool + { + if (! $other->hasTimeLimit()) { + return false; + } + + if ($other instanceof TimeLimitForMethod) { + return false; + } + + return $this->getTimeLimit()->isLessThan($other->getTimeLimit()); + } +} diff --git a/src/Metadata/TimeLimitForMethod.php b/src/Metadata/TimeLimitForMethod.php new file mode 100644 index 0000000..386fcdf --- /dev/null +++ b/src/Metadata/TimeLimitForMethod.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Nexus\PHPUnit\Tachycardia\Metadata; + +use Nexus\PHPUnit\Tachycardia\Parameter\Limit as LimitParameter; +use PHPUnit\Event\Telemetry\Duration; + +/** + * @internal + */ +final class TimeLimitForMethod implements Limit +{ + public function __construct( + private readonly float $seconds, + ) {} + + public function hasTimeLimit(): bool + { + return true; + } + + public function getTimeLimit(): Duration + { + return LimitParameter::fromSeconds($this->seconds)->duration(); + } + + public function isMoreImportantThan(Limit $other): bool + { + if (! $other->hasTimeLimit()) { + return false; + } + + if ($other instanceof TimeLimitForClass) { + return true; + } + + return $this->getTimeLimit()->isLessThan($other->getTimeLimit()); + } +} diff --git a/tests/unit/Metadata/LimitTest.php b/tests/unit/Metadata/LimitTest.php new file mode 100644 index 0000000..f90aa90 --- /dev/null +++ b/tests/unit/Metadata/LimitTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Nexus\PHPUnit\Tachycardia\Tests\Metadata; + +use Nexus\PHPUnit\Tachycardia\Metadata\NoTimeLimitForClass; +use Nexus\PHPUnit\Tachycardia\Metadata\NoTimeLimitForMethod; +use Nexus\PHPUnit\Tachycardia\Metadata\TimeLimitForClass; +use Nexus\PHPUnit\Tachycardia\Metadata\TimeLimitForMethod; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +/** + * @internal + */ +#[CoversClass(NoTimeLimitForClass::class)] +#[CoversClass(NoTimeLimitForMethod::class)] +#[CoversClass(TimeLimitForClass::class)] +#[CoversClass(TimeLimitForMethod::class)] +final class LimitTest extends TestCase +{ + public function testLimitMetadataTimeLimits(): void + { + $a = new NoTimeLimitForClass(); + $b = new NoTimeLimitForMethod(); + $c = new TimeLimitForClass(2.5); + $d = new TimeLimitForMethod(1.0); + + self::assertFalse($a->hasTimeLimit()); + self::assertFalse($b->hasTimeLimit()); + self::assertTrue($c->hasTimeLimit()); + self::assertTrue($d->hasTimeLimit()); + + self::assertSame(0.0, $a->getTimeLimit()->asFloat()); + self::assertSame(0.0, $b->getTimeLimit()->asFloat()); + self::assertSame(2.5, $c->getTimeLimit()->asFloat()); + self::assertSame(1.0, $d->getTimeLimit()->asFloat()); + } + + public function testLimitMetadataImportance(): void + { + $a = new NoTimeLimitForClass(); + $b = new NoTimeLimitForMethod(); + $c = new TimeLimitForClass(2.5); + $d = new TimeLimitForClass(2.0); + $e = new TimeLimitForMethod(1.5); + $f = new TimeLimitForMethod(1.0); + + self::assertFalse($a->isMoreImportantThan($b)); + self::assertTrue($a->isMoreImportantThan($c)); + self::assertTrue($a->isMoreImportantThan($d)); + self::assertTrue($a->isMoreImportantThan($e)); + self::assertTrue($a->isMoreImportantThan($f)); + + self::assertTrue($b->isMoreImportantThan($a)); + self::assertTrue($b->isMoreImportantThan($c)); + self::assertTrue($b->isMoreImportantThan($d)); + self::assertTrue($b->isMoreImportantThan($e)); + self::assertTrue($b->isMoreImportantThan($f)); + + self::assertFalse($c->isMoreImportantThan($a)); + self::assertFalse($c->isMoreImportantThan($b)); + self::assertFalse($c->isMoreImportantThan($d)); + self::assertFalse($c->isMoreImportantThan($e)); + self::assertFalse($c->isMoreImportantThan($f)); + + self::assertFalse($d->isMoreImportantThan($a)); + self::assertFalse($d->isMoreImportantThan($b)); + self::assertTrue($d->isMoreImportantThan($c)); + self::assertFalse($d->isMoreImportantThan($e)); + self::assertFalse($d->isMoreImportantThan($f)); + + self::assertFalse($e->isMoreImportantThan($a)); + self::assertFalse($e->isMoreImportantThan($b)); + self::assertTrue($e->isMoreImportantThan($c)); + self::assertTrue($e->isMoreImportantThan($d)); + self::assertFalse($e->isMoreImportantThan($f)); + } +}