bidict/tests/test_hypothesis.py

168 lines
5.9 KiB
Python

"""
Property-based tests using https://warehouse.python.org/project/hypothesis/
"""
from bidict import (
OrderedBidirectionalMapping, IGNORE, OVERWRITE, RAISE,
bidict, loosebidict, looseorderedbidict, orderedbidict,
frozenbidict, frozenorderedbidict)
from bidict.compat import iteritems, viewitems
from collections import OrderedDict
from hypothesis import given, settings
from hypothesis.strategies import integers, lists, tuples
from os import getenv
import pytest
# https://groups.google.com/d/msg/hypothesis-users/8FVs--1yUl4/JEkJ02euEwAJ
settings.register_profile('default', settings(strict=True))
settings.load_profile(getenv('HYPOTHESIS_PROFILE', 'default'))
def to_inv_odict(items):
return OrderedDict((v, k) for (k, v) in items)
def prune_dup_vals(items):
return list(iteritems(to_inv_odict(iteritems(to_inv_odict(items)))))
ondupbehaviors = (IGNORE, OVERWRITE, RAISE)
mutable_bidict_types = (bidict, loosebidict, looseorderedbidict, orderedbidict)
bidict_types = mutable_bidict_types + (frozenbidict, frozenorderedbidict)
mutating_methods_by_arity = {
0: ('clear', 'popitem',),
1: ('__delitem__', 'pop', 'setdefault', 'move_to_end',),
2: ('__setitem__', 'pop', 'put', 'forceput', 'setdefault',),
-1: ('update', 'forceupdate',),
}
immutable = integers()
# To test with more immutable types, can use the following, but it slows down
# the tests without finding more bugs:
# sz = dict(average_size=2)
# immu_atom = none() | booleans() | integers() | floats(allow_nan=False) | text(**sz) | binary(**sz)
# immu_coll = lambda e: frozensets(e, **sz) | lists(e, **sz).map(tuple)
# immutable = recursive(immu_atom, immu_coll)
itemlists = lists(tuples(immutable, immutable))
inititems = itemlists.map(prune_dup_vals)
@pytest.mark.parametrize('B', bidict_types)
@given(init=inititems)
def test_equality(B, init):
b = B(init)
d = dict(init)
o = OrderedDict(init)
oi = to_inv_odict(iteritems(o))
di = OrderedDict(oi)
assert b == d
assert b == o
assert not b != d
assert not b != o
assert b.inv == oi
assert b.inv == di
assert not b.inv != oi
assert not b.inv != di
@pytest.mark.parametrize('B', bidict_types)
@given(init=inititems)
def test_bidirectional_mappings(B, init):
ordered = issubclass(B, OrderedBidirectionalMapping)
C = list if ordered else sorted
b = B(init)
keysf = C(k for (k, v) in iteritems(b))
keysi = C(b.inv[v] for (k, v) in iteritems(b))
assert keysf == keysi
valsf = C(b[k] for (v, k) in iteritems(b.inv))
valsi = C(v for (v, k) in iteritems(b.inv))
assert valsf == valsi
@pytest.mark.parametrize('arity,methodname',
[(a, m) for (a, ms) in iteritems(mutating_methods_by_arity) for m in ms])
@pytest.mark.parametrize('B', mutable_bidict_types)
@given(init=inititems, arg1=immutable, arg2=immutable, items=itemlists)
def test_consistency_after_mutation(arity, methodname, B, init, arg1, arg2, items):
method = getattr(B, methodname, None)
if not method:
return
b = B(init)
args = []
if arity == -1:
args.append(items)
else:
if arity > 0:
args.append(arg1)
if arity > 1:
args.append(arg2)
b0 = b.copy()
try:
method(b, *args)
except:
# All methods should fail clean, reverting any changes made before failure.
assert b == b0
assert b.inv == b0.inv
else:
assert b == to_inv_odict(iteritems(b.inv))
assert b.inv == to_inv_odict(iteritems(b))
# If b is an orderedbidict and the method is not expected to change the
# ordering, test that the relative ordering of any items that survived
# the mutation is preserved, i.e. if (k1, v1) came before (k2, v2)
# before the mutation, it still does after.
#
# In the case of forceupdate(), order is preserved as much as possible,
# but in some cases it is not preserved completely, e.g.::
#
# >>> o = orderedbidict([(0, 2), (2, 1)])
# >>> o.forceupdate([(1, 2), (0, 0), (0, 2)])
# >>> o
# orderedbidict([(2, 1), (0, 2)])
#
# So this test is skipped for forceupdate().
ordered = issubclass(B, OrderedBidirectionalMapping)
if ordered and methodname not in ('move_to_end', 'forceupdate'):
items0 = viewitems(b0)
items1 = viewitems(b)
common = items0 & items1
if common:
items0 = list(items0)
items1 = list(items1)
for i in common:
idx0 = items0.index(i)
idx1 = items1.index(i)
beforei0 = [j for j in items0[:idx0] if j in common]
beforei1 = [j for j in items1[:idx1] if j in common]
assert beforei0 == beforei1
afteri0 = [j for j in items0[idx0 + 1:] if j in common]
afteri1 = [j for j in items1[idx1 + 1:] if j in common]
assert afteri0 == afteri1
@pytest.mark.parametrize('B', mutable_bidict_types)
@pytest.mark.parametrize('on_dup_key', ondupbehaviors)
@pytest.mark.parametrize('on_dup_val', ondupbehaviors)
@pytest.mark.parametrize('on_dup_kv', ondupbehaviors)
@given(init=inititems, items=itemlists)
def test_putall(B, on_dup_key, on_dup_val, on_dup_kv, init, items):
b0 = B(init)
expect = b0.copy()
expectexc = None
for (k, v) in items:
try:
expect.put(k, v, on_dup_key=on_dup_key, on_dup_val=on_dup_val, on_dup_kv=on_dup_kv)
except Exception as e:
expectexc = e
expect = b0 # bulk updates fail clean
break
check = b0.copy()
checkexc = None
try:
check.putall(items, on_dup_key=on_dup_key, on_dup_val=on_dup_val, on_dup_kv=on_dup_kv)
except Exception as e:
checkexc = e
assert type(checkexc) == type(expectexc)
assert check == expect
assert check.inv == expect.inv