-
Notifications
You must be signed in to change notification settings - Fork 21
Granular async instrumentation #687
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
KRRT7
wants to merge
40
commits into
standalone-fto-async
Choose a base branch
from
granular-async-instrumentation
base: standalone-fto-async
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
0a57afa
first pass at wrapper deco behavioral
KRRT7 c9aaaad
don't reapply too early
KRRT7 ad1d085
feedback
KRRT7 14353d4
Update codeflash_wrap_decorator.py
KRRT7 3c05f97
Update function_optimizer.py
KRRT7 bafad49
Update codeflash_wrap_decorator.py
KRRT7 745a3f7
test gen for async too
KRRT7 5a47237
Update aiservice.py
KRRT7 1ab1886
create path object once
KRRT7 af96274
use sqlite vs pickle
KRRT7 4dfda09
no dependencies on codeflash
KRRT7 2a2a3a3
add tests for async wrapper
KRRT7 b569d44
linting
KRRT7 4514dd6
address feedback re: using existing logic from tracer & cf capture
KRRT7 fb9555e
test
KRRT7 66f7a84
add unit tests
KRRT7 aa872b5
be consistent
KRRT7 0319803
Update codeflash_wrap_decorator.py
KRRT7 9bbe988
some async changes
KRRT7 1fe7c04
Update test_add_runtime_comments.py
KRRT7 91b8902
Update codeflash/code_utils/codeflash_wrap_decorator.py
misrasaurabh1 0f066c0
Merge branch 'granular-async-instrumentation' of https://github.com/c…
KRRT7 69fa701
mirror tests
KRRT7 862415e
add support for async tests in edit_generated_tests
KRRT7 febf2b6
add tests with run_and_parse_tests
KRRT7 61aade1
Update test_async_run_and_parse_tests.py
KRRT7 5bf5c20
⚡️ Speed up method `CommentMapper.visit_ClassDef` by 197% in PR #687 …
codeflash-ai[bot] b2bf9fa
add missing imports
KRRT7 ef74bb5
Update edit_generated_tests.py
KRRT7 e119085
Merge pull request #706 from codeflash-ai/codeflash/optimize-pr687-20…
KRRT7 600dafc
⚡️ Speed up method `CommentMapper.visit_FunctionDef` by 84% in PR #68…
codeflash-ai[bot] 1142de5
linting / formatting
KRRT7 14280fa
Merge pull request #709 from codeflash-ai/codeflash/optimize-pr687-20…
KRRT7 3ca6fad
linter
KRRT7 73d03ae
add E2E
KRRT7 0af669f
Update workload.py
KRRT7 1ebc616
Update workload.py
KRRT7 d8a6ab2
temp
KRRT7 7bcd136
go
KRRT7 2429801
restore original code if not found for async
KRRT7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import asyncio | ||
from typing import List, Union | ||
|
||
|
||
async def async_sorter(lst: List[Union[int, float]]) -> List[Union[int, float]]: | ||
""" | ||
Async bubble sort implementation for testing. | ||
""" | ||
print("codeflash stdout: Async sorting list") | ||
|
||
await asyncio.sleep(0.01) | ||
|
||
n = len(lst) | ||
for i in range(n): | ||
for j in range(0, n - i - 1): | ||
if lst[j] > lst[j + 1]: | ||
lst[j], lst[j + 1] = lst[j + 1], lst[j] | ||
|
||
result = lst.copy() | ||
print(f"result: {result}") | ||
return result | ||
|
||
|
||
class AsyncBubbleSorter: | ||
"""Class with async sorting method for testing.""" | ||
|
||
async def sorter(self, lst: List[Union[int, float]]) -> List[Union[int, float]]: | ||
""" | ||
Async bubble sort implementation within a class. | ||
""" | ||
print("codeflash stdout: AsyncBubbleSorter.sorter() called") | ||
|
||
# Add some async delay | ||
await asyncio.sleep(0.005) | ||
|
||
n = len(lst) | ||
for i in range(n): | ||
for j in range(0, n - i - 1): | ||
if lst[j] > lst[j + 1]: | ||
lst[j], lst[j + 1] = lst[j + 1], lst[j] | ||
|
||
result = lst.copy() | ||
return result |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,343 @@ | ||
from __future__ import annotations | ||
|
||
import os | ||
from argparse import Namespace | ||
from pathlib import Path | ||
|
||
from codeflash.code_utils.instrument_existing_tests import instrument_source_module_with_async_decorators | ||
from codeflash.discovery.functions_to_optimize import FunctionToOptimize | ||
from codeflash.models.models import FunctionParent, TestFile, TestFiles, TestingMode, TestType | ||
from codeflash.optimization.optimizer import Optimizer | ||
from codeflash.verification.instrument_codeflash_capture import instrument_codeflash_capture | ||
|
||
|
||
def test_async_bubble_sort_behavior_results() -> None: | ||
test_code = """import asyncio | ||
import pytest | ||
from code_to_optimize.async_bubble_sort import async_sorter | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_async_sort(): | ||
input = [5, 4, 3, 2, 1, 0] | ||
output = await async_sorter(input) | ||
assert output == [0, 1, 2, 3, 4, 5] | ||
|
||
input = [5.0, 4.0, 3.0, 2.0, 1.0, 0.0] | ||
output = await async_sorter(input) | ||
assert output == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]""" | ||
|
||
test_path = ( | ||
Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_bubble_sort_temp.py" | ||
).resolve() | ||
test_path_perf = ( | ||
Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_bubble_sort_perf_temp.py" | ||
).resolve() | ||
fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/async_bubble_sort.py").resolve() | ||
original_code = fto_path.read_text("utf-8") | ||
|
||
try: | ||
# Write test file | ||
with test_path.open("w") as f: | ||
f.write(test_code) | ||
|
||
tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() | ||
project_root_path = (Path(__file__).parent / "..").resolve() | ||
|
||
# Create async function to optimize | ||
func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True) | ||
|
||
# For async functions, instrument the source module directly with decorators | ||
source_success, instrumented_source = instrument_source_module_with_async_decorators( | ||
fto_path, func, TestingMode.BEHAVIOR | ||
) | ||
|
||
assert source_success | ||
assert instrumented_source is not None | ||
assert '''import asyncio\nfrom typing import List, Union\n\nfrom codeflash.code_utils.codeflash_wrap_decorator import \\\n codeflash_behavior_async\n\n\n@codeflash_behavior_async\nasync def async_sorter(lst: List[Union[int, float]]) -> List[Union[int, float]]:\n """\n Async bubble sort implementation for testing.\n """\n print("codeflash stdout: Async sorting list")\n \n # Add some async delay to simulate async work\n await asyncio.sleep(0.01)\n \n n = len(lst)\n for i in range(n):\n for j in range(0, n - i - 1):\n if lst[j] > lst[j + 1]:\n lst[j], lst[j + 1] = lst[j + 1], lst[j]\n \n result = lst.copy()\n print(f"result: {result}")\n return result\n\n\nclass AsyncBubbleSorter:\n """Class with async sorting method for testing."""\n \n async def sorter(self, lst: List[Union[int, float]]) -> List[Union[int, float]]:\n """\n Async bubble sort implementation within a class.\n """\n print("codeflash stdout: AsyncBubbleSorter.sorter() called")\n \n # Add some async delay\n await asyncio.sleep(0.005)\n \n n = len(lst)\n for i in range(n):\n for j in range(0, n - i - 1):\n if lst[j] > lst[j + 1]:\n lst[j], lst[j + 1] = lst[j + 1], lst[j]\n \n result = lst.copy()\n return result\n''' in instrumented_source | ||
|
||
# Write the instrumented source back | ||
fto_path.write_text(instrumented_source, "utf-8") | ||
|
||
# Add codeflash capture | ||
instrument_codeflash_capture(func, {}, tests_root) | ||
|
||
# Create optimizer | ||
opt = Optimizer( | ||
Namespace( | ||
project_root=project_root_path, | ||
disable_telemetry=True, | ||
tests_root=tests_root, | ||
test_framework="pytest", | ||
pytest_cmd="pytest", | ||
experiment_id=None, | ||
test_project_root=project_root_path, | ||
) | ||
) | ||
|
||
test_env = os.environ.copy() | ||
test_env["CODEFLASH_TEST_ITERATION"] = "0" | ||
test_env["CODEFLASH_LOOP_INDEX"] = "1" | ||
test_type = TestType.EXISTING_UNIT_TEST | ||
|
||
# Create function optimizer and set up test files | ||
func_optimizer = opt.create_function_optimizer(func) | ||
func_optimizer.test_files = TestFiles( | ||
test_files=[ | ||
TestFile( | ||
instrumented_behavior_file_path=test_path, | ||
test_type=test_type, | ||
original_file_path=test_path, | ||
benchmarking_file_path=test_path_perf, | ||
) | ||
] | ||
) | ||
|
||
test_results, coverage_data = func_optimizer.run_and_parse_tests( | ||
testing_type=TestingMode.BEHAVIOR, | ||
test_env=test_env, | ||
test_files=func_optimizer.test_files, | ||
optimization_iteration=0, | ||
pytest_min_loops=1, | ||
pytest_max_loops=1, | ||
testing_time=0.1, | ||
) | ||
|
||
assert test_results is not None | ||
assert test_results.test_results is not None | ||
|
||
results_list = test_results.test_results | ||
assert results_list[0].id.function_getting_tested == "async_sorter" | ||
assert results_list[0].id.test_class_name == "PytestPluginManager" | ||
assert results_list[0].id.test_function_name == "test_async_sort" | ||
assert results_list[0].did_pass | ||
assert results_list[0].runtime is None or results_list[0].runtime >= 0 | ||
|
||
expected_stdout = "codeflash stdout: Async sorting list\nresult: [0, 1, 2, 3, 4, 5]\n" | ||
assert expected_stdout == results_list[0].stdout | ||
|
||
|
||
if len(results_list) > 1: | ||
assert results_list[1].id.function_getting_tested == "async_sorter" | ||
assert results_list[1].id.test_function_name == "test_async_sort" | ||
assert results_list[1].did_pass | ||
|
||
expected_stdout2 = "codeflash stdout: Async sorting list\nresult: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]\n" | ||
assert expected_stdout2 == results_list[1].stdout | ||
|
||
finally: | ||
# Restore original code | ||
fto_path.write_text(original_code, "utf-8") | ||
# Clean up test files | ||
if test_path.exists(): | ||
test_path.unlink() | ||
if test_path_perf.exists(): | ||
test_path_perf.unlink() | ||
|
||
|
||
def test_async_class_method_behavior_results() -> None: | ||
"""Test async class method behavior with run_and_parse_tests.""" | ||
test_code = """import asyncio | ||
import pytest | ||
from code_to_optimize.async_bubble_sort import AsyncBubbleSorter | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_async_class_sort(): | ||
sorter = AsyncBubbleSorter() | ||
input = [3, 1, 4, 1, 5] | ||
output = await sorter.sorter(input) | ||
assert output == [1, 1, 3, 4, 5]""" | ||
|
||
test_path = ( | ||
Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_class_bubble_sort_temp.py" | ||
).resolve() | ||
test_path_perf = ( | ||
Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_class_bubble_sort_perf_temp.py" | ||
).resolve() | ||
fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/async_bubble_sort.py").resolve() | ||
original_code = fto_path.read_text("utf-8") | ||
|
||
try: | ||
with test_path.open("w") as f: | ||
f.write(test_code) | ||
|
||
tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() | ||
project_root_path = (Path(__file__).parent / "..").resolve() | ||
|
||
func = FunctionToOptimize( | ||
function_name="sorter", | ||
parents=[FunctionParent("AsyncBubbleSorter", "ClassDef")], | ||
file_path=Path(fto_path), | ||
is_async=True, | ||
) | ||
|
||
source_success, instrumented_source = instrument_source_module_with_async_decorators( | ||
fto_path, func, TestingMode.BEHAVIOR | ||
) | ||
|
||
assert source_success | ||
assert instrumented_source is not None | ||
assert "@codeflash_behavior_async" in instrumented_source | ||
|
||
fto_path.write_text(instrumented_source, "utf-8") | ||
|
||
instrument_codeflash_capture(func, {}, tests_root) | ||
|
||
opt = Optimizer( | ||
Namespace( | ||
project_root=project_root_path, | ||
disable_telemetry=True, | ||
tests_root=tests_root, | ||
test_framework="pytest", | ||
pytest_cmd="pytest", | ||
experiment_id=None, | ||
test_project_root=project_root_path, | ||
) | ||
) | ||
|
||
test_env = os.environ.copy() | ||
test_env["CODEFLASH_TEST_ITERATION"] = "0" | ||
test_env["CODEFLASH_LOOP_INDEX"] = "1" | ||
test_type = TestType.EXISTING_UNIT_TEST | ||
|
||
func_optimizer = opt.create_function_optimizer(func) | ||
func_optimizer.test_files = TestFiles( | ||
test_files=[ | ||
TestFile( | ||
instrumented_behavior_file_path=test_path, | ||
test_type=test_type, | ||
original_file_path=test_path, | ||
benchmarking_file_path=test_path_perf, | ||
) | ||
] | ||
) | ||
|
||
test_results, coverage_data = func_optimizer.run_and_parse_tests( | ||
testing_type=TestingMode.BEHAVIOR, | ||
test_env=test_env, | ||
test_files=func_optimizer.test_files, | ||
optimization_iteration=0, | ||
pytest_min_loops=1, | ||
pytest_max_loops=1, | ||
testing_time=0.1, | ||
) | ||
|
||
|
||
assert test_results is not None | ||
assert test_results.test_results is not None | ||
|
||
results_list = test_results.test_results | ||
assert len(results_list) == 2, f"Expected 2 results but got {len(results_list)}: {[r.id.function_getting_tested for r in results_list]}" | ||
|
||
init_result = results_list[0] | ||
sorter_result = results_list[1] | ||
|
||
|
||
assert sorter_result.id.function_getting_tested == "sorter" | ||
assert sorter_result.id.test_class_name == "PytestPluginManager" | ||
assert sorter_result.id.test_function_name == "test_async_class_sort" | ||
assert sorter_result.did_pass | ||
assert sorter_result.runtime is None or sorter_result.runtime >= 0 | ||
|
||
expected_stdout = "codeflash stdout: AsyncBubbleSorter.sorter() called\n" | ||
assert expected_stdout == sorter_result.stdout | ||
|
||
assert ".__init__" in init_result.id.function_getting_tested | ||
assert init_result.did_pass | ||
|
||
finally: | ||
fto_path.write_text(original_code, "utf-8") | ||
if test_path.exists(): | ||
test_path.unlink() | ||
if test_path_perf.exists(): | ||
test_path_perf.unlink() | ||
|
||
|
||
def test_async_function_performance_mode() -> None: | ||
test_code = """import asyncio | ||
import pytest | ||
from code_to_optimize.async_bubble_sort import async_sorter | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_async_perf(): | ||
input = [8, 7, 6, 5, 4, 3, 2, 1] | ||
output = await async_sorter(input) | ||
assert output == [1, 2, 3, 4, 5, 6, 7, 8]""" | ||
|
||
test_path = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_perf_temp.py").resolve() | ||
fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/async_bubble_sort.py").resolve() | ||
original_code = fto_path.read_text("utf-8") | ||
|
||
try: | ||
with test_path.open("w") as f: | ||
f.write(test_code) | ||
|
||
tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() | ||
project_root_path = (Path(__file__).parent / "..").resolve() | ||
|
||
# Create async function to optimize | ||
func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True) | ||
|
||
# Instrument the source module with async performance decorators | ||
source_success, instrumented_source = instrument_source_module_with_async_decorators( | ||
fto_path, func, TestingMode.PERFORMANCE | ||
) | ||
|
||
assert source_success | ||
assert instrumented_source is not None | ||
assert '''import asyncio\nfrom typing import List, Union\n\nfrom codeflash.code_utils.codeflash_wrap_decorator import \\\n codeflash_performance_async\n\n\n@codeflash_performance_async\nasync def async_sorter(lst: List[Union[int, float]]) -> List[Union[int, float]]:\n """\n Async bubble sort implementation for testing.\n """\n print("codeflash stdout: Async sorting list")\n \n # Add some async delay to simulate async work\n await asyncio.sleep(0.01)\n \n n = len(lst)\n for i in range(n):\n for j in range(0, n - i - 1):\n if lst[j] > lst[j + 1]:\n lst[j], lst[j + 1] = lst[j + 1], lst[j]\n \n result = lst.copy()\n print(f"result: {result}")\n return result\n\n\nclass AsyncBubbleSorter:\n """Class with async sorting method for testing."""\n \n async def sorter(self, lst: List[Union[int, float]]) -> List[Union[int, float]]:\n """\n Async bubble sort implementation within a class.\n """\n print("codeflash stdout: AsyncBubbleSorter.sorter() called")\n \n # Add some async delay\n await asyncio.sleep(0.005)\n \n n = len(lst)\n for i in range(n):\n for j in range(0, n - i - 1):\n if lst[j] > lst[j + 1]:\n lst[j], lst[j + 1] = lst[j + 1], lst[j]\n \n result = lst.copy()\n return result\n''' == instrumented_source | ||
|
||
fto_path.write_text(instrumented_source, "utf-8") | ||
|
||
instrument_codeflash_capture(func, {}, tests_root) | ||
|
||
opt = Optimizer( | ||
Namespace( | ||
project_root=project_root_path, | ||
disable_telemetry=True, | ||
tests_root=tests_root, | ||
test_framework="pytest", | ||
pytest_cmd="pytest", | ||
experiment_id=None, | ||
test_project_root=project_root_path, | ||
) | ||
) | ||
|
||
test_env = os.environ.copy() | ||
test_env["CODEFLASH_TEST_ITERATION"] = "0" | ||
test_env["CODEFLASH_LOOP_INDEX"] = "1" | ||
test_type = TestType.EXISTING_UNIT_TEST | ||
|
||
func_optimizer = opt.create_function_optimizer(func) | ||
func_optimizer.test_files = TestFiles( | ||
test_files=[ | ||
TestFile( | ||
instrumented_behavior_file_path=test_path, | ||
test_type=test_type, | ||
original_file_path=test_path, | ||
benchmarking_file_path=test_path, # Same file for perf | ||
) | ||
] | ||
) | ||
|
||
test_results, coverage_data = func_optimizer.run_and_parse_tests( | ||
testing_type=TestingMode.PERFORMANCE, | ||
test_env=test_env, | ||
test_files=func_optimizer.test_files, | ||
optimization_iteration=0, | ||
pytest_min_loops=1, | ||
pytest_max_loops=1, | ||
testing_time=0.1, | ||
) | ||
|
||
assert test_results is not None | ||
assert test_results.test_results is not None | ||
|
||
finally: | ||
# Restore original code | ||
fto_path.write_text(original_code, "utf-8") | ||
# Clean up test files | ||
if test_path.exists(): | ||
test_path.unlink() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.