Skip to content

Commit

Permalink
feat: track coverage on calls [APE-1026] (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Jul 5, 2023
1 parent 07f4d30 commit 57b065a
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 7 deletions.
42 changes: 36 additions & 6 deletions ape_foundry/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@
VirtualMachineError,
)
from ape.logging import logger
from ape.types import AddressType, BlockID, CallTreeNode, ContractCode, SnapshotID, TraceFrame
from ape.types import (
AddressType,
BlockID,
CallTreeNode,
ContractCode,
SnapshotID,
SourceTraceback,
TraceFrame,
)
from ape.utils import cached_property
from ape_test import Config as TestConfig
from eth_typing import HexStr
Expand Down Expand Up @@ -525,8 +533,15 @@ def send_call(self, txn: TransactionAPI, **kwargs: Any) -> bytes:

show_gas = kwargs.pop("show_gas_report", False)
show_trace = kwargs.pop("show_trace", False)
track_gas = self._test_runner is not None and self._test_runner.gas_tracker.enabled
needs_trace = show_gas or show_trace or track_gas

if self._test_runner is not None:
track_gas = self._test_runner.gas_tracker.enabled
track_coverage = self._test_runner.coverage_tracker.enabled
else:
track_gas = False
track_coverage = False

needs_trace = track_gas or track_coverage or show_gas or show_trace
if not needs_trace:
return self._eth_call(arguments)

Expand All @@ -537,6 +552,7 @@ def send_call(self, txn: TransactionAPI, **kwargs: Any) -> bytes:
arguments[0]["type"] = to_hex(arguments[0]["type"])

result, trace_frames = self._trace_call(arguments)
trace_frames, frames_copy = tee(trace_frames)
return_value = HexBytes(result["returnValue"])
root_node_kwargs = {
"gas_cost": result.get("gas", 0),
Expand All @@ -558,13 +574,27 @@ def send_call(self, txn: TransactionAPI, **kwargs: Any) -> bytes:
# Optimization to enrich early and in_place=True.
call_tree.enrich()

if track_gas and call_tree and receiver is not None:
if track_gas and call_tree and receiver is not None and self._test_runner is not None:
# Gas report being collected, likely for showing a report
# at the end of a test run.
# Use `in_place=False` in case also `show_trace=True`
enriched_call_tree = call_tree.enrich(in_place=False)
if self._test_runner:
self._test_runner.gas_tracker.append_gas(enriched_call_tree, receiver)
self._test_runner.gas_tracker.append_gas(enriched_call_tree, receiver)

if track_coverage and self._test_runner is not None and receiver:
contract_type = self.chain_manager.contracts.get(receiver)
if contract_type:
traceframes = (self._create_trace_frame(x) for x in frames_copy)
method_id = HexBytes(txn.data)
selector = (
contract_type.methods[method_id].selector
if method_id in contract_type.methods
else None
)
source_traceback = SourceTraceback.create(contract_type, traceframes, method_id)
self._test_runner.coverage_tracker.cover(
source_traceback, function=selector, contract=contract_type.name
)

if show_gas:
enriched_call_tree = call_tree.enrich(in_place=False)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
url="https://github.com/ApeWorX/ape-foundry",
include_package_data=True,
install_requires=[
"eth-ape>=0.6.9,<0.7",
"eth-ape>=0.6.12,<0.7",
"evm-trace", # Use same version as ape
"hexbytes", # Use same version as ape
"web3", # Use same version as ape
Expand Down
14 changes: 14 additions & 0 deletions tests/test_gas_report.py → tests/test_pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
transfer +\d +\d+ + \d+ + \d+ + \d+
{TOKEN_B_GAS_REPORT}
"""
COVERAGE_START_PATTERN = re.compile(r"=+ Coverage Profile =+")


def filter_expected_methods(*methods_to_remove: str) -> str:
Expand Down Expand Up @@ -102,3 +103,16 @@ def test_gas_flag_exclude_method_using_cli_option(ape_pytester):
expected = expected.replace(TOKEN_B_GAS_REPORT, "")
result = ape_pytester.runpytest("--gas", "--gas-exclude", "*:fooAndBar,*:myNumber,tokenB:*")
run_gas_test(result, expected_report=expected)


@pytest.mark.fork
def test_coverage(ape_pytester):
"""
NOTE: Since vyper is required, we are unable to have decent tests
verifying Foundry in coverage.
TODO: Write + Run tests in an env with both vyper and foundry.
"""
result = ape_pytester.runpytest("--coverage")
result.assert_outcomes(passed=NUM_TESTS)
assert any("Coverage Profile" in ln for ln in result.outlines)
assert any("WARNING: No coverage data found." in ln for ln in result.outlines)

0 comments on commit 57b065a

Please sign in to comment.