Skip to content

Commit

Permalink
test: fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Dec 6, 2024
1 parent 72c403f commit 6512afc
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 58 deletions.
19 changes: 16 additions & 3 deletions src/ape/pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,10 +549,16 @@ class IsolationManager(ManagerAccessMixin):
supported: bool = True
snapshots: SnapshotRegistry = SnapshotRegistry()

def __init__(self, config_wrapper: "ConfigWrapper", receipt_capture: "ReceiptCapture"):
def __init__(
self,
config_wrapper: "ConfigWrapper",
receipt_capture: "ReceiptCapture",
chain_snapshots: Optional[dict] = None,
):
self.config_wrapper = config_wrapper
self.receipt_capture = receipt_capture
self._records: list[str] = []
self._chain_snapshots = chain_snapshots

@cached_property
def _track_transactions(self) -> bool:
Expand All @@ -562,6 +568,10 @@ def _track_transactions(self) -> bool:
and (self.config_wrapper.track_gas or self.config_wrapper.track_coverage)
)

@property
def chain_snapshots(self) -> dict:
return self._chain_snapshots or self.chain_manager._snapshots

def get_snapshot(self, scope: Scope) -> Snapshot:
return self.snapshots[scope]

Expand Down Expand Up @@ -634,7 +644,7 @@ def restore(self, scope: Scope):
if snapshot_id is None:
return

elif snapshot_id not in self.chain_manager._snapshots[self.provider.chain_id]:
elif snapshot_id not in self.chain_snapshots[self.provider.chain_id]:
# Still clear out.
self.snapshots.clear_snapshot_id(scope)
return
Expand All @@ -643,7 +653,7 @@ def restore(self, scope: Scope):
self._records.append(f"restoring '{scope.name.upper()}'")

try:
self.chain_manager.restore(snapshot_id)
self._restore(snapshot_id)
except NotImplementedError:
logger.warning(
"The connected provider does not support snapshotting. "
Expand All @@ -654,6 +664,9 @@ def restore(self, scope: Scope):

self.snapshots.clear_snapshot_id(scope)

def _restore(self, snapshot_id: "SnapshotID"):
self.chain_manager.restore(snapshot_id)

def show_records(self):
if not self._records:
return
Expand Down
21 changes: 18 additions & 3 deletions src/ape_test/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,34 @@ def __init__(
):
self.config = config
self.chain_id = chain_id
self._backend = backend
self._ethereum_tester = None if backend is None else EthereumTester(backend)

@property
def ethereum_tester(self) -> EthereumTester:
if self._ethereum_tester is None:
backend = ApeEVMBackend(self.config)
self._ethereum_tester = EthereumTester(backend)
self._backend = ApeEVMBackend(self.config)
self._ethereum_tester = EthereumTester(self._backend)

return self._ethereum_tester

@ethereum_tester.setter
def ethereum_tester(self, value):
self._ethereum_tester = value
self._backend = value.backend

@property
def backend(self) -> "ApeEVMBackend":
if self._backend is None:
self._backend = ApeEVMBackend(self.config)
self._ethereum_tester = EthereumTester(self._backend)

return self._backend

@backend.setter
def backend(self, value):
self._backend = value
self._ethereum_tester = EthereumTester(self._backend)

@cached_property
def api_endpoints(self) -> dict: # type: ignore
Expand All @@ -143,7 +158,7 @@ def config(self) -> "ApeTestConfig": # type: ignore

@property
def evm_backend(self) -> ApeEVMBackend:
return self.tester.ethereum_tester.backend
return self.tester.backend

@cached_property
def tester(self) -> ApeTester:
Expand Down
156 changes: 104 additions & 52 deletions tests/functional/test_fixtures.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from typing import Optional

import pytest

from ape.exceptions import BlockNotFoundError
from ape.pytest.fixtures import IsolationManager, PytestApeFixtures
from ape.pytest.utils import Scope
from ape.types.vm import SnapshotID


@pytest.fixture
Expand Down Expand Up @@ -35,24 +38,6 @@ def mock_evm(mocker):
return mocker.MagicMock()


@pytest.fixture
def use_mock_provider(networks, mock_provider, mock_evm):
orig_provider = networks.active_provider
mock_provider._web3.eth.get_block.side_effect = orig_provider._web3.eth.get_block
networks.active_provider = mock_provider
orig_backend = mock_provider._evm_backend

# Ensure functional isolation still uses snapshot.
mock_evm.take_snapshot.side_effect = orig_backend.take_snapshot

try:
mock_provider._evm_backend = mock_evm
yield mock_provider
finally:
mock_provider._evm_backend = orig_backend
networks.active_provider = orig_provider


def test_isolation(isolation, receipt_capture):
# Set up receipt capture to fail on __exit__
# AFTER the yield statement. There was a bug
Expand Down Expand Up @@ -97,65 +82,132 @@ def test_isolation_restore_not_implemented(mocker, networks, fixtures):


@pytest.mark.parametrize("snapshot_id", (0, 1, "123"))
def test_isolation_snapshot_id_types(snapshot_id, use_mock_provider, fixtures, mock_evm):
mock_evm.take_snapshot.side_effect = lambda: snapshot_id
isolation_context = fixtures.isolation_manager.isolation(Scope.FUNCTION)
def test_isolation_snapshot_id_types(snapshot_id, fixtures):
class IsolationManagerWithCustomSnapshot(IsolationManager):
take_call_count = 0
restore_call_count = 0
restore_called_with = []

def take_snapshot(self) -> Optional[SnapshotID]:
self.take_call_count += 1
return snapshot_id

def restore(self, scope: Scope):
self.restore_call_count += 1
self.restore_called_with.append(scope)

isolation_manager = IsolationManagerWithCustomSnapshot(
fixtures.isolation_manager.config_wrapper,
fixtures.isolation_manager.receipt_capture,
)
isolation_context = isolation_manager.isolation(Scope.FUNCTION)
next(isolation_context) # Enter.
assert mock_evm.take_snapshot.call_count == 1
assert mock_evm.revert_to_snapshot.call_count == 0
assert isolation_manager.take_call_count == 1
assert isolation_manager.restore_call_count == 0
next(isolation_context, None) # Exit.
mock_evm.revert_to_snapshot.assert_called_once_with(snapshot_id)
assert isolation_manager.restore_called_with == [Scope.FUNCTION]


def test_isolation_when_snapshot_fails_avoids_restore(fixtures):
class IsolationManagerFailingAtSnapshotting(IsolationManager):
take_called = False
restore_called = False

def take_snapshot(self) -> Optional["SnapshotID"]:
self.take_called = True
raise NotImplementedError()

def test_isolation_when_snapshot_fails_avoids_restore(use_mock_provider, fixtures, mock_evm):
mock_evm.take_snapshot.side_effect = NotImplementedError
isolation_context = fixtures.isolation_manager.isolation(Scope.FUNCTION)
def restore(self, scope: Scope):
self.restore_called = True

isolation_manager = IsolationManagerFailingAtSnapshotting(
fixtures.isolation_manager.config_wrapper,
fixtures.isolation_manager.receipt_capture,
)
isolation_context = isolation_manager.isolation(Scope.FUNCTION)
next(isolation_context) # Enter.
assert mock_evm.take_snapshot.call_count == 1
assert mock_evm.revert_to_snapshot.call_count == 0
assert isolation_manager.take_called
assert not isolation_manager.restore_called
next(isolation_context, None) # Exit.
# It doesn't even try!
assert mock_evm.revert_to_snapshot.call_count == 0
assert not isolation_manager.restore_called


def test_isolation_restore_fails_avoids_snapshot_next_time(fixtures):
chain_snapshots = {}

class IsolationManagerFailingAtRestoring(IsolationManager):
take_called = False
restore_called = False

def take_snapshot(self) -> Optional["SnapshotID"]:
self.take_called = True
chain_snapshots[self.provider.chain_id] = ["123"]
return "123"

def test_isolation_restore_fails_avoids_snapshot_next_time(
networks, use_mock_provider, fixtures, mock_evm
):
mock_evm.take_snapshot.return_value = 123
mock_evm.revert_to_snapshot.side_effect = NotImplementedError
isolation_context = fixtures.isolation_manager.isolation(Scope.FUNCTION)
def _restore(self, snapshot_id: SnapshotID):
self.restore_called = True
raise NotImplementedError()

def reset_mock(self):
self.take_called = False
self.restore_called = False

isolation_manager = IsolationManagerFailingAtRestoring(
fixtures.isolation_manager.config_wrapper,
fixtures.isolation_manager.receipt_capture,
chain_snapshots=chain_snapshots,
)
isolation_context = isolation_manager.isolation(Scope.FUNCTION)
next(isolation_context) # Enter.
# Snapshot works, we get this far.
assert mock_evm.take_snapshot.call_count == 1
assert mock_evm.revert_to_snapshot.call_count == 0
assert isolation_manager.take_called
assert not isolation_manager.restore_called

# At this point, it is realized snapshotting is no-go.
mock_evm.take_snapshot.reset_mock()
# At this point, it realized snapshotting is no-go.
next(isolation_context, None) # Exit.
isolation_context = fixtures.isolation_manager.isolation(Scope.FUNCTION)
assert isolation_manager.restore_called

isolation_manager.reset_mock()
isolation_context = isolation_manager.isolation(Scope.FUNCTION)
next(isolation_context) # Enter again.

# This time, snapshotting is NOT attempted.
assert mock_evm.take_snapshot.call_count == 0
assert not isolation_manager.take_called
assert not isolation_manager.restore_called


def test_isolation_supported_flag_set_after_successful_snapshot(
use_mock_provider, fixtures, mock_evm
):
def test_isolation_supported_flag_set_after_successful_snapshot(fixtures):
"""
Testing the unusual case where `.supported` was changed manually after
a successful snapshot and before the restore attempt.
"""
mock_evm.take_snapshot.return_value = 123
isolation_context = fixtures.isolation_manager.isolation(Scope.FUNCTION)

class CustomIsolationManager(IsolationManager):
take_called = False
restore_called = False

def take_snapshot(self) -> Optional["SnapshotID"]:
self.take_called = True
return 123

def restore(self, scope: Scope):
self.restore_called = True

isolation_manager = CustomIsolationManager(
fixtures.isolation_manager.config_wrapper,
fixtures.isolation_manager.receipt_capture,
)
isolation_context = isolation_manager.isolation(Scope.FUNCTION)
next(isolation_context) # Enter.
assert mock_evm.take_snapshot.call_count == 1
assert mock_evm.revert_to_snapshot.call_count == 0
assert isolation_manager.take_called
assert not isolation_manager.restore_called

# HACK: Change the flag manually to show it will avoid
# the restore.
fixtures.isolation_manager.supported = False

isolation_manager.supported = False
isolation_manager.take_called = False # Reset
next(isolation_context, None) # Exit.
# Even though snapshotting worked, the flag was changed,
# and so the restore never gets attempted.
assert mock_evm.revert_to_snapshot.call_count == 0
assert not isolation_manager.take_called

0 comments on commit 6512afc

Please sign in to comment.