mirror of https://github.com/jab/bidict.git
Minor changes to hypothesis tests.
Use sampled_from(powerset(keys_cross_vals)) for items strategy (and so forth) to avoid hypothesis generating invalid examples.
This commit is contained in:
parent
b0a7e4706f
commit
0cb7d99aed
|
@ -3,6 +3,13 @@ name: tests
|
|||
|
||||
"on":
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
hypothesis_profile:
|
||||
type: choice
|
||||
description: (optional)
|
||||
options:
|
||||
- default
|
||||
- more-examples
|
||||
schedule:
|
||||
- cron: "15 16 * * *"
|
||||
push:
|
||||
|
@ -57,10 +64,10 @@ jobs:
|
|||
key: mypy
|
||||
- name: run mypy
|
||||
run: python -m mypy bidict tests
|
||||
- name: maybe set --hypothesis-profile=more-examples # See tests/conftest.py
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
- name: set hypothesis profile
|
||||
run: |
|
||||
echo PYTEST_ADDOPTS="${PYTEST_ADDOPTS} --hypothesis-profile=more-examples" >> "${GITHUB_ENV}"
|
||||
hypothesis_profile=${{ github.event.inputs.hypothesis_profile || (github.event_name == 'schedule' && 'more-examples' || 'default') }}
|
||||
echo PYTEST_ADDOPTS="${PYTEST_ADDOPTS} --hypothesis-profile=$hypothesis_profile" >> "${GITHUB_ENV}"
|
||||
- name: cache .hypothesis dir
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
|
||||
with:
|
||||
|
|
|
@ -10,6 +10,8 @@ import operator
|
|||
import typing as t
|
||||
from collections import UserDict
|
||||
from dataclasses import dataclass
|
||||
from itertools import chain
|
||||
from itertools import combinations
|
||||
from itertools import starmap
|
||||
|
||||
from bidict import DROP_NEW
|
||||
|
@ -92,6 +94,12 @@ def should_be_reversible(bi_t: BT[KT, VT]) -> bool:
|
|||
assert all(not should_be_reversible(bi_t) or issubclass(bi_t, t.Reversible) for bi_t in bidict_types)
|
||||
|
||||
|
||||
def powerset(iterable: t.Iterable[t.Any]) -> t.Iterable[tuple[t.Any, ...]]:
|
||||
if not isinstance(iterable, (tuple, list)):
|
||||
iterable = tuple(iterable)
|
||||
return chain.from_iterable(combinations(iterable, r) for r in range(len(iterable) + 1))
|
||||
|
||||
|
||||
SET_OPS: t.Any = (
|
||||
operator.le,
|
||||
operator.lt,
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
from hypothesis import settings
|
||||
|
||||
|
||||
settings.register_profile('more-examples', deadline=None, max_examples=999, stateful_step_count=200)
|
||||
settings.register_profile('more-examples', deadline=None, max_examples=200, stateful_step_count=100)
|
||||
|
|
|
@ -38,6 +38,7 @@ from bidict_test_fixtures import UserOrderedBi
|
|||
from bidict_test_fixtures import bidict_types
|
||||
from bidict_test_fixtures import dedup
|
||||
from bidict_test_fixtures import mutable_bidict_types
|
||||
from bidict_test_fixtures import powerset
|
||||
from bidict_test_fixtures import should_be_reversible
|
||||
from bidict_test_fixtures import update_arg_types
|
||||
from bidict_test_fixtures import zip_equal
|
||||
|
@ -50,13 +51,8 @@ from hypothesis.stateful import invariant
|
|||
from hypothesis.stateful import precondition
|
||||
from hypothesis.stateful import rule
|
||||
from hypothesis.strategies import booleans
|
||||
from hypothesis.strategies import dictionaries
|
||||
from hypothesis.strategies import frozensets
|
||||
from hypothesis.strategies import integers
|
||||
from hypothesis.strategies import lists
|
||||
from hypothesis.strategies import randoms
|
||||
from hypothesis.strategies import sampled_from
|
||||
from hypothesis.strategies import tuples
|
||||
from typing_extensions import assert_type
|
||||
|
||||
from bidict import BidirectionalMapping
|
||||
|
@ -73,20 +69,18 @@ from bidict import inverted
|
|||
from bidict._typing import MapOrItems
|
||||
|
||||
|
||||
MAX_SIZE = 5 # Used for init_items and updates. 5 is enough to cover all possible duplication scenarios.
|
||||
Items = t.Sequence[t.Tuple[int, int]]
|
||||
Items121 = t.Dict[t.Any, t.Any]
|
||||
|
||||
|
||||
keys = integers(min_value=1, max_value=10)
|
||||
vals = integers(min_value=-10, max_value=-1) # faster than keys.map(operator.neg)
|
||||
InitItems = t.Dict[t.Any, t.Any]
|
||||
init_items = dictionaries(vals, keys, max_size=MAX_SIZE).map(lambda d: {v: k for (k, v) in d.items()}) # no dup vals
|
||||
ks = tuple(range(1, 5))
|
||||
vs = tuple(range(-1, -5, -1))
|
||||
keys = sampled_from(ks)
|
||||
vals = sampled_from(vs)
|
||||
items = sampled_from(list(powerset(product(ks, vs))))
|
||||
items121 = items.map(dedup)
|
||||
bidict_t = sampled_from(bidict_types)
|
||||
mut_bidict_t = sampled_from(mutable_bidict_types)
|
||||
items = tuples(keys, vals)
|
||||
ItemLists = t.List[t.Tuple[int, int]]
|
||||
itemlists = lists(items, max_size=MAX_SIZE) # "lists" to allow testing updates with dup k and/or v
|
||||
updates_t = sampled_from(update_arg_types)
|
||||
itemsets = frozensets(items, max_size=MAX_SIZE)
|
||||
on_dups = tuple(starmap(OnDup, product(OnDupAction, repeat=2)))
|
||||
on_dup = sampled_from(on_dups)
|
||||
|
||||
|
@ -95,10 +89,10 @@ class BidictStateMachine(RuleBasedStateMachine):
|
|||
bi: MutableBidict[int, int]
|
||||
oracle: Oracle[int, int]
|
||||
|
||||
@initialize(mut_bidict_t=mut_bidict_t, init_items=init_items)
|
||||
def init(self, mut_bidict_t: type[MutableBidict[int, int]], init_items: InitItems) -> None:
|
||||
self.bi = mut_bidict_t(init_items)
|
||||
self.oracle = Oracle(init_items, ordered=self.is_ordered())
|
||||
@initialize(mut_bidict_t=mut_bidict_t, items121=items121)
|
||||
def init(self, mut_bidict_t: type[MutableBidict[int, int]], items121: Items121) -> None:
|
||||
self.bi = mut_bidict_t(items121)
|
||||
self.oracle = Oracle(items121, ordered=self.is_ordered())
|
||||
|
||||
def is_ordered(self) -> bool:
|
||||
return isinstance(self.bi, OrderedBidict)
|
||||
|
@ -111,7 +105,7 @@ class BidictStateMachine(RuleBasedStateMachine):
|
|||
viewnames = sampled_from(('keys', 'values', 'items'))
|
||||
|
||||
# Would make this an invariant rather than a rule, but it slows down the tests too much.
|
||||
@rule(rand=randoms(), viewname=viewnames, set_op=sampled_from(SET_OPS), other_set=itemsets)
|
||||
@rule(rand=randoms(), viewname=viewnames, set_op=sampled_from(SET_OPS), other_set=items.map(frozenset))
|
||||
def assert_views_match_oracle(self, rand: Random, viewname: str, set_op: t.Any, other_set: t.Any) -> None:
|
||||
check = getattr(self.bi, viewname)()
|
||||
expect = getattr(self.oracle.data, viewname)() if viewname != 'values' else self.oracle.data_inv.keys()
|
||||
|
@ -177,7 +171,7 @@ class BidictStateMachine(RuleBasedStateMachine):
|
|||
partial(self.oracle.put, key, val, on_dup),
|
||||
)
|
||||
|
||||
@rule(updates=itemlists, updates_t=updates_t, on_dup=on_dup)
|
||||
@rule(updates=items, updates_t=updates_t, on_dup=on_dup)
|
||||
def putall(self, updates: MapOrItems[int, int], updates_t: t.Any, on_dup: OnDup) -> None:
|
||||
# Don't let the updates_t(updates) calls below raise a DuplicationError.
|
||||
if isinstance(updates_t, type) and issubclass(updates_t, BidirectionalMapping):
|
||||
|
@ -188,14 +182,14 @@ class BidictStateMachine(RuleBasedStateMachine):
|
|||
partial(self.oracle.putall, updates_t(updates), on_dup),
|
||||
)
|
||||
|
||||
@rule(other=init_items)
|
||||
@rule(other=items121)
|
||||
def __ior__(self, other: t.Mapping[KT, VT]) -> None:
|
||||
assert_calls_match(
|
||||
partial(self.bi.__ior__, other),
|
||||
partial(self.oracle.__ior__, other),
|
||||
)
|
||||
|
||||
@rule(other=init_items)
|
||||
@rule(other=items121)
|
||||
def __or__(self, other: t.Mapping[KT, VT]) -> None:
|
||||
assert_calls_match(
|
||||
partial(self.bi.__or__, other),
|
||||
|
@ -204,7 +198,7 @@ class BidictStateMachine(RuleBasedStateMachine):
|
|||
|
||||
# https://bidict.rtfd.io/basic-usage.html#order-matters
|
||||
@precondition(lambda self: zip_equal(self.bi, self.oracle.data))
|
||||
@rule(other=init_items)
|
||||
@rule(other=items121)
|
||||
def __ror__(self, other: t.Mapping[KT, VT]) -> None:
|
||||
assert_calls_match(
|
||||
partial(self.bi.__ror__, other),
|
||||
|
@ -341,38 +335,38 @@ def test_eq_and_or_with_non_mapping(bi_t: BT[KT, VT], non_mapping: t.Any) -> Non
|
|||
non_mapping | bi
|
||||
|
||||
|
||||
@given(init_items=init_items, bidict_t=bidict_t, rand=randoms())
|
||||
def test_ne_ordsens_to_equal_map_with_diff_order(init_items: InitItems, bidict_t: BT[KT, VT], rand: Random) -> None:
|
||||
bi = bidict_t(init_items)
|
||||
items_shuf = list(init_items.items())
|
||||
@given(items121=items121.filter(lambda x: len(x) > 2), bidict_t=bidict_t, rand=randoms())
|
||||
def test_equals_order_sensitive(items121: Items121, bidict_t: BT[KT, VT], rand: Random) -> None:
|
||||
bi = bidict_t(items121)
|
||||
items_shuf = list(items121.items())
|
||||
rand.shuffle(items_shuf)
|
||||
assume(not zip_equal(items_shuf, init_items.items()))
|
||||
assume(not zip_equal(items_shuf, items121.items()))
|
||||
map_shuf = dict(items_shuf)
|
||||
assert bi == map_shuf
|
||||
assert not bi.equals_order_sensitive(map_shuf)
|
||||
|
||||
|
||||
@given(items=itemlists, bidict_t=bidict_t)
|
||||
def test_inverted(items: ItemLists, bidict_t: BT[int, int]) -> None:
|
||||
check_list = list(inverted(inverted(items)))
|
||||
expect_list = items
|
||||
assert check_list == expect_list
|
||||
@given(items=items, bidict_t=bidict_t)
|
||||
def test_inverted(items: Items, bidict_t: BT[int, int]) -> None:
|
||||
check = tuple(inverted(inverted(items)))
|
||||
expect = items
|
||||
assert check == expect
|
||||
items_nodup = dedup(items)
|
||||
check_bi = bidict_t(inverted(bidict_t(items_nodup)))
|
||||
expect_bi = bidict_t({v: k for (k, v) in items_nodup.items()})
|
||||
assert_bidicts_equal(check_bi, expect_bi)
|
||||
|
||||
|
||||
@given(init_items=init_items)
|
||||
def test_frozenbidicts_hashable(init_items: InitItems) -> None:
|
||||
@given(items121=items121)
|
||||
def test_frozenbidicts_hashable(items121: Items121) -> None:
|
||||
"""Frozen bidicts can be hashed (and therefore inserted into sets and mappings)."""
|
||||
bi = frozenbidict(init_items)
|
||||
bi = frozenbidict(items121)
|
||||
h1 = hash(bi)
|
||||
h2 = hash(bi)
|
||||
assert h1 == h2
|
||||
assert {bi}
|
||||
assert {bi: bi}
|
||||
bi2 = frozenbidict(init_items)
|
||||
bi2 = frozenbidict(items121)
|
||||
assert bi2 == bi
|
||||
assert hash(bi2) == h1
|
||||
|
||||
|
@ -381,14 +375,13 @@ def test_frozenbidicts_hashable(init_items: InitItems) -> None:
|
|||
# (Hypothesis doesn't always generate examples that cover all the branches otherwise.)
|
||||
@pytest.mark.parametrize(('bi_t', 'on_dup'), product(mutable_bidict_types, on_dups))
|
||||
def test_putall_matches_bulk_put(bi_t: type[MutableBidict[int, int]], on_dup: OnDup) -> None:
|
||||
init_items = {0: 0, 1: 1}
|
||||
bi = bi_t(init_items)
|
||||
bi = bi_t({0: 0, 1: 1})
|
||||
for k1, v1, k2, v2 in product(range(4), repeat=4):
|
||||
for b in bi, bi.inv:
|
||||
assert_putall_matches_bulk_put(b, [(k1, v1), (k2, v2)], on_dup)
|
||||
|
||||
|
||||
def assert_putall_matches_bulk_put(bi: MutableBidict[int, int], new_items: ItemLists, on_dup: OnDup) -> None:
|
||||
def assert_putall_matches_bulk_put(bi: MutableBidict[int, int], new_items: Items, on_dup: OnDup) -> None:
|
||||
tmp = bi.copy()
|
||||
checkexc = None
|
||||
expectexc = None
|
||||
|
@ -485,8 +478,8 @@ def test_bidicts_freed_on_zero_refcount(bidict_t: BT[KT, VT]) -> None:
|
|||
|
||||
|
||||
@skip_if_pypy
|
||||
@given(init_items=init_items)
|
||||
def test_orderedbidict_nodes_freed_on_zero_refcount(init_items: InitItems) -> None:
|
||||
@given(items121=items121)
|
||||
def test_orderedbidict_nodes_freed_on_zero_refcount(items121: Items121) -> None:
|
||||
"""On CPython, the moment you have no more references to an ordered bidict,
|
||||
the refcount of each of its internal nodes drops to 0
|
||||
(i.e. the linked list of nodes does not create a reference cycle),
|
||||
|
@ -494,7 +487,7 @@ def test_orderedbidict_nodes_freed_on_zero_refcount(init_items: InitItems) -> No
|
|||
"""
|
||||
gc.disable()
|
||||
try:
|
||||
ob = OrderedBidict(init_items)
|
||||
ob = OrderedBidict(items121)
|
||||
nodes = weakref.WeakSet(ob._sntl.iternodes())
|
||||
assert len(nodes) == len(ob)
|
||||
del ob
|
||||
|
@ -503,10 +496,10 @@ def test_orderedbidict_nodes_freed_on_zero_refcount(init_items: InitItems) -> No
|
|||
gc.enable()
|
||||
|
||||
|
||||
@given(init_items=init_items)
|
||||
def test_orderedbidict_nodes_consistent(init_items: InitItems) -> None:
|
||||
@given(items121=items121)
|
||||
def test_orderedbidict_nodes_consistent(items121: Items121) -> None:
|
||||
"""The nodes in an ordered bidict's backing linked list should be the same as those in its backing mapping."""
|
||||
ob = OrderedBidict(init_items)
|
||||
ob = OrderedBidict(items121)
|
||||
mapnodes = set(ob._node_by_korv.inverse)
|
||||
linkedlistnodes = set(ob._sntl.iternodes())
|
||||
assert mapnodes == linkedlistnodes
|
||||
|
|
Loading…
Reference in New Issue