Skip to content

Commit

Permalink
Package up base db and atomic db test suites into re-usable classes
Browse files Browse the repository at this point in the history
  • Loading branch information
pipermerriam committed Aug 6, 2019
1 parent d2ccc1e commit 5c2bb36
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 332 deletions.
2 changes: 1 addition & 1 deletion eth/db/backends/base.py
Expand Up @@ -40,7 +40,7 @@ def delete(self, key: bytes) -> None:
try:
del self[key]
except KeyError:
return None
pass

def __iter__(self) -> Iterator[bytes]:
raise NotImplementedError("By default, DB classes cannot be iterated.")
Expand Down
2 changes: 2 additions & 0 deletions eth/db/backends/level.py
Expand Up @@ -62,6 +62,8 @@ def _exists(self, key: bytes) -> bool:
return self.db.get(key) is not None

def __delitem__(self, key: bytes) -> None:
if self.db.get(key) is None:
raise KeyError(key)
self.db.delete(key)

@contextmanager
Expand Down
Empty file added eth/tools/db/__init__.py
Empty file.
158 changes: 158 additions & 0 deletions eth/tools/db/atomic.py
@@ -0,0 +1,158 @@
import pytest

from eth_utils import ValidationError

from eth.abc import AtomicDatabaseAPI


class AtomicDatabaseBatchAPITestSuite:
def test_atomic_batch_set_and_get(self, atomic_db: AtomicDatabaseAPI) -> None:
with atomic_db.atomic_batch() as batch:
batch.set(b'1', b'2')
assert batch.get(b'1') == b'2'

assert atomic_db.get(b'1') == b'2'

def test_atomic_db_cannot_recursively_batch(self, atomic_db: AtomicDatabaseAPI) -> None:
with atomic_db.atomic_batch() as batch:
assert not hasattr(batch, 'atomic_batch')

def test_atomic_db_with_set_and_delete_batch(self, atomic_db: AtomicDatabaseAPI) -> None:
atomic_db[b'key-1'] = b'origin'

with atomic_db.atomic_batch() as batch:
batch.delete(b'key-1')

assert b'key-1' not in batch
with pytest.raises(KeyError):
assert batch[b'key-1']

with pytest.raises(KeyError):
atomic_db[b'key-1']

def test_atomic_db_unbatched_sets_are_immediate(self, atomic_db: AtomicDatabaseAPI) -> None:
atomic_db[b'1'] = b'A'

with atomic_db.atomic_batch() as batch:
# Unbatched changes are immediate, and show up in batch reads
atomic_db[b'1'] = b'B'
assert batch[b'1'] == b'B'

batch[b'1'] = b'C1'

# It doesn't matter what changes happen underlying, all reads now
# show the write applied to the batch db handle
atomic_db[b'1'] = b'C2'
assert batch[b'1'] == b'C1'

# the batch write should overwrite any intermediate changes
assert atomic_db[b'1'] == b'C1'

def test_atomic_db_unbatched_deletes_are_immediate(self, atomic_db: AtomicDatabaseAPI) -> None:
atomic_db[b'1'] = b'A'

with atomic_db.atomic_batch() as batch:
assert b'1' in batch

# Unbatched changes are immediate, and show up in batch reads
del atomic_db[b'1']

assert b'1' not in batch

batch[b'1'] = b'C1'

# It doesn't matter what changes happen underlying, all reads now
# show the write applied to the batch db handle
atomic_db[b'1'] = b'C2'
assert batch[b'1'] == b'C1'

# the batch write should overwrite any intermediate changes
assert atomic_db[b'1'] == b'C1'

def test_atomic_db_cannot_use_batch_after_context(self, atomic_db: AtomicDatabaseAPI) -> None:
atomic_db[b'1'] = b'A'

with atomic_db.atomic_batch() as batch:
batch[b'1'] = b'B'

# set
with pytest.raises(ValidationError):
batch[b'1'] = b'C'

with pytest.raises(ValidationError):
batch.set(b'1', b'C')

# get
with pytest.raises(ValidationError):
batch[b'1']

with pytest.raises(ValidationError):
batch.get(b'1')

# exists
with pytest.raises(ValidationError):
b'1' in batch

with pytest.raises(ValidationError):
batch.exists(b'1')

# delete
with pytest.raises(ValidationError):
del batch[b'1']

with pytest.raises(ValidationError):
batch.delete(b'1')

# none of the invalid changes above should change the original db
assert atomic_db[b'1'] == b'B'

def test_atomic_db_with_reverted_delete_batch(self, atomic_db: AtomicDatabaseAPI) -> None:
class CustomException(Exception):
pass

atomic_db[b'key-1'] = b'origin'

with pytest.raises(CustomException):
with atomic_db.atomic_batch() as batch:
batch.delete(b'key-1')

assert b'key-1' not in batch
with pytest.raises(KeyError):
assert batch[b'key-1']

raise CustomException('pretend something went wrong')

assert atomic_db[b'key-1'] == b'origin'

def test_atomic_db_temporary_state_dropped_across_batches(self,
atomic_db: AtomicDatabaseAPI) -> None:
class CustomException(Exception):
pass

atomic_db[b'key-1'] = b'origin'

with pytest.raises(CustomException):
with atomic_db.atomic_batch() as batch:
batch.delete(b'key-1')
batch.set(b'key-2', b'val-2')
raise CustomException('pretend something went wrong')

with atomic_db.atomic_batch() as batch:
assert batch[b'key-1'] == b'origin'
assert b'key-2' not in batch

def test_atomic_db_with_exception_batch(self, atomic_db: AtomicDatabaseAPI) -> None:
atomic_db.set(b'key-1', b'value-1')

try:
with atomic_db.atomic_batch() as batch:
batch.set(b'key-1', b'new-value-1')
batch.set(b'key-2', b'value-2')
raise Exception
except Exception:
pass

assert atomic_db.get(b'key-1') == b'value-1'

with pytest.raises(KeyError):
atomic_db[b'key-2']
76 changes: 76 additions & 0 deletions eth/tools/db/base.py
@@ -0,0 +1,76 @@
import pytest

from eth.abc import DatabaseAPI


class DatabaseAPITestSuite:
def test_database_api_get(self, db: DatabaseAPI) -> None:
db[b'key-1'] = b'value-1'
assert db.get(b'key-1') == b'value-1'

def test_database_api_item_getter(self, db: DatabaseAPI) -> None:
db[b'key-1'] = b'value-1'
assert db[b'key-1'] == b'value-1'

def test_database_api_get_missing_key(self, db: DatabaseAPI) -> None:
assert b'key-1' not in db
assert db.get(b'key-1') is None

def test_database_api_item_getter_missing_key(self, db: DatabaseAPI) -> None:
assert b'key-1' not in db
with pytest.raises(KeyError):
db[b'key-1']

def test_database_api_set(self, db: DatabaseAPI) -> None:
db[b'key-1'] = b'value-1'
assert db[b'key-1'] == b'value-1'
db[b'key-1'] = b'value-2'
assert db[b'key-1'] == b'value-2'

def test_database_api_item_setter(self, db: DatabaseAPI) -> None:
db.set(b'key-1', b'value-1')
assert db[b'key-1'] == b'value-1'
db.set(b'key-1', b'value-2')
assert db[b'key-1'] == b'value-2'

def test_database_api_exists(self, db: DatabaseAPI) -> None:
assert db.exists(b'key-1') is False

db[b'key-1'] = b'value-1'

assert db.exists(b'key-1') is True

def test_database_api_contains_checking(self, db: DatabaseAPI) -> None:
assert b'key-1' not in db

db[b'key-1'] = b'value-1'

assert b'key-1' in db

def test_database_api_delete(self, db: DatabaseAPI) -> None:
db[b'key-1'] = b'value-1'

assert b'key-1' in db

db.delete(b'key-1')

assert not db.exists(b'key-1')
assert b'key-1' not in db

def test_database_api_item_delete(self, db: DatabaseAPI) -> None:
db[b'key-1'] = b'value-1'

assert b'key-1' in db

del db[b'key-1']

assert b'key-1' not in db

def test_database_api_delete_missing_key(self, db: DatabaseAPI) -> None:
assert b'key-1' not in db
db.delete(b'key-1')

def test_database_api_item_delete_missing_key(self, db: DatabaseAPI) -> None:
assert b'key-1' not in db
with pytest.raises(KeyError):
del db[b'key-1']
30 changes: 30 additions & 0 deletions tests/database/test_atomic_database_api.py
@@ -0,0 +1,30 @@
import pytest

from eth.db.atomic import AtomicDB
from eth.db.backends.level import LevelDB

from eth.tools.db.base import DatabaseAPITestSuite
from eth.tools.db.atomic import AtomicDatabaseBatchAPITestSuite


@pytest.fixture(params=['atomic', 'level'])
def atomic_db(request, tmpdir):
if request.param == 'atomic':
return AtomicDB()
elif request.param == 'level':
return LevelDB(db_path=tmpdir.mkdir("level_db_path"))
else:
raise ValueError("Unexpected database type: {}".format(request.param))


@pytest.fixture
def db(atomic_db):
return atomic_db


class TestAtomicDatabaseBatchAPI(AtomicDatabaseBatchAPITestSuite):
pass


class TestAtomicDatabaseAPI(DatabaseAPITestSuite):
pass
112 changes: 0 additions & 112 deletions tests/database/test_atomic_db.py

This file was deleted.

0 comments on commit 5c2bb36

Please sign in to comment.