Skip to content

Commit c07f480

Browse files
Merge branch 'main' into chore/error-on-missing-key-in-fork
2 parents 4801bc6 + 73f6fa0 commit c07f480

File tree

11 files changed

+591
-43
lines changed

11 files changed

+591
-43
lines changed

code_to_optimize/topological_sort.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from collections import defaultdict
23

34

@@ -21,9 +22,10 @@ def topologicalSortUtil(self, v, visited, stack):
2122
def topologicalSort(self):
2223
visited = [False] * self.V
2324
stack = []
25+
sorting_id = uuid.uuid4()
2426

2527
for i in range(self.V):
2628
if visited[i] == False:
2729
self.topologicalSortUtil(i, visited, stack)
2830

29-
return stack
31+
return stack, str(sorting_id)

codeflash/cli_cmds/cmd_init.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,8 @@ def run_end_to_end_test(args: Namespace, bubble_sort_path: str, bubble_sort_test
941941
command = ["codeflash", "--file", "bubble_sort.py", "--function", "sorter"]
942942
if args.no_pr:
943943
command.append("--no-pr")
944+
if args.verbose:
945+
command.append("--verbose")
944946

945947
logger.info("Running sample optimization…")
946948
console.rule()
@@ -953,7 +955,7 @@ def run_end_to_end_test(args: Namespace, bubble_sort_path: str, bubble_sort_test
953955
if process.stdout:
954956
for line in process.stdout:
955957
stripped = line.strip()
956-
console.print(stripped)
958+
console.out(stripped)
957959
output.append(stripped)
958960
process.wait()
959961
console.rule()

codeflash/optimization/function_optimizer.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,23 +1321,11 @@ def generate_and_instrument_tests(
13211321
]
13221322

13231323
def cleanup_generated_files(self) -> None:
1324-
paths_to_cleanup = (
1325-
[
1326-
test_file.instrumented_behavior_file_path
1327-
for test_type in [
1328-
TestType.GENERATED_REGRESSION,
1329-
TestType.EXISTING_UNIT_TEST,
1330-
TestType.CONCOLIC_COVERAGE_TEST,
1331-
]
1332-
for test_file in self.test_files.get_by_type(test_type).test_files
1333-
]
1334-
+ [
1335-
test_file.benchmarking_file_path
1336-
for test_type in [TestType.GENERATED_REGRESSION, TestType.EXISTING_UNIT_TEST]
1337-
for test_file in self.test_files.get_by_type(test_type).test_files
1338-
]
1339-
+ [self.test_cfg.concolic_test_root_dir]
1340-
)
1324+
paths_to_cleanup = [self.test_cfg.concolic_test_root_dir]
1325+
for test_file in self.test_files:
1326+
paths_to_cleanup.append(test_file.instrumented_behavior_file_path)
1327+
paths_to_cleanup.append(test_file.benchmarking_file_path)
1328+
13411329
cleanup_paths(paths_to_cleanup)
13421330
if hasattr(get_run_tmp_file, "tmpdir"):
13431331
get_run_tmp_file.tmpdir.cleanup()

codeflash/optimization/optimizer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def __init__(self, args: Namespace) -> None:
4545
self.local_aiservice_client = LocalAiServiceClient() if self.experiment_id else None
4646
self.replay_tests_dir = None
4747
self.functions_checkpoint: CodeflashRunCheckpoint | None = None
48+
self.current_function_optimizer: FunctionOptimizer | None = None
4849

4950
def create_function_optimizer(
5051
self,
@@ -265,7 +266,9 @@ def run(self) -> None:
265266
function_to_tests,
266267
validated_original_code[original_module_path].source_code,
267268
)
268-
269+
self.current_function_optimizer = (
270+
function_optimizer # needed to clean up from the outside of this function
271+
)
269272
best_optimization = function_optimizer.optimize_function()
270273
if self.functions_checkpoint:
271274
self.functions_checkpoint.add_function_to_checkpoint(
@@ -293,6 +296,9 @@ def run(self) -> None:
293296
def cleanup_temporary_paths(self) -> None:
294297
from codeflash.code_utils.code_utils import cleanup_paths
295298

299+
if self.current_function_optimizer:
300+
self.current_function_optimizer.cleanup_generated_files()
301+
296302
cleanup_paths([self.test_cfg.concolic_test_root_dir, self.replay_tests_dir])
297303

298304

codeflash/telemetry/sentry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def init_sentry(enabled: bool = False, exclude_errors: bool = False) -> None: #
1212
if exclude_errors
1313
else logging.ERROR, # Otherwise, error logs will create sentry events
1414
)
15+
1516
sentry_sdk.init(
1617
dsn="https://4b9a1902f9361b48c04376df6483bc96@o4506833230561280.ingest.sentry.io/4506833262477312",
1718
integrations=[sentry_logging],
@@ -22,4 +23,5 @@ def init_sentry(enabled: bool = False, exclude_errors: bool = False) -> None: #
2223
# of sampled transactions.
2324
# We recommend adjusting this value in production.
2425
profiles_sample_rate=1.0,
26+
ignore_errors=[KeyboardInterrupt],
2527
)

codeflash/tracer.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
from pathlib import Path
3030
from typing import TYPE_CHECKING, Any, Callable, ClassVar
3131

32-
import dill
3332
import isort
3433
from rich.align import Align
3534
from rich.panel import Panel
@@ -41,6 +40,7 @@
4140
from codeflash.code_utils.code_utils import module_name_from_file_path
4241
from codeflash.code_utils.config_parser import parse_config_file
4342
from codeflash.discovery.functions_to_optimize import filter_files_optimized
43+
from codeflash.picklepatch.pickle_patcher import PicklePatcher
4444
from codeflash.tracing.replay_test import create_trace_replay_test
4545
from codeflash.tracing.tracing_utils import FunctionModules
4646
from codeflash.verification.verification_utils import get_test_file_path
@@ -399,22 +399,12 @@ def tracer_logic(self, frame: FrameType, event: str) -> None: # noqa: PLR0911
399399
arguments_copy = dict(arguments.items()) # Use the local 'arguments' from frame.f_locals
400400
if class_name and code.co_name == "__init__" and "self" in arguments_copy:
401401
del arguments_copy["self"]
402-
local_vars = pickle.dumps(arguments_copy, protocol=pickle.HIGHEST_PROTOCOL)
402+
local_vars = PicklePatcher.dumps(arguments_copy, protocol=pickle.HIGHEST_PROTOCOL)
403403
sys.setrecursionlimit(original_recursion_limit)
404404
except Exception:
405-
# we retry with dill if pickle fails. It's slower but more comprehensive
406-
try:
407-
sys.setrecursionlimit(10000) # Ensure limit is high for dill too
408-
# arguments_copy should be used here as well if defined above
409-
local_vars = dill.dumps(
410-
arguments_copy if "arguments_copy" in locals() else dict(arguments.items()),
411-
protocol=dill.HIGHEST_PROTOCOL,
412-
)
413-
sys.setrecursionlimit(original_recursion_limit)
414-
415-
except Exception:
416-
self.function_count[function_qualified_name] -= 1
417-
return
405+
self.function_count[function_qualified_name] -= 1
406+
sys.setrecursionlimit(original_recursion_limit)
407+
return
418408

419409
cur.execute(
420410
"INSERT INTO function_calls VALUES(?, ?, ?, ?, ?, ?, ?, ?)",

codeflash/verification/pytest_plugin.py

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import platform
1010
import re
1111
import sys
12-
import time
12+
import time as _time_module
1313
import warnings
1414
from pathlib import Path
1515
from typing import TYPE_CHECKING, Any, Callable
@@ -74,6 +74,125 @@ class UnexpectedError(Exception):
7474
resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))
7575

7676

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+
77196
def pytest_addoption(parser: Parser) -> None:
78197
"""Add command line options."""
79198
pytest_loops = parser.getgroup("loops")
@@ -137,6 +256,9 @@ def pytest_configure(config: Config) -> None:
137256
config.addinivalue_line("markers", "loops(n): run the given test function `n` times.")
138257
config.pluginmanager.register(PytestLoops(config), PytestLoops.name)
139258

259+
# Apply deterministic patches when the plugin is configured
260+
_apply_deterministic_patches()
261+
140262

141263
class PytestLoops:
142264
name: str = "pytest-loops"
@@ -157,7 +279,7 @@ def pytest_runtestloop(self, session: Session) -> bool:
157279
if session.config.option.collectonly:
158280
return True
159281

160-
start_time: float = time.time()
282+
start_time: float = _ORIGINAL_TIME_TIME()
161283
total_time: float = self._get_total_time(session)
162284

163285
count: int = 0
@@ -184,7 +306,7 @@ def pytest_runtestloop(self, session: Session) -> bool:
184306
raise session.Interrupted(session.shouldstop)
185307
if self._timed_out(session, start_time, count):
186308
break # exit loop
187-
time.sleep(self._get_delay_time(session))
309+
_ORIGINAL_TIME_SLEEP(self._get_delay_time(session))
188310
return True
189311

190312
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:
283405
"""
284406
return count >= session.config.option.codeflash_max_loops or (
285407
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)
287409
)
288410

289411
@pytest.fixture

codeflash/version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# These version placeholders will be replaced by uv-dynamic-versioning during build.
2-
__version__ = "0.14.5"
3-
__version_tuple__ = (0, 14, 5)
2+
__version__ = "0.14.6"
3+
__version_tuple__ = (0, 14, 6)

tests/scripts/end_to_end_test_topological_sort.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
import pathlib
3-
import tomlkit
43

54
from codeflash.code_utils.code_utils import add_addopts_to_pyproject
65
from end_to_end_test_utilities import CoverageExpectation, TestConfig, run_codeflash_command, run_with_retries
@@ -17,7 +16,7 @@ def run_test(expected_improvement_pct: int) -> bool:
1716
CoverageExpectation(
1817
function_name="Graph.topologicalSort",
1918
expected_coverage=100.0,
20-
expected_lines=[24, 25, 26, 27, 28, 29],
19+
expected_lines=[25, 26, 27, 28, 29, 30, 31],
2120
)
2221
],
2322
)

tests/test_comparator.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import decimal
66
import re
77
import sys
8+
import uuid
89
from enum import Enum, Flag, IntFlag, auto
910
from pathlib import Path
1011
import array # Add import for array
@@ -241,6 +242,12 @@ class Color4(IntFlag):
241242
assert not comparator(empty_arr_i1, empty_arr_f)
242243
assert not comparator(empty_arr_i1, arr1)
243244

245+
id1 = uuid.uuid4()
246+
id3 = uuid.uuid4()
247+
assert comparator(id1, id1)
248+
assert not comparator(id1, id3)
249+
250+
244251

245252

246253
def test_numpy():

0 commit comments

Comments
 (0)