9
9
import platform
10
10
import re
11
11
import sys
12
- import time
12
+ import time as _time_module
13
13
import warnings
14
14
from pathlib import Path
15
15
from typing import TYPE_CHECKING , Any , Callable
@@ -74,6 +74,125 @@ class UnexpectedError(Exception):
74
74
resource .setrlimit (resource .RLIMIT_AS , (memory_limit , memory_limit ))
75
75
76
76
77
+ # Store references to original functions before any patching
78
+ _ORIGINAL_TIME_TIME = _time_module .time
79
+ _ORIGINAL_PERF_COUNTER = _time_module .perf_counter
80
+ _ORIGINAL_TIME_SLEEP = _time_module .sleep
81
+
82
+
83
+ # Apply deterministic patches for reproducible test execution
84
+ def _apply_deterministic_patches () -> None :
85
+ """Apply patches to make all sources of randomness deterministic."""
86
+ import datetime
87
+ import random
88
+ import time
89
+ import uuid
90
+
91
+ # Store original functions (these are already saved globally above)
92
+ _original_time = time .time
93
+ _original_perf_counter = time .perf_counter
94
+ _original_datetime_now = datetime .datetime .now
95
+ _original_datetime_utcnow = datetime .datetime .utcnow
96
+ _original_uuid4 = uuid .uuid4
97
+ _original_uuid1 = uuid .uuid1
98
+ _original_random = random .random
99
+
100
+ # Fixed deterministic values
101
+ fixed_timestamp = 1609459200.0 # 2021-01-01 00:00:00 UTC
102
+ fixed_datetime = datetime .datetime (2021 , 1 , 1 , 0 , 0 , 0 , tzinfo = datetime .timezone .utc )
103
+ fixed_uuid = uuid .UUID ("12345678-1234-5678-9abc-123456789012" )
104
+
105
+ # Counter for perf_counter to maintain relative timing
106
+ _perf_counter_start = fixed_timestamp
107
+ _perf_counter_calls = 0
108
+
109
+ def mock_time_time () -> float :
110
+ """Return fixed timestamp while preserving performance characteristics."""
111
+ _original_time () # Maintain performance characteristics
112
+ return fixed_timestamp
113
+
114
+ def mock_perf_counter () -> float :
115
+ """Return incrementing counter for relative timing."""
116
+ nonlocal _perf_counter_calls
117
+ _original_perf_counter () # Maintain performance characteristics
118
+ _perf_counter_calls += 1
119
+ return _perf_counter_start + (_perf_counter_calls * 0.001 ) # Increment by 1ms each call
120
+
121
+ def mock_datetime_now (tz : datetime .timezone | None = None ) -> datetime .datetime :
122
+ """Return fixed datetime while preserving performance characteristics."""
123
+ _original_datetime_now (tz ) # Maintain performance characteristics
124
+ if tz is None :
125
+ return fixed_datetime
126
+ return fixed_datetime .replace (tzinfo = tz )
127
+
128
+ def mock_datetime_utcnow () -> datetime .datetime :
129
+ """Return fixed UTC datetime while preserving performance characteristics."""
130
+ _original_datetime_utcnow () # Maintain performance characteristics
131
+ return fixed_datetime
132
+
133
+ def mock_uuid4 () -> uuid .UUID :
134
+ """Return fixed UUID4 while preserving performance characteristics."""
135
+ _original_uuid4 () # Maintain performance characteristics
136
+ return fixed_uuid
137
+
138
+ def mock_uuid1 (node : int | None = None , clock_seq : int | None = None ) -> uuid .UUID :
139
+ """Return fixed UUID1 while preserving performance characteristics."""
140
+ _original_uuid1 (node , clock_seq ) # Maintain performance characteristics
141
+ return fixed_uuid
142
+
143
+ def mock_random () -> float :
144
+ """Return deterministic random value while preserving performance characteristics."""
145
+ _original_random () # Maintain performance characteristics
146
+ return 0.123456789 # Fixed random value
147
+
148
+ # Apply patches
149
+ time .time = mock_time_time
150
+ time .perf_counter = mock_perf_counter
151
+ uuid .uuid4 = mock_uuid4
152
+ uuid .uuid1 = mock_uuid1
153
+
154
+ # Seed random module for other random functions
155
+ random .seed (42 )
156
+ random .random = mock_random
157
+
158
+ # For datetime, we need to use a different approach since we can't patch class methods
159
+ # Store original methods for potential later use
160
+ import builtins
161
+
162
+ builtins ._original_datetime_now = _original_datetime_now # noqa: SLF001
163
+ builtins ._original_datetime_utcnow = _original_datetime_utcnow # noqa: SLF001
164
+ builtins ._mock_datetime_now = mock_datetime_now # noqa: SLF001
165
+ builtins ._mock_datetime_utcnow = mock_datetime_utcnow # noqa: SLF001
166
+
167
+ # Patch numpy.random if available
168
+ try :
169
+ import numpy as np
170
+
171
+ # Use modern numpy random generator approach
172
+ np .random .default_rng (42 )
173
+ np .random .seed (42 ) # Keep legacy seed for compatibility # noqa: NPY002
174
+ except ImportError :
175
+ pass
176
+
177
+ # Patch os.urandom if needed
178
+ try :
179
+ import os
180
+
181
+ _original_urandom = os .urandom
182
+
183
+ def mock_urandom (n : int ) -> bytes :
184
+ _original_urandom (n ) # Maintain performance characteristics
185
+ return b"\x42 " * n # Fixed bytes
186
+
187
+ os .urandom = mock_urandom
188
+ except (ImportError , AttributeError ):
189
+ pass
190
+
191
+
192
+ # Note: Deterministic patches are applied conditionally, not globally
193
+ # They should only be applied when running CodeFlash optimization tests
194
+
195
+
77
196
def pytest_addoption (parser : Parser ) -> None :
78
197
"""Add command line options."""
79
198
pytest_loops = parser .getgroup ("loops" )
@@ -137,6 +256,9 @@ def pytest_configure(config: Config) -> None:
137
256
config .addinivalue_line ("markers" , "loops(n): run the given test function `n` times." )
138
257
config .pluginmanager .register (PytestLoops (config ), PytestLoops .name )
139
258
259
+ # Apply deterministic patches when the plugin is configured
260
+ _apply_deterministic_patches ()
261
+
140
262
141
263
class PytestLoops :
142
264
name : str = "pytest-loops"
@@ -157,7 +279,7 @@ def pytest_runtestloop(self, session: Session) -> bool:
157
279
if session .config .option .collectonly :
158
280
return True
159
281
160
- start_time : float = time . time ()
282
+ start_time : float = _ORIGINAL_TIME_TIME ()
161
283
total_time : float = self ._get_total_time (session )
162
284
163
285
count : int = 0
@@ -184,7 +306,7 @@ def pytest_runtestloop(self, session: Session) -> bool:
184
306
raise session .Interrupted (session .shouldstop )
185
307
if self ._timed_out (session , start_time , count ):
186
308
break # exit loop
187
- time . sleep (self ._get_delay_time (session ))
309
+ _ORIGINAL_TIME_SLEEP (self ._get_delay_time (session ))
188
310
return True
189
311
190
312
def _clear_lru_caches (self , item : pytest .Item ) -> None :
@@ -283,7 +405,7 @@ def _timed_out(self, session: Session, start_time: float, count: int) -> bool:
283
405
"""
284
406
return count >= session .config .option .codeflash_max_loops or (
285
407
count >= session .config .option .codeflash_min_loops
286
- and time . time () - start_time > self ._get_total_time (session )
408
+ and _ORIGINAL_TIME_TIME () - start_time > self ._get_total_time (session )
287
409
)
288
410
289
411
@pytest .fixture
0 commit comments