Coverage for src / mcp_server_langgraph / core / time_provider.py: 88%
46 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-03 00:43 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-03 00:43 +0000
1"""
2Time provider abstraction for test performance optimization.
4This module provides a virtual time system that allows tests to fast-forward time
5without real sleep() calls, eliminating 50+ seconds of sleep overhead.
7Design Pattern: Dependency Injection
8- Components accept a TimeProvider parameter for time operations
9- Production: uses RealTimeProvider (delegates to time module)
10- Testing: uses VirtualClock (instant time advancement)
12TDD Context:
13- This implementation follows the Green phase (making tests pass)
14- Tests were written first in tests/unit/test_time_provider.py
15- Eliminates sleep overhead in TTL, timeout, and staleness tests
16"""
18import asyncio
19import time
20from typing import Protocol
23class TimeProvider(Protocol):
24 """
25 Protocol defining time operations for dependency injection.
27 This enables test doubles to replace real time with virtual time,
28 allowing tests to fast-forward without actual sleep delays.
29 """
31 def time(self) -> float:
32 """Return current Unix timestamp (seconds since epoch)."""
33 ...
35 def sleep(self, duration: float) -> None:
36 """Block for specified duration (seconds)."""
37 ...
39 def monotonic(self) -> float:
40 """Return monotonically increasing time (for elapsed time calculations)."""
41 ...
43 async def async_sleep(self, duration: float) -> None:
44 """Async sleep for specified duration (seconds)."""
45 ...
48class RealTimeProvider:
49 """
50 Production time provider that delegates to Python's time module.
52 This is the default provider used in production, providing real time
53 and actual sleep operations.
54 """
56 def time(self) -> float:
57 """Return current Unix timestamp."""
58 return time.time()
60 def sleep(self, duration: float) -> None:
61 """Block for specified duration."""
62 time.sleep(duration)
64 def monotonic(self) -> float:
65 """Return monotonically increasing time."""
66 return time.monotonic()
68 async def async_sleep(self, duration: float) -> None:
69 """Async sleep for specified duration."""
70 await asyncio.sleep(duration)
73class VirtualClock:
74 """
75 Test time provider that allows instant time advancement.
77 Instead of real sleep() calls, this clock advances time instantly,
78 eliminating sleep overhead in tests while preserving behavior.
80 Performance Impact:
81 - Reduces test suite execution by ~50 seconds
82 - TTL tests: 1-3s sleep → instant (100x faster)
83 - Timeout tests: 1s sleep → instant (1000x faster)
84 - Property tests: 35s → 0.35s (100x faster)
86 Example:
87 >>> clock = VirtualClock()
88 >>> clock.time()
89 0.0
90 >>> clock.sleep(10.0) # Instant, no actual waiting
91 >>> clock.time()
92 10.0
93 """
95 def __init__(self, start_time: float = 0.0):
96 """
97 Initialize virtual clock at specified time.
99 Args:
100 start_time: Initial time value (default: 0.0)
101 """
102 self._current_time = start_time
103 self._lock = asyncio.Lock() # Thread-safe time updates
105 def time(self) -> float:
106 """Return current virtual time."""
107 return self._current_time
109 def sleep(self, duration: float) -> None:
110 """
111 Advance virtual time instantly without real sleep.
113 Args:
114 duration: Time to advance (seconds)
116 Raises:
117 ValueError: If duration is negative
118 """
119 if duration < 0:
120 msg = "sleep duration must be non-negative"
121 raise ValueError(msg)
122 self._current_time += duration
124 def monotonic(self) -> float:
125 """Return current virtual time (monotonically increasing)."""
126 return self._current_time
128 async def async_sleep(self, duration: float) -> None:
129 """
130 Async version of sleep - advances time instantly.
132 Args:
133 duration: Time to advance (seconds)
135 Raises:
136 ValueError: If duration is negative
137 """
138 if duration < 0:
139 msg = "sleep duration must be non-negative"
140 raise ValueError(msg)
142 async with self._lock:
143 self._current_time += duration
144 # Yield control to allow other coroutines to run
145 await asyncio.sleep(0)
147 def advance(self, duration: float) -> None:
148 """
149 Manually advance virtual time (test helper).
151 This is useful for tests that need to skip time without calling sleep().
153 Args:
154 duration: Time to advance (seconds)
155 """
156 self._current_time += duration
159# Default time provider for production use
160_default_provider = RealTimeProvider()
163def get_default_time_provider() -> RealTimeProvider:
164 """
165 Get the default time provider for production use.
167 Returns:
168 RealTimeProvider instance
169 """
170 return _default_provider