Source code for pytest_test_categories.timers
"""Real timer implementation for measuring test duration."""
from __future__ import annotations
import time
from pydantic import Field
from pytest_test_categories.types import (
TestTimer,
TimerState,
)
[docs]
class WallTimer(TestTimer):
"""Timer implementation using wall clock time.
This timer uses time.perf_counter() for high-resolution timing
that is not affected by system clock updates.
This is the production adapter that should be used in real pytest runs.
"""
[docs]
start_time: float | None = Field(default=None, description='Start time in seconds')
[docs]
end_time: float | None = Field(default=None, description='End time in seconds')
[docs]
def reset(self) -> None:
"""Reset the timer to initial state."""
self.state = TimerState.READY
self.start_time = None
self.end_time = None
[docs]
def start(self) -> None:
"""Start timing, recording the current time."""
if self.state != TimerState.READY:
self.reset() # Reset if not in ready state
super().start() # Parent handles state transition and contracts
self.start_time = time.perf_counter()
self.end_time = None
[docs]
def stop(self) -> None:
"""Stop timing, recording the end time."""
self.end_time = time.perf_counter()
super().stop() # Parent handles state transition and contracts
[docs]
def duration(self) -> float:
"""Calculate the duration in seconds.
Returns:
The duration in seconds with microsecond precision.
Raises:
RuntimeError: If called before both start and stop.
"""
if self.start_time is None:
msg = 'Timer was never started'
raise RuntimeError(msg)
if self.end_time is None:
msg = 'Timer was never stopped'
raise RuntimeError(msg)
return self.end_time - self.start_time
[docs]
class FakeTimer(TestTimer):
"""Controllable timer adapter for testing.
This is a test double that allows tests to control time explicitly
rather than depending on the system clock. This eliminates flaky
tests caused by timing variations and makes tests deterministic.
The FakeTimer follows hexagonal architecture principles:
- Implements the TestTimer port (interface)
- Provides controllable time advancement via advance()
- Used in tests as a substitute for WallTimer
- Enables testing behavior without implementation details
Example:
>>> timer = FakeTimer()
>>> timer.start()
>>> timer.advance(0.5) # Simulate 0.5 seconds
>>> timer.stop()
>>> assert timer.duration() == 0.5 # Exact, deterministic
"""
[docs]
current_time: float = Field(default=0.0, description='Simulated current time in seconds')
[docs]
start_time: float | None = Field(default=None, description='Simulated start time')
[docs]
end_time: float | None = Field(default=None, description='Simulated end time')
[docs]
def reset(self) -> None:
"""Reset the timer to initial state."""
self.state = TimerState.READY
self.start_time = None
self.end_time = None
self.current_time = 0.0
[docs]
def advance(self, seconds: float) -> None:
"""Advance the simulated clock by the specified duration.
Args:
seconds: Number of seconds to advance the clock.
"""
self.current_time += seconds
[docs]
def start(self) -> None:
"""Start timing, recording the simulated current time."""
if self.state != TimerState.READY:
self.reset() # Reset if not in ready state
super().start() # Parent handles state transition and contracts
self.start_time = self.current_time
self.end_time = None
[docs]
def stop(self) -> None:
"""Stop timing, recording the simulated end time."""
self.end_time = self.current_time
super().stop() # Parent handles state transition and contracts
[docs]
def duration(self) -> float:
"""Calculate the simulated duration in seconds.
Returns:
The simulated duration in seconds.
Raises:
RuntimeError: If called before both start and stop.
"""
if self.start_time is None:
msg = 'Timer was never started'
raise RuntimeError(msg)
if self.end_time is None:
msg = 'Timer was never stopped'
raise RuntimeError(msg)
return self.end_time - self.start_time