Skip to content

Commit 87ad743

Browse files
committed
Merge branch 'refs/heads/merge_test_results_into_models' into codeflash-trace-decorator
# Conflicts: # codeflash/github/PrComment.py # codeflash/models/models.py # codeflash/optimization/optimizer.py # codeflash/result/explanation.py # codeflash/verification/test_results.py
2 parents 57b80ec + 637323c commit 87ad743

22 files changed

+496
-512
lines changed

codeflash/code_utils/instrument_existing_tests.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
from codeflash.cli_cmds.console import logger
1010
from codeflash.code_utils.code_utils import get_run_tmp_file, module_name_from_file_path
1111
from codeflash.discovery.functions_to_optimize import FunctionToOptimize
12-
from codeflash.models.models import FunctionParent, TestingMode
13-
from codeflash.verification.test_results import VerificationType
12+
from codeflash.models.models import FunctionParent, TestingMode, VerificationType
1413

1514
if TYPE_CHECKING:
1615
from collections.abc import Iterable

codeflash/discovery/discover_unit_tests.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
from codeflash.cli_cmds.console import console, logger
1717
from codeflash.code_utils.code_utils import get_run_tmp_file, module_name_from_file_path
1818
from codeflash.code_utils.compat import SAFE_SYS_EXECUTABLE
19-
from codeflash.models.models import CodePosition, FunctionCalledInTest, TestsInFile
20-
from codeflash.verification.test_results import TestType
19+
from codeflash.models.models import CodePosition, FunctionCalledInTest, TestsInFile, TestType
2120

2221
if TYPE_CHECKING:
2322
from codeflash.verification.verification_utils import TestConfig

codeflash/github/PrComment.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from codeflash.code_utils.time_utils import humanize_runtime
88
from codeflash.models.models import BenchmarkDetail
99
from codeflash.verification.test_results import TestResults
10+
from codeflash.models.models import TestResults
1011

1112

1213
@dataclass(frozen=True, config={"arbitrary_types_allowed": True})

codeflash/models/models.py

Lines changed: 244 additions & 205 deletions
Large diffs are not rendered by default.

codeflash/optimization/function_optimizer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
TestFile,
5858
TestFiles,
5959
TestingMode,
60+
TestResults,
61+
TestType
6062
)
6163
from codeflash.result.create_pr import check_create_pr, existing_tests_source_for
6264
from codeflash.result.critic import coverage_critic, performance_gain, quantity_of_tests_critic, speedup_critic
@@ -66,7 +68,6 @@
6668
from codeflash.verification.equivalence import compare_test_results
6769
from codeflash.verification.instrument_codeflash_capture import instrument_codeflash_capture
6870
from codeflash.verification.parse_test_output import parse_test_results
69-
from codeflash.verification.test_results import TestResults, TestType
7071
from codeflash.verification.test_runner import run_behavioral_tests, run_benchmarking_tests
7172
from codeflash.verification.verification_utils import get_test_file_path
7273
from codeflash.verification.verifier import generate_tests

codeflash/optimization/optimizer.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@
2020
from codeflash.discovery.discover_unit_tests import discover_unit_tests
2121
from codeflash.discovery.functions_to_optimize import get_functions_to_optimize
2222
from codeflash.either import is_successful
23-
from codeflash.models.models import ValidCode, BenchmarkKey
23+
from codeflash.models.models import ValidCode, TestType, BenchmarkKey
2424
from codeflash.optimization.function_optimizer import FunctionOptimizer
2525
from codeflash.telemetry.posthog_cf import ph
26-
from codeflash.verification.test_results import TestType
2726
from codeflash.verification.verification_utils import TestConfig
2827
from codeflash.benchmarking.utils import print_benchmark_table
2928
from codeflash.benchmarking.instrument_codeflash_trace import instrument_codeflash_trace_decorator

codeflash/result/critic.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
from codeflash.cli_cmds.console import logger
44
from codeflash.code_utils import env_utils
55
from codeflash.code_utils.config_consts import COVERAGE_THRESHOLD, MIN_IMPROVEMENT_THRESHOLD
6-
from codeflash.models.models import CoverageData, OptimizedCandidateResult
7-
from codeflash.verification.test_results import TestType
8-
6+
from codeflash.models.models import CoverageData, OptimizedCandidateResult, TestType
97

108
def performance_gain(*, original_runtime_ns: int, optimized_runtime_ns: int) -> float:
119
"""Calculate the performance gain of an optimized code over the original code.

codeflash/result/explanation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic.dataclasses import dataclass
66

77
from codeflash.code_utils.time_utils import humanize_runtime
8-
from codeflash.models.models import BenchmarkDetail
8+
from codeflash.models.models import BenchmarkDetail, TestResults
99
from codeflash.verification.test_results import TestResults
1010

1111

codeflash/verification/codeflash_capture.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import dill as pickle
1212

13-
from codeflash.verification.test_results import VerificationType
13+
from codeflash.models.models import VerificationType
1414

1515

1616
def get_test_info_from_stack(tests_root: str) -> tuple[str, str | None, str, str]:
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Optional, cast
4+
5+
6+
import json
7+
from collections.abc import Collection, Iterator
8+
from pathlib import Path
9+
from typing import Annotated, Any, Optional, Union, cast
10+
11+
import sentry_sdk
12+
from coverage.exceptions import NoDataError
13+
14+
from codeflash.cli_cmds.console import console, logger
15+
from codeflash.code_utils.coverage_utils import (
16+
build_fully_qualified_name,
17+
extract_dependent_function,
18+
generate_candidates,
19+
)
20+
from codeflash.models.models import CoverageData, CodeOptimizationContext, CoverageStatus, FunctionCoverage
21+
22+
23+
class CoverageUtils:
24+
"""Coverage utils class for interfacing with Coverage."""
25+
26+
@staticmethod
27+
def load_from_sqlite_database(
28+
database_path: Path, config_path: Path, function_name: str, code_context: CodeOptimizationContext, source_code_path: Path
29+
) -> CoverageData:
30+
"""Load coverage data from an SQLite database, mimicking the behavior of load_from_coverage_file."""
31+
from coverage import Coverage
32+
from coverage.jsonreport import JsonReporter
33+
34+
cov = Coverage(data_file=database_path,config_file=config_path, data_suffix=True, auto_data=True, branch=True)
35+
36+
if not database_path.stat().st_size or not database_path.exists():
37+
logger.debug(f"Coverage database {database_path} is empty or does not exist")
38+
sentry_sdk.capture_message(f"Coverage database {database_path} is empty or does not exist")
39+
return CoverageUtils.create_empty(source_code_path, function_name, code_context)
40+
cov.load()
41+
42+
reporter = JsonReporter(cov)
43+
temp_json_file = database_path.with_suffix(".report.json")
44+
with temp_json_file.open("w") as f:
45+
try:
46+
reporter.report(morfs=[source_code_path.as_posix()], outfile=f)
47+
except NoDataError:
48+
sentry_sdk.capture_message(f"No coverage data found for {function_name} in {source_code_path}")
49+
return CoverageUtils.create_empty(source_code_path, function_name, code_context)
50+
with temp_json_file.open() as f:
51+
original_coverage_data = json.load(f)
52+
53+
coverage_data, status = CoverageUtils._parse_coverage_file(temp_json_file, source_code_path)
54+
55+
main_func_coverage, dependent_func_coverage = CoverageUtils._fetch_function_coverages(
56+
function_name, code_context, coverage_data, original_cov_data=original_coverage_data
57+
)
58+
59+
total_executed_lines, total_unexecuted_lines = CoverageUtils._aggregate_coverage(
60+
main_func_coverage, dependent_func_coverage
61+
)
62+
63+
total_lines = total_executed_lines | total_unexecuted_lines
64+
coverage = len(total_executed_lines) / len(total_lines) * 100 if total_lines else 0.0
65+
# coverage = (lines covered of the original function + its 1 level deep helpers) / (lines spanned by original function + its 1 level deep helpers), if no helpers then just the original function coverage
66+
67+
functions_being_tested = [main_func_coverage.name]
68+
if dependent_func_coverage:
69+
functions_being_tested.append(dependent_func_coverage.name)
70+
71+
graph = CoverageUtils._build_graph(main_func_coverage, dependent_func_coverage)
72+
temp_json_file.unlink()
73+
74+
return CoverageData(
75+
file_path=source_code_path,
76+
coverage=coverage,
77+
function_name=function_name,
78+
functions_being_tested=functions_being_tested,
79+
graph=graph,
80+
code_context=code_context,
81+
main_func_coverage=main_func_coverage,
82+
dependent_func_coverage=dependent_func_coverage,
83+
status=status,
84+
)
85+
86+
@staticmethod
87+
def _parse_coverage_file(
88+
coverage_file_path: Path, source_code_path: Path
89+
) -> tuple[dict[str, dict[str, Any]], CoverageStatus]:
90+
with coverage_file_path.open() as f:
91+
coverage_data = json.load(f)
92+
93+
candidates = generate_candidates(source_code_path)
94+
95+
logger.debug(f"Looking for coverage data in {' -> '.join(candidates)}")
96+
for candidate in candidates:
97+
try:
98+
cov: dict[str, dict[str, Any]] = coverage_data["files"][candidate]["functions"]
99+
logger.debug(f"Coverage data found for {source_code_path} in {candidate}")
100+
status = CoverageStatus.PARSED_SUCCESSFULLY
101+
break
102+
except KeyError:
103+
continue
104+
else:
105+
logger.debug(f"No coverage data found for {source_code_path} in {candidates}")
106+
cov = {}
107+
status = CoverageStatus.NOT_FOUND
108+
return cov, status
109+
110+
@staticmethod
111+
def _fetch_function_coverages(
112+
function_name: str,
113+
code_context: CodeOptimizationContext,
114+
coverage_data: dict[str, dict[str, Any]],
115+
original_cov_data: dict[str, dict[str, Any]],
116+
) -> tuple[FunctionCoverage, Union[FunctionCoverage, None]]:
117+
resolved_name = build_fully_qualified_name(function_name, code_context)
118+
try:
119+
main_function_coverage = FunctionCoverage(
120+
name=resolved_name,
121+
coverage=coverage_data[resolved_name]["summary"]["percent_covered"],
122+
executed_lines=coverage_data[resolved_name]["executed_lines"],
123+
unexecuted_lines=coverage_data[resolved_name]["missing_lines"],
124+
executed_branches=coverage_data[resolved_name]["executed_branches"],
125+
unexecuted_branches=coverage_data[resolved_name]["missing_branches"],
126+
)
127+
except KeyError:
128+
main_function_coverage = FunctionCoverage(
129+
name=resolved_name,
130+
coverage=0,
131+
executed_lines=[],
132+
unexecuted_lines=[],
133+
executed_branches=[],
134+
unexecuted_branches=[],
135+
)
136+
137+
dependent_function = extract_dependent_function(function_name, code_context)
138+
dependent_func_coverage = (
139+
CoverageUtils.grab_dependent_function_from_coverage_data(
140+
dependent_function, coverage_data, original_cov_data
141+
)
142+
if dependent_function
143+
else None
144+
)
145+
146+
return main_function_coverage, dependent_func_coverage
147+
148+
@staticmethod
149+
def _aggregate_coverage(
150+
main_func_coverage: FunctionCoverage, dependent_func_coverage: Union[FunctionCoverage, None]
151+
) -> tuple[set[int], set[int]]:
152+
total_executed_lines = set(main_func_coverage.executed_lines)
153+
total_unexecuted_lines = set(main_func_coverage.unexecuted_lines)
154+
155+
if dependent_func_coverage:
156+
total_executed_lines.update(dependent_func_coverage.executed_lines)
157+
total_unexecuted_lines.update(dependent_func_coverage.unexecuted_lines)
158+
159+
return total_executed_lines, total_unexecuted_lines
160+
161+
@staticmethod
162+
def _build_graph(
163+
main_func_coverage: FunctionCoverage, dependent_func_coverage: Union[FunctionCoverage, None]
164+
) -> dict[str, dict[str, Collection[object]]]:
165+
graph = {
166+
main_func_coverage.name: {
167+
"executed_lines": set(main_func_coverage.executed_lines),
168+
"unexecuted_lines": set(main_func_coverage.unexecuted_lines),
169+
"executed_branches": main_func_coverage.executed_branches,
170+
"unexecuted_branches": main_func_coverage.unexecuted_branches,
171+
}
172+
}
173+
174+
if dependent_func_coverage:
175+
graph[dependent_func_coverage.name] = {
176+
"executed_lines": set(dependent_func_coverage.executed_lines),
177+
"unexecuted_lines": set(dependent_func_coverage.unexecuted_lines),
178+
"executed_branches": dependent_func_coverage.executed_branches,
179+
"unexecuted_branches": dependent_func_coverage.unexecuted_branches,
180+
}
181+
182+
return graph
183+
184+
@staticmethod
185+
def grab_dependent_function_from_coverage_data(
186+
dependent_function_name: str,
187+
coverage_data: dict[str, dict[str, Any]],
188+
original_cov_data: dict[str, dict[str, Any]],
189+
) -> FunctionCoverage:
190+
"""Grab the dependent function from the coverage data."""
191+
try:
192+
return FunctionCoverage(
193+
name=dependent_function_name,
194+
coverage=coverage_data[dependent_function_name]["summary"]["percent_covered"],
195+
executed_lines=coverage_data[dependent_function_name]["executed_lines"],
196+
unexecuted_lines=coverage_data[dependent_function_name]["missing_lines"],
197+
executed_branches=coverage_data[dependent_function_name]["executed_branches"],
198+
unexecuted_branches=coverage_data[dependent_function_name]["missing_branches"],
199+
)
200+
except KeyError:
201+
msg = f"Coverage data not found for dependent function {dependent_function_name} in the coverage data"
202+
try:
203+
files = original_cov_data["files"]
204+
for file in files:
205+
functions = files[file]["functions"]
206+
for function in functions:
207+
if dependent_function_name in function:
208+
return FunctionCoverage(
209+
name=dependent_function_name,
210+
coverage=functions[function]["summary"]["percent_covered"],
211+
executed_lines=functions[function]["executed_lines"],
212+
unexecuted_lines=functions[function]["missing_lines"],
213+
executed_branches=functions[function]["executed_branches"],
214+
unexecuted_branches=functions[function]["missing_branches"],
215+
)
216+
msg = f"Coverage data not found for dependent function {dependent_function_name} in the original coverage data"
217+
except KeyError:
218+
raise ValueError(msg) from None
219+
220+
return FunctionCoverage(
221+
name=dependent_function_name,
222+
coverage=0,
223+
executed_lines=[],
224+
unexecuted_lines=[],
225+
executed_branches=[],
226+
unexecuted_branches=[],
227+
)
228+

0 commit comments

Comments
 (0)