"""
Property-based tests using https://warehouse.python.org/project/hypothesis/
"""

from functools import reduce
from math import isnan
from operator import or_

from hypothesis import assume, given, strategy
from hypothesis.specifiers import dictionary

from bidict import bidict

def safe_isnan(x):
    return isnan(x) if isinstance(x, float) else False

# work around https://bitbucket.org/pypy/pypy/issue/1974
nan = float('nan')
WORKAROUND_NAN_BUG = (nan, nan) != (nan, nan)
if WORKAROUND_NAN_BUG:
    from warnings import warn
    warn('working around buggy python implementation: '
         '(nan, nan) should equal (nan, nan)')

immutable_types_ = (None, bool, int, float, str, bytes, tuple(), frozenset())
immutable_types = reduce(or_, map(strategy, immutable_types_))

@given(dictionary(immutable_types, immutable_types))
def test_basic_properties_bidict(fwd):
    # ensure all values in fwd are unique
    assume(len(fwd.values()) == len(set(fwd.values())))

    inv = {v: k for (k, v) in fwd.items()}
    b = bidict(fwd)

    # len
    assert len(fwd) == len(inv) == len(b)

    # getitem
    for k, v in b.items():
        k_ = b[:v] if v is not None else b.inv[v]
        v_ = b[k]
        assert k == k_ if not safe_isnan(k) else safe_isnan(k_)
        assert v == v_ if not safe_isnan(v) else safe_isnan(v_)

    # .inv, is, ==, !=, and ~ operators. == and != are polymorphic.
    assert b is b.inv.inv is ~~b
    assert b.inv is b.inv.inv.inv is ~b
    assert b == fwd or WORKAROUND_NAN_BUG
    assert ~b == inv or WORKAROUND_NAN_BUG
    assert not b != fwd or WORKAROUND_NAN_BUG
    assert not ~b != inv or WORKAROUND_NAN_BUG

"""
Property-based tests using https://warehouse.python.org/project/hypothesis/
"""

import pytest

from functools import reduce
from math import isnan
from operator import or_

from hypothesis import assume, given, strategy
from hypothesis.specifiers import dictionary

from bidict import bidict


def inv(d):
    return {v: k for (k, v) in d.items()}

def prune_dup_vals(d):
    pruned = inv(inv(d))
    assume(len(pruned) >= 0.5 * len(d))
    return pruned

immutable_types_ = (None, bool, int, float, str, bytes, tuple(), frozenset())
immutable_types = reduce(or_, map(strategy, immutable_types_))
d = strategy(dictionary(immutable_types, immutable_types)).map(prune_dup_vals)

@given(d)
def test_len(d):
    assert len(d) == len(inv(d)) == len(bidict(d))

def isnan_(x):
    return isnan(x) if isinstance(x, float) else False

def both_nan(a, b):
    return isnan_(a) and isnan_(b)

@given(d)
def test_bidirectional_mappings(d):
    b = bidict(d)
    for k, v in b.items():
        v_ = b[k]
        k_ = b.inv[v]
        assert k == k_ or both_nan(k, k_)
        assert v == v_ or both_nan(v, v_)

@given(d)
def test_getitem_with_slice(d):
    b = bidict(d)
    for k, v in b.items():
        # https://bidict.readthedocs.org/en/latest/caveats.html#none-breaks-the-slice-syntax
        if v is not None:
            k_ = b[:v]
            assert k == k_ or both_nan(k, k_)
        if k is not None:
            v_ = b.inv[:k]
            assert v == v_ or both_nan(v, v_)

@given(d)
def test_inv_identity(d):
    b = bidict(d)
    assert b is b.inv.inv is ~~b
    assert b.inv is b.inv.inv.inv is ~b

# work around https://bitbucket.org/pypy/pypy/issue/1974
nan = float('nan')
WORKAROUND_NAN_BUG = (nan, nan) != (nan, nan)

@pytest.mark.xfail(WORKAROUND_NAN_BUG, reason='python with nan bug detected')
@given(d)
def test_equality(d):
    i = inv(d)
    b = bidict(d)
    assert b == d
    assert ~b == i
    assert not b != d
    assert not ~b != i

split hypothesis test into smaller individual tests