Skip to content

Commit 9daab04

Browse files
committed
add tests
1 parent a1ea771 commit 9daab04

File tree

1 file changed

+328
-0
lines changed

1 file changed

+328
-0
lines changed
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
"""Test the deterministic patching functionality in pytest_plugin.py.
2+
3+
This test verifies that all sources of randomness and non-determinism are properly
4+
mocked/patched to ensure reproducible test execution for CodeFlash optimization.
5+
6+
Key functionality tested:
7+
- time.time() returns fixed timestamp (1609459200.0 = 2021-01-01 00:00:00 UTC)
8+
- time.perf_counter() returns incrementing values (maintaining relative timing)
9+
- uuid.uuid4() and uuid.uuid1() return fixed UUID (12345678-1234-5678-9abc-123456789012)
10+
- random.random() returns fixed value (0.123456789)
11+
- random module is seeded deterministically (seed=42)
12+
- os.urandom() returns fixed bytes (0x42 repeated)
13+
- numpy.random is seeded if available (seed=42)
14+
- Performance characteristics are maintained (original functions called internally)
15+
- datetime mock functions are properly stored in builtins
16+
- All patches work consistently across multiple calls
17+
- Integration with real optimization scenarios
18+
19+
This ensures that CodeFlash optimization correctness checks will pass by eliminating
20+
all sources of non-determinism that could cause object comparison failures.
21+
"""
22+
23+
import datetime
24+
import os
25+
import random
26+
import sys
27+
import time
28+
import uuid
29+
from unittest.mock import patch
30+
31+
import pytest
32+
33+
34+
class TestDeterministicPatches:
35+
"""Test suite for deterministic patching functionality."""
36+
37+
@pytest.fixture(autouse=True)
38+
def setup_and_teardown(self):
39+
"""Setup and teardown for each test."""
40+
# Import plugin to apply patches (patches are applied at module level)
41+
import codeflash.verification.pytest_plugin # noqa: F401
42+
43+
# Note: Original functions are already patched by the time we get here
44+
# This is expected behavior since patches are applied at module import
45+
46+
yield
47+
48+
# Note: In practice, these patches should remain for the entire test session
49+
50+
def test_time_time_deterministic(self):
51+
"""Test that time.time() returns a fixed deterministic value."""
52+
expected_timestamp = 1609459200.0 # 2021-01-01 00:00:00 UTC
53+
54+
# Call multiple times and verify consistent results
55+
result1 = time.time()
56+
result2 = time.time()
57+
result3 = time.time()
58+
59+
assert result1 == expected_timestamp
60+
assert result2 == expected_timestamp
61+
assert result3 == expected_timestamp
62+
assert result1 == result2 == result3
63+
64+
def test_perf_counter_incremental(self):
65+
"""Test that time.perf_counter() returns incrementing values."""
66+
# Call multiple times and verify incrementing behavior
67+
result1 = time.perf_counter()
68+
result2 = time.perf_counter()
69+
result3 = time.perf_counter()
70+
71+
# Verify they're different and incrementing by approximately 0.001
72+
assert result1 < result2 < result3
73+
assert abs((result2 - result1) - 0.001) < 1e-6 # Use reasonable epsilon for float comparison
74+
assert abs((result3 - result2) - 0.001) < 1e-6
75+
76+
def test_uuid4_deterministic(self):
77+
"""Test that uuid.uuid4() returns a fixed deterministic UUID."""
78+
expected_uuid = uuid.UUID('12345678-1234-5678-9abc-123456789012')
79+
80+
# Call multiple times and verify consistent results
81+
result1 = uuid.uuid4()
82+
result2 = uuid.uuid4()
83+
result3 = uuid.uuid4()
84+
85+
assert result1 == expected_uuid
86+
assert result2 == expected_uuid
87+
assert result3 == expected_uuid
88+
assert result1 == result2 == result3
89+
assert isinstance(result1, uuid.UUID)
90+
91+
def test_uuid1_deterministic(self):
92+
"""Test that uuid.uuid1() returns a fixed deterministic UUID."""
93+
expected_uuid = uuid.UUID('12345678-1234-5678-9abc-123456789012')
94+
95+
# Call multiple times with different parameters
96+
result1 = uuid.uuid1()
97+
result2 = uuid.uuid1(node=123456)
98+
result3 = uuid.uuid1(clock_seq=789)
99+
100+
assert result1 == expected_uuid
101+
assert result2 == expected_uuid
102+
assert result3 == expected_uuid
103+
assert isinstance(result1, uuid.UUID)
104+
105+
def test_random_random_deterministic(self):
106+
"""Test that random.random() returns a fixed deterministic value."""
107+
expected_value = 0.123456789
108+
109+
# Call multiple times and verify consistent results
110+
result1 = random.random()
111+
result2 = random.random()
112+
result3 = random.random()
113+
114+
assert result1 == expected_value
115+
assert result2 == expected_value
116+
assert result3 == expected_value
117+
assert 0.0 <= result1 <= 1.0 # Should still be a valid random float
118+
119+
def test_random_seed_deterministic(self):
120+
"""Test that random module is seeded deterministically."""
121+
# The plugin should have already seeded with 42
122+
# Test other random functions for consistency
123+
124+
# Note: random.random() is patched to always return the same value
125+
# So we test that the random module behaves deterministically
126+
# by testing that random.seed() affects other functions consistently
127+
128+
# First, test that our patched random.random always returns the same value
129+
assert random.random() == 0.123456789
130+
assert random.random() == 0.123456789
131+
132+
# Test that seeding affects other random functions consistently
133+
random.seed(42)
134+
result1_int = random.randint(1, 100)
135+
result1_choice = random.choice([1, 2, 3, 4, 5])
136+
137+
# Re-seed and get same results
138+
random.seed(42)
139+
result2_int = random.randint(1, 100)
140+
result2_choice = random.choice([1, 2, 3, 4, 5])
141+
142+
assert result1_int == result2_int
143+
assert result1_choice == result2_choice
144+
145+
def test_os_urandom_deterministic(self):
146+
"""Test that os.urandom() returns deterministic bytes."""
147+
# Test various byte lengths
148+
for n in [1, 8, 16, 32]:
149+
result1 = os.urandom(n)
150+
result2 = os.urandom(n)
151+
152+
# Should return fixed bytes (0x42 repeated)
153+
expected = b'\x42' * n
154+
assert result1 == expected
155+
assert result2 == expected
156+
assert len(result1) == n
157+
assert isinstance(result1, bytes)
158+
159+
def test_numpy_seeding(self):
160+
"""Test that numpy.random is seeded if available."""
161+
try:
162+
import numpy as np
163+
164+
# Generate some random numbers
165+
result1 = np.random.random(5)
166+
167+
# Re-seed and generate again
168+
np.random.seed(42)
169+
result2 = np.random.random(5)
170+
171+
# Should be deterministic due to seeding
172+
assert np.array_equal(result1, result2)
173+
174+
except ImportError:
175+
# numpy not available, test should pass
176+
pytest.skip("NumPy not available")
177+
178+
def test_performance_characteristics_maintained(self):
179+
"""Test that performance characteristics are maintained."""
180+
# Test that they still execute quickly (performance check)
181+
start = time.perf_counter()
182+
for _ in range(1000):
183+
time.time()
184+
uuid.uuid4()
185+
random.random()
186+
end = time.perf_counter()
187+
188+
# Should complete quickly (less than 1 second for 1000 calls)
189+
duration = end - start
190+
assert duration < 1.0, f"Performance degraded: {duration}s for 1000 calls"
191+
192+
def test_builtins_datetime_mocks_stored(self):
193+
"""Test that datetime mock functions are stored in builtins."""
194+
import builtins
195+
196+
# Verify that the mock functions are stored
197+
assert hasattr(builtins, '_original_datetime_now')
198+
assert hasattr(builtins, '_original_datetime_utcnow')
199+
assert hasattr(builtins, '_mock_datetime_now')
200+
assert hasattr(builtins, '_mock_datetime_utcnow')
201+
202+
# Test that the mock functions work
203+
mock_now = builtins._mock_datetime_now
204+
mock_utcnow = builtins._mock_datetime_utcnow
205+
206+
result1 = mock_now()
207+
result2 = mock_utcnow()
208+
209+
expected_dt = datetime.datetime(2021, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
210+
assert result1 == expected_dt
211+
assert result2 == expected_dt
212+
213+
def test_consistency_across_multiple_calls(self):
214+
"""Test that all patched functions remain consistent across many calls."""
215+
# Store initial results
216+
initial_time = time.time()
217+
initial_uuid = uuid.uuid4()
218+
initial_random = random.random()
219+
initial_urandom = os.urandom(8)
220+
221+
# Call functions many times (but not perf_counter since it increments)
222+
for _ in range(5):
223+
assert time.time() == initial_time
224+
assert uuid.uuid4() == initial_uuid
225+
assert random.random() == initial_random
226+
assert os.urandom(8) == initial_urandom
227+
228+
def test_perf_counter_state_management(self):
229+
"""Test that perf_counter maintains its own internal state correctly."""
230+
# Get a baseline
231+
base = time.perf_counter()
232+
233+
# Call several times and verify incrementing
234+
results = [time.perf_counter() for _ in range(5)]
235+
236+
# Each call should increment by approximately 0.001
237+
for i, result in enumerate(results):
238+
expected = base + ((i + 1) * 0.001)
239+
assert abs(result - expected) < 1e-6, f"Expected {expected}, got {result}"
240+
241+
def test_different_uuid_functions_same_result(self):
242+
"""Test that both uuid4 and uuid1 return the same deterministic UUID."""
243+
uuid4_result = uuid.uuid4()
244+
uuid1_result = uuid.uuid1()
245+
246+
# Both should return the same fixed UUID
247+
assert uuid4_result == uuid1_result
248+
assert str(uuid4_result) == '12345678-1234-5678-9abc-123456789012'
249+
250+
def test_patches_applied_at_module_level(self):
251+
"""Test that patches are applied when the module is imported."""
252+
# Test that functions return expected deterministic values
253+
# (This indirectly confirms they are patched)
254+
assert time.time() == 1609459200.0
255+
assert uuid.uuid4() == uuid.UUID('12345678-1234-5678-9abc-123456789012')
256+
assert random.random() == 0.123456789
257+
258+
# Test that function names indicate they are mock functions
259+
assert 'mock' in time.time.__name__
260+
assert 'mock' in uuid.uuid4.__name__
261+
assert 'mock' in random.random.__name__
262+
263+
def test_edge_cases(self):
264+
"""Test edge cases and boundary conditions."""
265+
# Test uuid functions with edge case parameters
266+
assert uuid.uuid1(node=0) == uuid.UUID('12345678-1234-5678-9abc-123456789012')
267+
assert uuid.uuid1(clock_seq=0) == uuid.UUID('12345678-1234-5678-9abc-123456789012')
268+
269+
# Test urandom with edge cases
270+
assert os.urandom(0) == b''
271+
assert os.urandom(1) == b'\x42'
272+
273+
# Test datetime mock with timezone
274+
import builtins
275+
mock_now = builtins._mock_datetime_now
276+
277+
# Test with different timezone
278+
utc_tz = datetime.timezone.utc
279+
result_with_tz = mock_now(utc_tz)
280+
expected_with_tz = datetime.datetime(2021, 1, 1, 0, 0, 0, tzinfo=utc_tz)
281+
assert result_with_tz == expected_with_tz
282+
283+
def test_integration_with_actual_optimization_scenario(self):
284+
"""Test the patching in a scenario similar to actual optimization."""
285+
# Simulate what happens during optimization - multiple function calls
286+
# that would normally produce different results but should now be deterministic
287+
288+
class MockOptimizedFunction:
289+
"""Mock function that uses various sources of randomness."""
290+
291+
def __init__(self):
292+
self.id = uuid.uuid4()
293+
self.created_at = time.time()
294+
self.random_factor = random.random()
295+
self.random_bytes = os.urandom(4)
296+
297+
def execute(self):
298+
execution_time = time.perf_counter()
299+
random_choice = random.randint(1, 100)
300+
return {
301+
'id': self.id,
302+
'created_at': self.created_at,
303+
'execution_time': execution_time,
304+
'random_factor': self.random_factor,
305+
'random_choice': random_choice,
306+
'random_bytes': self.random_bytes
307+
}
308+
309+
# Create two instances and execute them
310+
func1 = MockOptimizedFunction()
311+
func2 = MockOptimizedFunction()
312+
313+
result1 = func1.execute()
314+
result2 = func2.execute()
315+
316+
# All values should be identical due to deterministic patching
317+
assert result1['id'] == result2['id']
318+
assert result1['created_at'] == result2['created_at']
319+
assert result1['random_factor'] == result2['random_factor']
320+
assert result1['random_bytes'] == result2['random_bytes']
321+
322+
# Only execution_time should be different (incremental)
323+
assert result1['execution_time'] != result2['execution_time']
324+
assert result2['execution_time'] > result1['execution_time']
325+
326+
327+
if __name__ == '__main__':
328+
pytest.main([__file__, '-v'])

0 commit comments

Comments
 (0)