From 8b24abde3103d07b7858786a08847b40e10f8b0a Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 6 May 2019 00:31:57 +0200 Subject: [PATCH] implement spectest decorator, update attestation tests --- .../test_process_attestation.py | 93 ++++++++++--------- test_libs/pyspec/tests/conftest.py | 19 ---- test_libs/pyspec/tests/context.py | 10 ++ test_libs/pyspec/tests/utils.py | 46 +++++++++ 4 files changed, 104 insertions(+), 64 deletions(-) create mode 100644 test_libs/pyspec/tests/context.py create mode 100644 test_libs/pyspec/tests/utils.py diff --git a/test_libs/pyspec/tests/block_processing/test_process_attestation.py b/test_libs/pyspec/tests/block_processing/test_process_attestation.py index bcf71376c..c90d13fd5 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_attestation.py +++ b/test_libs/pyspec/tests/block_processing/test_process_attestation.py @@ -8,8 +8,7 @@ from eth2spec.phase0.state_transition import ( ) from eth2spec.phase0.spec import ( get_current_epoch, - process_attestation, - slot_to_epoch, + process_attestation ) from tests.helpers import ( build_empty_block_for_next_slot, @@ -18,63 +17,75 @@ from tests.helpers import ( next_slot, ) - -# mark entire file as 'attestations' -pytestmark = pytest.mark.attestations +from tests.utils import spectest +from tests.context import with_state def run_attestation_processing(state, attestation, valid=True): """ - Run ``process_attestation`` returning the pre and post state. + Run ``process_attestation``, yielding pre-state ('pre'), attestation ('attestation'), and post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ - post_state = deepcopy(state) + # yield pre-state + yield 'pre', state + yield 'attestation', attestation + + # If the attestation is invalid, processing is aborted, and there is no post-state. if not valid: with pytest.raises(AssertionError): - process_attestation(post_state, attestation) - return state, None + process_attestation(state, attestation) + yield 'post', None + return - process_attestation(post_state, attestation) + current_epoch_count = len(state.current_epoch_attestations) + previous_epoch_count = len(state.previous_epoch_attestations) - current_epoch = get_current_epoch(state) - if attestation.data.target_epoch == current_epoch: - assert len(post_state.current_epoch_attestations) == len(state.current_epoch_attestations) + 1 + # process attestation + process_attestation(state, attestation) + + # Make sure the attestation has been processed + if attestation.data.target_epoch == get_current_epoch(state): + assert len(state.current_epoch_attestations) == current_epoch_count + 1 else: - assert len(post_state.previous_epoch_attestations) == len(state.previous_epoch_attestations) + 1 + assert len(state.previous_epoch_attestations) == previous_epoch_count + 1 - return state, post_state + # yield post-state + yield 'post', state +# shorthand for decorating @with_state @spectest() +def attestation_test(fn): + return with_state(spectest()(fn)) + + +@attestation_test def test_success(state): attestation = get_valid_attestation(state) state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - pre_state, post_state = run_attestation_processing(state, attestation) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation) +@attestation_test def test_success_prevous_epoch(state): attestation = get_valid_attestation(state) block = build_empty_block_for_next_slot(state) block.slot = state.slot + spec.SLOTS_PER_EPOCH state_transition(state, block) - pre_state, post_state = run_attestation_processing(state, attestation) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation) +@attestation_test def test_before_inclusion_delay(state): attestation = get_valid_attestation(state) # do not increment slot to allow for inclusion delay - pre_state, post_state = run_attestation_processing(state, attestation, False) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation, False) +@attestation_test def test_after_epoch_slots(state): attestation = get_valid_attestation(state) block = build_empty_block_for_next_slot(state) @@ -82,44 +93,40 @@ def test_after_epoch_slots(state): block.slot = state.slot + spec.SLOTS_PER_EPOCH + 1 state_transition(state, block) - pre_state, post_state = run_attestation_processing(state, attestation, False) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation, False) +@attestation_test def test_bad_source_epoch(state): attestation = get_valid_attestation(state) state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY attestation.data.source_epoch += 10 - pre_state, post_state = run_attestation_processing(state, attestation, False) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation, False) +@attestation_test def test_bad_source_root(state): attestation = get_valid_attestation(state) state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY attestation.data.source_root = b'\x42' * 32 - pre_state, post_state = run_attestation_processing(state, attestation, False) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation, False) +@attestation_test def test_non_zero_crosslink_data_root(state): attestation = get_valid_attestation(state) state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY attestation.data.crosslink_data_root = b'\x42' * 32 - pre_state, post_state = run_attestation_processing(state, attestation, False) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation, False) +@attestation_test def test_bad_previous_crosslink(state): next_epoch(state) attestation = get_valid_attestation(state) @@ -128,28 +135,24 @@ def test_bad_previous_crosslink(state): state.current_crosslinks[attestation.data.shard].epoch += 10 - pre_state, post_state = run_attestation_processing(state, attestation, False) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation, False) +@attestation_test def test_non_empty_custody_bitfield(state): attestation = get_valid_attestation(state) state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY attestation.custody_bitfield = deepcopy(attestation.aggregation_bitfield) - pre_state, post_state = run_attestation_processing(state, attestation, False) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation, False) +@attestation_test def test_empty_aggregation_bitfield(state): attestation = get_valid_attestation(state) state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY attestation.aggregation_bitfield = b'\x00' * len(attestation.aggregation_bitfield) - pre_state, post_state = run_attestation_processing(state, attestation, False) - - return pre_state, attestation, post_state + yield from run_attestation_processing(state, attestation, False) diff --git a/test_libs/pyspec/tests/conftest.py b/test_libs/pyspec/tests/conftest.py index 9840dc7b2..8b90ce3b0 100644 --- a/test_libs/pyspec/tests/conftest.py +++ b/test_libs/pyspec/tests/conftest.py @@ -3,10 +3,6 @@ import pytest from eth2spec.phase0 import spec from preset_loader import loader -from .helpers import ( - create_genesis_state, -) - def pytest_addoption(parser): parser.addoption( @@ -19,18 +15,3 @@ def config(request): config_name = request.config.getoption("--config") presets = loader.load_presets('../../configs/', config_name) spec.apply_constants_preset(presets) - - -@pytest.fixture -def num_validators(config): - return spec.SLOTS_PER_EPOCH * 8 - - -@pytest.fixture -def deposit_data_leaves(): - return list() - - -@pytest.fixture -def state(num_validators, deposit_data_leaves): - return create_genesis_state(num_validators, deposit_data_leaves) diff --git a/test_libs/pyspec/tests/context.py b/test_libs/pyspec/tests/context.py new file mode 100644 index 000000000..27a91a031 --- /dev/null +++ b/test_libs/pyspec/tests/context.py @@ -0,0 +1,10 @@ + +from eth2spec.phase0 import spec +from tests.utils import with_args + +from .helpers import ( + create_genesis_state, +) + +# Provides a genesis state as first argument to the function decorated with this +with_state = with_args(lambda: [create_genesis_state(spec.SLOTS_PER_EPOCH * 8, list())]) diff --git a/test_libs/pyspec/tests/utils.py b/test_libs/pyspec/tests/utils.py new file mode 100644 index 000000000..f13eede65 --- /dev/null +++ b/test_libs/pyspec/tests/utils.py @@ -0,0 +1,46 @@ +from eth2spec.debug.encode import encode + + +def spectest(description: str = None): + def runner(fn): + # this wraps the function, to hide that the function actually yielding data. + def entry(*args, **kw): + # check generator mode, may be None/else. + # "pop" removes it, so it is not passed to the inner function. + if kw.pop('generator_mode', False) is True: + out = {} + if description is None: + # fall back on function name for test description + name = fn.__name__ + if name.startswith('test_'): + name = name[5:] + out['description'] = name + else: + # description can be explicit + out['description'] = description + # put all generated data into a dict. + for data in fn(*args, **kw): + # If there is a type argument, encode it as that type. + if len(data) == 3: + (key, value, typ) = data + out[key] = encode(value, typ) + else: + # Otherwise, just put the raw value. + (key, value) = data + out[key] = value + return out + else: + # just complete the function, ignore all yielded data, we are not using it + for _ in fn(*args, **kw): + continue + return entry + return runner + + +def with_args(create_args): + def runner(fn): + # this wraps the function, to hide that the function actually yielding data. + def entry(*args, **kw): + return fn(*(create_args() + list(args)), **kw) + return entry + return runner