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