now()); } public function testSystemClockReturnsCurrentTime(): void { $before = new \DateTimeImmutable(); $now = (new SystemClock())->now(); $after = new \DateTimeImmutable(); self::assertGreaterThanOrEqual($before->getTimestamp(), $now->getTimestamp()); self::assertLessThanOrEqual($after->getTimestamp(), $now->getTimestamp()); } // ------------------------------------------------------------------------- // FrozenClock // ------------------------------------------------------------------------- public function testFrozenClockImplementsInterface(): void { $clock = new FrozenClock(new \DateTimeImmutable('2026-01-01 12:00:00')); self::assertInstanceOf(ClockInterface::class, $clock); } public function testFrozenClockAlwaysReturnsSameTime(): void { $fixed = new \DateTimeImmutable('2026-01-01 12:00:00'); $clock = new FrozenClock($fixed); self::assertSame($fixed, $clock->now()); self::assertSame($fixed, $clock->now()); } public function testFrozenClockSetAdvancesTime(): void { $clock = new FrozenClock(new \DateTimeImmutable('2026-01-01 12:00:00')); $later = new \DateTimeImmutable('2026-06-15 08:30:00'); $clock->set($later); self::assertSame($later, $clock->now()); } public function testFrozenClockIsUsableInTypedContext(): void { $clock = new FrozenClock(new \DateTimeImmutable('2026-01-01 00:00:00')); $timestamp = $this->readTimestamp($clock); self::assertSame('2026-01-01 00:00:00', $timestamp); } private function readTimestamp(ClockInterface $clock): string { return $clock->now()->format('Y-m-d H:i:s'); } // ------------------------------------------------------------------------- // Stopwatch // ------------------------------------------------------------------------- public function testStopwatchStartReturnsInstance(): void { self::assertInstanceOf(Stopwatch::class, Stopwatch::start()); } public function testStopwatchElapsedIsNonNegative(): void { $sw = Stopwatch::start(); self::assertGreaterThanOrEqual(0.0, $sw->elapsed()); } public function testStopwatchElapsedMsIsNonNegative(): void { $sw = Stopwatch::start(); self::assertGreaterThanOrEqual(0.0, $sw->elapsedMs()); } public function testStopwatchElapsedMsIsLargerThanElapsedSeconds(): void { $sw = Stopwatch::start(); // A non-trivial amount of time: elapsed in ms should be > in seconds // for any time >= 1ms (both should be close to zero but ms > s for small values) $elapsedSec = $sw->elapsed(); $elapsedMs = $sw->elapsedMs(); // For any positive time: ms value >= s value * 1 (since 1ms = 0.001s) // This holds as long as elapsed < 1 second (which it will be in a test) self::assertGreaterThanOrEqual($elapsedSec, $elapsedMs); } public function testStopwatchResetMakesElapsedNearZero(): void { $sw = Stopwatch::start(); // Let some time pass conceptually (we can't sleep), then reset $sw->reset(); self::assertLessThan(0.01, $sw->elapsed()); // under 10ms } public function testStopwatchLapReturnsNonNegative(): void { $sw = Stopwatch::start(); self::assertGreaterThanOrEqual(0.0, $sw->lap()); } public function testStopwatchSubsequentLapsAreIndependent(): void { $sw = Stopwatch::start(); $lap1 = $sw->lap(); $lap2 = $sw->lap(); // Both laps are non-negative and their sum should not exceed total elapsed self::assertGreaterThanOrEqual(0.0, $lap1); self::assertGreaterThanOrEqual(0.0, $lap2); } public function testStopwatchLapDoesNotResetTotalElapsed(): void { $sw = Stopwatch::start(); $sw->lap(); $total = $sw->elapsed(); self::assertGreaterThanOrEqual(0.0, $total); } }