From a8325723ba283c63cd820ded97d76640cb3c6cd8 Mon Sep 17 00:00:00 2001 From: Jannik Luhn Date: Thu, 28 Mar 2019 11:45:12 +0100 Subject: [PATCH 1/6] Remove outdated uint test generator --- ssz/uint_test_generators.py | 132 ------------------------------------ 1 file changed, 132 deletions(-) delete mode 100644 ssz/uint_test_generators.py diff --git a/ssz/uint_test_generators.py b/ssz/uint_test_generators.py deleted file mode 100644 index a353785..0000000 --- a/ssz/uint_test_generators.py +++ /dev/null @@ -1,132 +0,0 @@ -import random - -from eth_utils import ( - to_tuple, -) - -import ssz -from ssz.sedes import ( - UInt, -) -from renderers import ( - render_test, - render_test_case, -) - -random.seed(0) - - -BIT_SIZES = [i for i in range(8, 512 + 1, 8)] -RANDOM_TEST_CASES_PER_BIT_SIZE = 10 -RANDOM_TEST_CASES_PER_LENGTH = 3 - - -def get_random_bytes(length): - return bytes(random.randint(0, 255) for _ in range(length)) - - -def generate_uint_bounds_test(): - test_cases = generate_uint_bounds_test_cases() + generate_uint_out_of_bounds_test_cases() - - return render_test( - title="UInt Bounds", - summary="Integers right at or beyond the bounds of the allowed value range", - version="0.1", - test_cases=test_cases, - ) - - -def generate_uint_random_test(): - test_cases = generate_random_uint_test_cases() - - return render_test( - title="UInt Random", - summary="Random integers chosen uniformly over the allowed value range", - version="0.1", - test_cases=test_cases, - ) - - -def generate_uint_wrong_length_test(): - test_cases = generate_uint_wrong_length_test_cases() - - return render_test( - title="UInt Wrong Length", - summary="Serialized integers that are too short or too long", - version="0.1", - test_cases=test_cases, - ) - - -@to_tuple -def generate_random_uint_test_cases(): - for bit_size in BIT_SIZES: - sedes = UInt(bit_size) - - for _ in range(RANDOM_TEST_CASES_PER_BIT_SIZE): - value = random.randrange(0, 2 ** bit_size) - serial = ssz.encode(value, sedes) - # note that we need to create the tags in each loop cycle, otherwise ruamel will use - # YAML references which makes the resulting file harder to read - tags = tuple(["atomic", "uint", "random"]) - yield render_test_case( - sedes=sedes, - valid=True, - value=value, - serial=serial, - tags=tags, - ) - - -@to_tuple -def generate_uint_wrong_length_test_cases(): - for bit_size in BIT_SIZES: - sedes = UInt(bit_size) - lengths = sorted({ - 0, - sedes.length // 2, - sedes.length - 1, - sedes.length + 1, - sedes.length * 2, - }) - for length in lengths: - for _ in range(RANDOM_TEST_CASES_PER_LENGTH): - tags = tuple(["atomic", "uint", "wrong_length"]) - yield render_test_case( - sedes=sedes, - valid=False, - serial=get_random_bytes(length), - tags=tags, - ) - - -@to_tuple -def generate_uint_bounds_test_cases(): - common_tags = ("atomic", "uint") - for bit_size in BIT_SIZES: - sedes = UInt(bit_size) - - for value, tag in ((0, "uint_lower_bound"), (2 ** bit_size - 1, "uint_upper_bound")): - serial = ssz.encode(value, sedes) - yield render_test_case( - sedes=sedes, - valid=True, - value=value, - serial=serial, - tags=common_tags + (tag,), - ) - - -@to_tuple -def generate_uint_out_of_bounds_test_cases(): - common_tags = ("atomic", "uint") - for bit_size in BIT_SIZES: - sedes = UInt(bit_size) - - for value, tag in ((-1, "uint_underflow"), (2 ** bit_size, "uint_overflow")): - yield render_test_case( - sedes=sedes, - valid=False, - value=value, - tags=common_tags + (tag,), - ) From 35029f50731a97fb3b5ec522f6a5e62ec8cacab4 Mon Sep 17 00:00:00 2001 From: Jannik Luhn Date: Thu, 28 Mar 2019 11:45:46 +0100 Subject: [PATCH 2/6] Remove alias renderers and add vector renderer --- ssz/renderers.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/ssz/renderers.py b/ssz/renderers.py index 394d783..63feb3f 100644 --- a/ssz/renderers.py +++ b/ssz/renderers.py @@ -11,11 +11,10 @@ from ssz.sedes import ( BaseSedes, Boolean, - Bytes, - BytesN, Container, List, UInt, + Vector, ) @@ -45,13 +44,10 @@ def render_type_definition(sedes): return "bool" elif isinstance(sedes, UInt): - return f"uint{sedes.length * 8}" + return f"uint{sedes.size * 8}" - elif isinstance(sedes, BytesN): - return f"bytes{sedes.length}" - - elif isinstance(sedes, Bytes): - return f"bytes" + elif isinstance(sedes, Vector): + return [render_type_definition(sedes.element_sedes), sedes.number_of_elements] elif isinstance(sedes, List): return [render_type_definition(sedes.element_sedes)] @@ -99,4 +95,4 @@ def render_test(*, title, summary, version, test_cases): if summary is not None: yield "summary", summary yield "version", version - yield "test_cases", test_cases + yield "test_cases", tuple(test_cases) From 6d4c98c9576d3f86545ec529ce1ea2c003113506 Mon Sep 17 00:00:00 2001 From: Jannik Luhn Date: Thu, 28 Mar 2019 11:46:53 +0100 Subject: [PATCH 3/6] Add test generator for basic types --- ssz/basic_types.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 ssz/basic_types.py diff --git a/ssz/basic_types.py b/ssz/basic_types.py new file mode 100644 index 0000000..fefea73 --- /dev/null +++ b/ssz/basic_types.py @@ -0,0 +1,72 @@ +from random import Random + +import ssz +from ssz.sedes import ( + boolean, + UInt, +) + +from renderers import ( + render_test_case, + render_test, +) + + +random = Random(0) + +UINT_SIZES = tuple(8 * 2**exponent for exponent in range(0, 6)) # 8, 16, ..., 256 +NUM_RANDOM_UINT_VALUES = 16 + + +def generate_uint_test_cases(): + for bit_size in UINT_SIZES: + sedes = UInt(bit_size) + max_int = 2**bit_size - 1 + + values = ( + 0, + 1, + max_int - 1, + max_int, + ) + tuple( + random.randint(0, max_int) for _ in range(NUM_RANDOM_UINT_VALUES) + ) + + for value in values: + serial = ssz.encode(value, sedes) + yield render_test_case( + sedes=sedes, + valid=True, + value=value, + serial=serial, + tags=["basic", "uint"], + ) + + +def generate_uint_test(): + return render_test( + title="UInt", + summary="UInt tests for all sizes", + version="0.1", + test_cases=generate_uint_test_cases(), + ) + + +def generate_bool_test_cases(): + for value in (True, False): + yield render_test_case( + sedes=boolean, + valid=True, + value=value, + serial=ssz.encode(value, boolean), + tags=["basic", "bool"], + ) + + +def generate_bool_test(): + return render_test( + title="Bool", + summary="Tests for the two bool values", + version="0.1", + test_cases=generate_bool_test_cases(), + ) From 190d2438b385b3d13f941496dbd1674677309171 Mon Sep 17 00:00:00 2001 From: Jannik Luhn Date: Thu, 28 Mar 2019 11:47:06 +0100 Subject: [PATCH 4/6] Add test generator for flat composite types --- ssz/flat_composite_types.py | 168 ++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 ssz/flat_composite_types.py diff --git a/ssz/flat_composite_types.py b/ssz/flat_composite_types.py new file mode 100644 index 0000000..1ce488e --- /dev/null +++ b/ssz/flat_composite_types.py @@ -0,0 +1,168 @@ +from itertools import chain +from random import Random + +import ssz +from ssz.sedes import ( + boolean, + Boolean, + Container, + List, + UInt, + Vector, +) + +from renderers import ( + render_test_case, + render_test, +) + + +random = Random(0) + +UINT_SIZES = tuple(8 * 2**exponent for exponent in range(0, 6)) # 8, 16, ..., 256 +NUM_RANDOM_LENGTHS = 16 +MAX_RANDOM_LIST_LENGTH = 512 +MAX_RANDOM_VECTOR_LENGTH = 512 +MAX_RANDOM_CONTAINER_LENGTH = 32 + + +def get_random_basic_value(sedes): + if isinstance(sedes, Boolean): + return random.choice((True, False)) + elif isinstance(sedes, UInt): + return random.randint(0, 8**sedes.size) + else: + raise ValueError("Neither bool nor UInt") + + +def generate_flat_list_test_cases(): + for element_sedes in (boolean,) + tuple(UInt(bit_size) for bit_size in UINT_SIZES): + sedes = List(element_sedes) + lengths = ( + 0, + 1, + 256 // element_sedes.size - 1, + 256 // element_sedes.size, + ) + tuple( + random.randint(0, MAX_RANDOM_LIST_LENGTH) + for _ in range(NUM_RANDOM_LENGTHS) + ) + for length in lengths: + value = tuple(get_random_basic_value(element_sedes) for _ in range(length)) + yield render_test_case( + sedes=sedes, + valid=True, + value=value, + serial=ssz.encode(value, sedes), + tags=["composite", "list", "flat"], + ) + + +def generate_flat_homogenous_container_test_cases(): + for element_sedes in (boolean,) + tuple(UInt(bit_size) for bit_size in UINT_SIZES): + lengths = ( + 0, + 1, + ) + tuple( + random.randint(0, MAX_RANDOM_CONTAINER_LENGTH) + for _ in range(NUM_RANDOM_LENGTHS) + ) + for length in lengths: + field_names = tuple(f"field{field_index}" for field_index in range(length)) + sedes = Container(tuple( + (field_name, element_sedes) + for field_name in field_names + )) + value = { + field_name: get_random_basic_value(element_sedes) + for field_name in field_names + } + yield render_test_case( + sedes=sedes, + valid=True, + value=value, + serial=ssz.encode(value, sedes), + tags=["composite", "container", "flat", "homogenous"], + ) + + +def generate_flat_heterogenous_container_test_cases(): + lengths = tuple( + random.randint(0, MAX_RANDOM_CONTAINER_LENGTH) + for _ in range(NUM_RANDOM_LENGTHS) + ) + element_sedes_choices = ( + boolean, + ) + tuple( + UInt(bit_size) for bit_size in UINT_SIZES + ) + for length in lengths: + field_names = tuple(f"field{field_index}" for field_index in range(length)) + field_sedes = tuple(random.choice(element_sedes_choices) for _ in range(length)) + sedes = Container(tuple( + (field_name, field_sedes) + for field_name, field_sedes in zip(field_names, field_sedes) + )) + value = { + field_name: get_random_basic_value(sedes) + for field_name, sedes in zip(field_names, field_sedes) + } + + yield render_test_case( + sedes=sedes, + valid=True, + value=value, + serial=ssz.encode(value, sedes), + tags=["composite", "container", "flat", "heterogenous"], + ) + + +def generate_flat_vector_test_cases(): + for element_sedes in (boolean,) + tuple(UInt(bit_size) for bit_size in UINT_SIZES): + lengths = ( + 0, + 1, + ) + tuple( + random.randint(0, MAX_RANDOM_VECTOR_LENGTH) + for _ in range(NUM_RANDOM_LENGTHS) + ) + for length in lengths: + sedes = Vector(length, element_sedes) + value = tuple(get_random_basic_value(element_sedes) for _ in range(length)) + yield render_test_case( + sedes=sedes, + valid=True, + value=value, + serial=ssz.encode(value, sedes), + tags=["composite", "vector", "flat", "homogenous"], + ) + + +def generate_flat_list_test(): + return render_test( + title="Flat List", + summary="Tests for lists of basic types", + version="0.1", + test_cases=generate_flat_list_test_cases(), + ) + + +def generate_flat_container_test(): + return render_test( + title="Flat Container", + summary="Tests for containers consisting of only basic types", + version="0.1", + test_cases=chain( + generate_flat_homogenous_container_test_cases(), + generate_flat_heterogenous_container_test_cases(), + ) + ) + + +def generate_flat_vector_test(): + return render_test( + title="Flat Vector", + summary="Tests for vectors of basic types", + version="0.1", + test_cases=generate_flat_vector_test_cases(), + ) From 3fb2016e1c32a5a12f9467035f93dd98d6de0c76 Mon Sep 17 00:00:00 2001 From: Jannik Luhn Date: Thu, 28 Mar 2019 11:48:30 +0100 Subject: [PATCH 5/6] Add test generator for nested composite types --- ssz/nested_composite_types.py | 167 ++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 ssz/nested_composite_types.py diff --git a/ssz/nested_composite_types.py b/ssz/nested_composite_types.py new file mode 100644 index 0000000..084c413 --- /dev/null +++ b/ssz/nested_composite_types.py @@ -0,0 +1,167 @@ +import random +from functools import ( + partial, +) +from itertools import ( + product, +) + +import ssz +from ssz.sedes import ( + boolean, + Boolean, + Container, + List, + UInt, + Vector, +) + +from renderers import ( + render_test_case, + render_test, +) + + +UINT_SIZES = tuple(8 * 2**exponent for exponent in range(0, 6)) # 8, 16, ..., 256 +NUM_TEST_CASES_PER_TYPE = 20 +MAX_RANDOM_LIST_LENGTH = 16 +MAX_RANDOM_VECTOR_LENGTH = 16 +MAX_RANDOM_CONTAINER_LENGTH = 8 + +NUM_DEEP_NESTING_TEST_CASES = 20 +DEEP_NESTING_MAX_WIDTH = 4 +DEEP_NESTING_EXPECTED_BRANCH_LENGTH = 4 +# choose to go one layer deeper with this probability +DEEP_NESTING_NEXT_LAYER_PROB = 1 - 1 / DEEP_NESTING_EXPECTED_BRANCH_LENGTH + + +def get_random_vector_length(): + return random.randint(0, MAX_RANDOM_VECTOR_LENGTH) + + +def get_random_basic_sedes(): + return random.choice(( + boolean, + ) + tuple( + UInt(bit_size) + for bit_size in UINT_SIZES + )) + + +def get_random_value(sedes, max_list_length): + if isinstance(sedes, Boolean): + return random.choice((True, False)) + elif isinstance(sedes, UInt): + return random.randint(0, 8**sedes.size) + elif isinstance(sedes, Vector): + return tuple( + get_random_value(sedes.element_sedes, max_list_length) + for _ in range(sedes.number_of_elements) + ) + elif isinstance(sedes, List): + length = random.randint(0, max_list_length) + return tuple( + get_random_value(sedes.element_sedes, max_list_length) + for _ in range(length) + ) + elif isinstance(sedes, Container): + return { + field_name: get_random_value(field_sedes, max_list_length) + for field_name, field_sedes in sedes.fields + } + else: + raise ValueError(f"Cannot generate random value for sedes {sedes}") + + +def get_random_list_sedes(element_sedes_factory): + element_sedes = element_sedes_factory() + return List(element_sedes) + + +def get_random_vector_sedes(element_sedes_factory, max_length): + length = random.randint(0, max_length) + element_sedes = element_sedes_factory() + return Vector(length, element_sedes) + + +def get_random_container_sedes(element_sedes_factory, max_length): + length = random.randint(0, max_length) + fields = tuple( + (f"field{index}", element_sedes_factory()) + for index in range(length) + ) + return Container(fields) + + +def get_random_deep_nested_sedes(): + should_go_deeper = random.random() < DEEP_NESTING_NEXT_LAYER_PROB + + composite_sedes_factories = tuple(( + get_random_list_sedes, + partial(get_random_vector_sedes, max_length=DEEP_NESTING_MAX_WIDTH), + partial(get_random_container_sedes, max_length=DEEP_NESTING_MAX_WIDTH), + )) + + if not should_go_deeper: + return get_random_basic_sedes() + + else: + sedes_factory = random.choice(composite_sedes_factories) + return sedes_factory(get_random_deep_nested_sedes) + + +def generate_two_layer_composite_test_cases(): + composite_sedes_factories = tuple(( + get_random_list_sedes, + partial(get_random_vector_sedes, max_length=MAX_RANDOM_VECTOR_LENGTH), + partial(get_random_container_sedes, max_length=MAX_RANDOM_VECTOR_LENGTH), + )) + + inner_sedes_factories = tuple( + partial(sedes_factory, get_random_basic_sedes) + for sedes_factory in composite_sedes_factories + ) + inner_and_outer_sedes_factories = product(inner_sedes_factories, composite_sedes_factories) + for inner_sedes_factory, outer_sedes_factory in inner_and_outer_sedes_factories: + for _ in range(NUM_TEST_CASES_PER_TYPE): + outer_sedes = outer_sedes_factory(inner_sedes_factory) + + value = get_random_value(outer_sedes, max_list_length=MAX_RANDOM_LIST_LENGTH) + yield render_test_case( + sedes=outer_sedes, + valid=True, + value=value, + serial=ssz.encode(value, outer_sedes), + tags=["composite", "nested", "shallow"], + ) + + +def generate_deeply_nested_composite_test_cases(): + for _ in range(NUM_DEEP_NESTING_TEST_CASES): + sedes = get_random_deep_nested_sedes() + value = get_random_value(sedes, DEEP_NESTING_MAX_WIDTH) + yield render_test_case( + sedes=sedes, + valid=True, + value=value, + serial=ssz.encode(value, sedes), + tags=["composite", "nested", "deep"], + ) + + +def generate_two_layer_composite_test(): + return render_test( + title="Nested composite types", + summary="Tests for composite types of other composite types", + version="0.1", + test_cases=generate_two_layer_composite_test_cases(), + ) + + +def generate_deeply_nested_composite_test(): + return render_test( + title="Deeply nested composite types", + summary="Tests for nested composite types with a random number of layers", + version="0.1", + test_cases=generate_deeply_nested_composite_test_cases(), + ) From de8bd4807cccb93a15267fcf3c730b579a0c9ef4 Mon Sep 17 00:00:00 2001 From: Jannik Luhn Date: Thu, 28 Mar 2019 11:48:58 +0100 Subject: [PATCH 6/6] Enable new test generators --- ssz/test_generator.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/ssz/test_generator.py b/ssz/test_generator.py index d19ec12..a3af0ca 100644 --- a/ssz/test_generator.py +++ b/ssz/test_generator.py @@ -6,16 +6,30 @@ YAML, ) -from uint_test_generators import ( - generate_uint_bounds_test, - generate_uint_random_test, - generate_uint_wrong_length_test, +from basic_types import ( + generate_bool_test, + generate_uint_test, +) +from flat_composite_types import ( + generate_flat_list_test, + generate_flat_container_test, + generate_flat_vector_test, +) +from nested_composite_types import ( + generate_two_layer_composite_test, + generate_deeply_nested_composite_test, ) test_generators = [ - generate_uint_random_test, - generate_uint_wrong_length_test, - generate_uint_bounds_test, + generate_bool_test, + generate_uint_test, + + generate_flat_list_test, + generate_flat_container_test, + generate_flat_vector_test, + + generate_two_layer_composite_test, + generate_deeply_nested_composite_test, ]