Refactor Hypothesis strategy for self-tests (#706)

* Ease Hypothesis shrinking

This is a tiny performance improvement for shrinking.

Unconditionally drawing and conditionally *using* these boolean flags minimises the impact of mutating other parts of an input on the structure here, which in turn means more attempts will be valid.

* Refactor Hypothesis strategy

Using the composite decorator allows for a much clearer imperative description of how the class is constructed.
This commit is contained in:
Zac Hatfield-Dodds 2020-10-21 16:04:12 +11:00 committed by GitHub
parent bc527b9f29
commit f6ffab7be0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 19 additions and 57 deletions

View File

@ -46,14 +46,8 @@ def maybe_underscore_prefix(source):
to_underscore = not to_underscore
def _create_hyp_class(attrs):
"""
A helper function for Hypothesis to generate attrs classes.
"""
return make_class("HypClass", dict(zip(gen_attr_names(), attrs)))
def _create_hyp_nested_strategy(simple_class_strategy):
@st.composite
def _create_hyp_nested_strategy(draw, simple_class_strategy):
"""
Create a recursive attrs class.
@ -62,47 +56,17 @@ def _create_hyp_nested_strategy(simple_class_strategy):
the simpler class, a list of simpler classes, a tuple of simpler classes,
an ordered dict or a dict mapping the string "cls" to a simpler class.
"""
# Use a tuple strategy to combine simple attributes and an attr class.
def just_class(tup):
combined_attrs = list(tup[0])
combined_attrs.append(attr.ib(default=attr.Factory(tup[1])))
return _create_hyp_class(combined_attrs)
def list_of_class(tup):
default = attr.Factory(lambda: [tup[1]()])
combined_attrs = list(tup[0])
combined_attrs.append(attr.ib(default=default))
return _create_hyp_class(combined_attrs)
def tuple_of_class(tup):
default = attr.Factory(lambda: (tup[1](),))
combined_attrs = list(tup[0])
combined_attrs.append(attr.ib(default=default))
return _create_hyp_class(combined_attrs)
def dict_of_class(tup):
default = attr.Factory(lambda: {"cls": tup[1]()})
combined_attrs = list(tup[0])
combined_attrs.append(attr.ib(default=default))
return _create_hyp_class(combined_attrs)
def ordereddict_of_class(tup):
default = attr.Factory(lambda: OrderedDict([("cls", tup[1]())]))
combined_attrs = list(tup[0])
combined_attrs.append(attr.ib(default=default))
return _create_hyp_class(combined_attrs)
# A strategy producing tuples of the form ([list of attributes], <given
# class strategy>).
attrs_and_classes = st.tuples(list_of_attrs, simple_class_strategy)
return st.one_of(
attrs_and_classes.map(just_class),
attrs_and_classes.map(list_of_class),
attrs_and_classes.map(tuple_of_class),
attrs_and_classes.map(dict_of_class),
attrs_and_classes.map(ordereddict_of_class),
)
cls = draw(simple_class_strategy)
factories = [
cls,
lambda: [cls()],
lambda: (cls(),),
lambda: {"cls": cls()},
lambda: OrderedDict([("cls", cls())]),
]
factory = draw(st.sampled_from(factories))
attrs = draw(list_of_attrs) + [attr.ib(default=attr.Factory(factory))]
return make_class("HypClass", dict(zip(gen_attr_names(), attrs)))
bare_attrs = st.builds(attr.ib, default=st.none())
@ -177,11 +141,9 @@ def simple_classes(
private, and if `private_attrs=False`, no attributes will be private.
"""
attrs = draw(list_of_attrs)
frozen_flag = draw(st.booleans()) if frozen is None else frozen
slots_flag = draw(st.booleans()) if slots is None else slots
weakref_slot_flag = (
draw(st.booleans()) if weakref_slot is None else weakref_slot
)
frozen_flag = draw(st.booleans())
slots_flag = draw(st.booleans())
weakref_flag = draw(st.booleans())
if private_attrs is None:
attr_names = maybe_underscore_prefix(gen_attr_names())
@ -202,9 +164,9 @@ def simple_classes(
return make_class(
"HypClass",
cls_dict,
slots=slots_flag,
frozen=frozen_flag,
weakref_slot=weakref_slot_flag,
slots=slots_flag if slots is None else slots,
frozen=frozen_flag if frozen is None else frozen,
weakref_slot=weakref_flag if weakref_slot is None else weakref_slot,
)