2017-11-20 03:24:08 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2017 Joshua Bronson. All Rights Reserved.
|
|
|
|
#
|
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
2017-11-21 15:35:51 +00:00
|
|
|
"""Property-based tests using https://hypothesis.readthedocs.io."""
|
2015-03-22 18:21:15 +00:00
|
|
|
|
2016-06-28 04:05:22 +00:00
|
|
|
from collections import OrderedDict
|
2016-01-13 16:54:29 +00:00
|
|
|
from os import getenv
|
2017-11-16 20:44:51 +00:00
|
|
|
|
2016-06-28 04:05:22 +00:00
|
|
|
import pytest
|
2017-11-18 03:35:40 +00:00
|
|
|
from hypothesis import assume, given, settings
|
2017-11-16 20:44:51 +00:00
|
|
|
from hypothesis.strategies import integers, lists, tuples
|
|
|
|
from bidict import (
|
|
|
|
IGNORE, OVERWRITE, RAISE,
|
2017-11-20 03:24:08 +00:00
|
|
|
bidict, OrderedBidict,
|
|
|
|
frozenbidict, FrozenOrderedBidict)
|
2017-11-16 20:44:51 +00:00
|
|
|
from bidict.compat import iteritems
|
2016-01-13 16:54:29 +00:00
|
|
|
|
|
|
|
|
2017-11-20 03:24:08 +00:00
|
|
|
settings.register_profile('default', settings(max_examples=200, deadline=None))
|
2016-01-13 16:54:29 +00:00
|
|
|
settings.load_profile(getenv('HYPOTHESIS_PROFILE', 'default'))
|
2015-03-22 18:21:15 +00:00
|
|
|
|
|
|
|
|
2017-11-16 20:44:51 +00:00
|
|
|
# pylint: disable=C0111
|
2016-06-28 04:05:22 +00:00
|
|
|
def to_inv_odict(items):
|
2017-11-21 15:35:51 +00:00
|
|
|
"""Return an OrderedDict with the inverse of each item in ``items``."""
|
2016-06-28 04:05:22 +00:00
|
|
|
return OrderedDict((v, k) for (k, v) in items)
|
2015-04-29 18:29:33 +00:00
|
|
|
|
2015-12-21 03:05:20 +00:00
|
|
|
|
2017-05-10 18:35:23 +00:00
|
|
|
def dedup(items):
|
2017-11-21 15:35:51 +00:00
|
|
|
"""Given some generated items, prune any with duplicated keys or values."""
|
2016-07-03 15:34:32 +00:00
|
|
|
pruned = list(iteritems(to_inv_odict(iteritems(to_inv_odict(items)))))
|
|
|
|
assume(len(pruned) >= len(items) // 2)
|
|
|
|
return pruned
|
2015-12-21 03:05:20 +00:00
|
|
|
|
|
|
|
|
2017-11-16 20:44:51 +00:00
|
|
|
# pylint: disable=C0103
|
2016-06-28 04:05:22 +00:00
|
|
|
ondupbehaviors = (IGNORE, OVERWRITE, RAISE)
|
2017-11-20 03:24:08 +00:00
|
|
|
mutable_bidict_types = (bidict, OrderedBidict)
|
|
|
|
immutable_bidict_types = (frozenbidict, FrozenOrderedBidict)
|
|
|
|
bidict_types = mutable_bidict_types + immutable_bidict_types
|
2015-12-21 03:05:20 +00:00
|
|
|
mutating_methods_by_arity = {
|
2016-07-03 21:36:15 +00:00
|
|
|
0: ('clear', 'popitem'),
|
|
|
|
1: ('__delitem__', 'pop', 'setdefault', 'move_to_end'),
|
|
|
|
2: ('__setitem__', 'pop', 'put', 'forceput', 'setdefault'),
|
|
|
|
-1: ('update', 'forceupdate'),
|
2015-12-21 03:05:20 +00:00
|
|
|
}
|
2016-06-28 04:05:22 +00:00
|
|
|
immutable = integers()
|
|
|
|
itemlists = lists(tuples(immutable, immutable))
|
2017-05-10 18:35:23 +00:00
|
|
|
inititems = itemlists.map(dedup)
|
2016-06-28 04:05:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('B', bidict_types)
|
|
|
|
@given(init=inititems)
|
2017-11-16 20:44:51 +00:00
|
|
|
def test_equality(B, init): # noqa
|
2016-06-28 04:05:22 +00:00
|
|
|
b = B(init)
|
2016-06-30 16:48:48 +00:00
|
|
|
d = dict(init)
|
|
|
|
o = OrderedDict(init)
|
|
|
|
oi = to_inv_odict(iteritems(o))
|
|
|
|
di = OrderedDict(oi)
|
2015-04-29 18:29:33 +00:00
|
|
|
assert b == d
|
2016-06-30 16:48:48 +00:00
|
|
|
assert b == o
|
2015-04-29 18:29:33 +00:00
|
|
|
assert not b != d
|
2016-06-30 16:48:48 +00:00
|
|
|
assert not b != o
|
|
|
|
assert b.inv == oi
|
|
|
|
assert b.inv == di
|
|
|
|
assert not b.inv != oi
|
|
|
|
assert not b.inv != di
|
2015-05-26 05:37:42 +00:00
|
|
|
|
2015-12-21 03:05:20 +00:00
|
|
|
|
2016-06-28 04:05:22 +00:00
|
|
|
@pytest.mark.parametrize('B', bidict_types)
|
|
|
|
@given(init=inititems)
|
2017-11-16 20:44:51 +00:00
|
|
|
def test_bidirectional_mappings(B, init): # noqa
|
2017-11-18 03:35:40 +00:00
|
|
|
ordered = bool(getattr(B, '__reversed__', None))
|
2017-11-16 20:44:51 +00:00
|
|
|
C = list if ordered else sorted # noqa
|
2016-06-28 04:05:22 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2017-11-16 20:44:51 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'arity, methodname',
|
2016-06-28 04:05:22 +00:00
|
|
|
[(a, m) for (a, ms) in iteritems(mutating_methods_by_arity) for m in ms])
|
2017-11-16 20:44:51 +00:00
|
|
|
@pytest.mark.parametrize('B', mutable_bidict_types) # noqa
|
2016-06-28 04:05:22 +00:00
|
|
|
@given(init=inititems, arg1=immutable, arg2=immutable, items=itemlists)
|
|
|
|
def test_consistency_after_mutation(arity, methodname, B, init, arg1, arg2, items):
|
2017-11-21 15:35:51 +00:00
|
|
|
"""
|
|
|
|
Call every mutating method on every bidict type that supports it,
|
|
|
|
and ensure the bidict is left in a consistent state afterward.
|
|
|
|
"""
|
2016-06-28 04:05:22 +00:00
|
|
|
method = getattr(B, methodname, None)
|
|
|
|
if not method:
|
|
|
|
return
|
|
|
|
args = []
|
|
|
|
if arity == -1:
|
|
|
|
args.append(items)
|
|
|
|
else:
|
|
|
|
if arity > 0:
|
|
|
|
args.append(arg1)
|
|
|
|
if arity > 1:
|
|
|
|
args.append(arg2)
|
2016-07-03 21:36:15 +00:00
|
|
|
b0 = B(init)
|
|
|
|
b1 = b0.copy()
|
2016-06-28 04:05:22 +00:00
|
|
|
try:
|
2016-07-03 21:36:15 +00:00
|
|
|
method(b1, *args)
|
2017-11-16 20:44:51 +00:00
|
|
|
except Exception as exc: # pylint: disable=W0703
|
2017-11-18 03:35:40 +00:00
|
|
|
# method should fail clean, i.e. b1 should be in the same state it was before the call.
|
|
|
|
assert b1 == b0, '%r did not fail clean: %r' % (method, exc)
|
|
|
|
assert b1.inv == b0.inv, '%r did not fail clean: %r' % (method, exc)
|
|
|
|
# Whether method failed or succeeded, b1 should pass consistency checks.
|
|
|
|
assert len(b1) == sum(1 for _ in iteritems(b1))
|
|
|
|
assert len(b1) == sum(1 for _ in iteritems(b1.inv))
|
|
|
|
assert b1 == dict(iteritems(b1))
|
|
|
|
assert b1.inv == dict(iteritems(b1.inv))
|
2016-07-03 21:36:15 +00:00
|
|
|
assert b1 == to_inv_odict(iteritems(b1.inv))
|
|
|
|
assert b1.inv == to_inv_odict(iteritems(b1))
|
2016-06-28 04:05:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('on_dup_key', ondupbehaviors)
|
|
|
|
@pytest.mark.parametrize('on_dup_val', ondupbehaviors)
|
|
|
|
@pytest.mark.parametrize('on_dup_kv', ondupbehaviors)
|
2017-11-20 03:24:08 +00:00
|
|
|
@pytest.mark.parametrize('B', mutable_bidict_types)
|
2016-06-28 04:05:22 +00:00
|
|
|
@given(init=inititems, items=itemlists)
|
2017-11-20 03:24:08 +00:00
|
|
|
def test_putall(on_dup_key, on_dup_val, on_dup_kv, B, init, items): # noqa
|
2016-06-28 04:05:22 +00:00
|
|
|
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)
|
2017-11-16 20:44:51 +00:00
|
|
|
except Exception as exc: # pylint: disable=W0703
|
|
|
|
expectexc = exc
|
2016-06-28 04:05:22 +00:00
|
|
|
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)
|
2017-11-16 20:44:51 +00:00
|
|
|
except Exception as exc: # pylint: disable=W0703
|
|
|
|
checkexc = exc
|
|
|
|
assert type(checkexc) == type(expectexc) # pylint: disable=C0123
|
2016-06-28 04:05:22 +00:00
|
|
|
assert check == expect
|
|
|
|
assert check.inv == expect.inv
|