Skip to content

Commit 4a322b4

Browse files
authored
Merge pull request #4641 from Liam-DeVoe/test-updates
Various test updates and fixes
2 parents 178e31e + b1bc5c2 commit 4a322b4

9 files changed

Lines changed: 91 additions & 44 deletions

File tree

build.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ fi
3131

3232
TOOL_REQUIREMENTS="$ROOT/requirements/tools.txt"
3333

34-
TOOL_HASH=$("$PYTHON" "$SCRIPTS/tool-hash.py" < "$TOOL_REQUIREMENTS")
34+
# append PYTHON_VERSION to bust caches when we upgrade versions
35+
TOOL_HASH=$( (cat "$TOOL_REQUIREMENTS" && echo "$PYTHON_VERSION") | "$PYTHON" "$SCRIPTS/tool-hash.py")
3536

3637
TOOL_VIRTUALENV="$VIRTUALENVS/build-$TOOL_HASH"
3738
TOOL_PYTHON="$TOOL_VIRTUALENV/bin/python"

hypothesis-python/docs/changelog.rst

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ The type annotations for |st.register_type_strategy| now indicate that it accept
579579
6.138.1 - 2025-08-15
580580
--------------------
581581

582-
Internal refactoring and cleanup. As a result, ``hypothesis[black]`` now requires ``black>=20.8b0`` instead of the previous ``black>=19.10b0``.
582+
Internal refactoring and cleanup. As a result, ``hypothesis[cli]`` and ``hypothesis[ghostwriter]`` now require ``black>=20.8b0`` instead of the previous ``black>=19.10b0``.
583583

584584
.. _v6.138.0:
585585

@@ -2427,7 +2427,7 @@ This patch makes progress towards adding type hints to our internal conjecture e
24272427

24282428
This release allows using :obj:`~python:typing.Annotated`
24292429
and :obj:`!ReadOnly` types
2430-
for :class:`~python:typing.TypedDict` value types
2430+
for :obj:`~python:typing.TypedDict` value types
24312431
with :func:`~hypothesis.strategies.from_type`.
24322432

24332433
.. _v6.108.10:
@@ -5075,7 +5075,7 @@ scans.
50755075
-------------------
50765076

50775077
This patch by Cheuk Ting Ho adds support for :pep:`655` ``Required`` and ``NotRequired`` as attributes of
5078-
:class:`~python:typing.TypedDict` in :func:`~hypothesis.strategies.from_type` (:issue:`3339`).
5078+
:obj:`~python:typing.TypedDict` in :func:`~hypothesis.strategies.from_type` (:issue:`3339`).
50795079

50805080
.. _v6.46.5:
50815081

@@ -5248,7 +5248,7 @@ already been decorated with :func:`@given() <hypothesis.given>`. Previously,
52485248
6.42.3 - 2022-04-10
52495249
-------------------
52505250

5251-
This patch fixes :func:`~hypothesis.strategies.from_type` on a :class:`~python:typing.TypedDict`
5251+
This patch fixes :func:`~hypothesis.strategies.from_type` on a :obj:`~python:typing.TypedDict`
52525252
with complex annotations, defined in a file using ``from __future__ import annotations``.
52535253
Thanks to Katelyn Gigante for identifying and fixing this bug!
52545254

@@ -6760,7 +6760,7 @@ creating import cycles. There is no user-visible change.
67606760
------------------
67616761

67626762
This patch enables :func:`~hypothesis.strategies.register_type_strategy` for subclasses of
6763-
:class:`python:typing.TypedDict`. Previously, :func:`~hypothesis.strategies.from_type`
6763+
:obj:`python:typing.TypedDict`. Previously, :func:`~hypothesis.strategies.from_type`
67646764
would ignore the registered strategy (:issue:`2872`).
67656765

67666766
Thanks to Ilya Lebedev for identifying and fixing this bug!
@@ -8074,7 +8074,7 @@ deprecated - you can explicitly pass ``min_magnitude=0``, or omit the argument e
80748074
-------------------
80758075

80768076
This patch fixes an internal error in :func:`~hypothesis.strategies.from_type`
8077-
for :class:`python:typing.NamedTuple` in Python 3.9. Thanks to Michel Salim
8077+
for :obj:`python:typing.NamedTuple` in Python 3.9. Thanks to Michel Salim
80788078
for reporting and fixing :issue:`2427`!
80798079

80808080
.. _v5.13.0:
@@ -12603,7 +12603,7 @@ not matter.
1260312603
-------------------
1260412604

1260512605
This patch fixes inference in the :func:`~hypothesis.strategies.builds`
12606-
strategy with subtypes of :class:`python:typing.NamedTuple`, where the
12606+
strategy with subtypes of :obj:`python:typing.NamedTuple`, where the
1260712607
``__init__`` method is not useful for introspection. We now use the
1260812608
field types instead - thanks to James Uther for identifying this bug.
1260912609

hypothesis-python/tests/cover/test_lookup.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,18 @@ def test_hashable_type_unhashable_value():
10021002
)
10031003

10041004

1005+
def test_unhashable_type():
1006+
class UnhashableMeta(type):
1007+
__hash__ = None
1008+
1009+
class UnhashableType(metaclass=UnhashableMeta):
1010+
pass
1011+
1012+
assert_simple_property(
1013+
st.from_type(UnhashableType), lambda x: isinstance(x, UnhashableType)
1014+
)
1015+
1016+
10051017
class _EmptyClass:
10061018
def __init__(self, value=-1) -> None:
10071019
pass

hypothesis-python/tests/cover/test_random_module.py

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,22 @@ def test_cannot_register_non_Random():
7575

7676

7777
@skipif_threading
78-
@pytest.mark.filterwarnings(
79-
"ignore:It looks like `register_random` was passed an object that could be garbage collected"
80-
)
81-
@pytest.mark.xfail(
82-
sys.version_info[:2] == (3, 14),
83-
reason="TODO_314: is this intentional semantics of the new gc?",
84-
)
8578
def test_registering_a_Random_is_idempotent():
8679
gc_collect()
8780
n_registered = len(entropy.RANDOMS_TO_MANAGE)
88-
r = random.Random()
81+
# on 3.14+, python introduced the LOAD_FAST_BORROW opcode, which does
82+
# not increment the refcount. Passing a bare r to register_random here on 3.14+
83+
# would use LOAD_FAST_BORROW and entropy.py would see a non-increasing refcount
84+
# and hard-error. On 3.13 and earlier, this is a warning instead.
85+
#
86+
# For compatibility with both versions, this test forces a refcount increment
87+
# with a redundant container.
88+
container = [random.Random()]
89+
r = container[0]
8990
register_random(r)
9091
register_random(r)
9192
assert len(entropy.RANDOMS_TO_MANAGE) == n_registered + 1
93+
del container
9294
del r
9395
gc_collect()
9496
assert len(entropy.RANDOMS_TO_MANAGE) == n_registered
@@ -174,13 +176,6 @@ def test_find_does_not_pollute_state():
174176
assert state_a2 != state_b2
175177

176178

177-
@pytest.mark.filterwarnings(
178-
"ignore:It looks like `register_random` was passed an object that could be garbage collected"
179-
)
180-
@pytest.mark.skipif(
181-
sys.version_info[:2] == (3, 14),
182-
reason="TODO_314: is this intentional semantics of the new gc?",
183-
)
184179
@skipif_threading # we assume we're the only writer to entropy.RANDOMS_TO_MANAGE
185180
def test_evil_prng_registration_nonsense():
186181
# my guess is that other tests may register randoms that are then marked for
@@ -195,7 +190,10 @@ def test_evil_prng_registration_nonsense():
195190
pass
196191

197192
n_registered = len(entropy.RANDOMS_TO_MANAGE)
198-
r1, r2, r3 = random.Random(1), random.Random(2), random.Random(3)
193+
# put inside a list to increment ref count and avoid our warning/error about no
194+
# referrers
195+
c1, c2, c3 = [random.Random(1)], [random.Random(2)], [random.Random(3)]
196+
r1, r2, r3 = c1[0], c2[0], c3[0]
199197
s2 = r2.getstate()
200198

201199
# We're going to be totally evil here: register two randoms, then
@@ -208,6 +206,7 @@ def test_evil_prng_registration_nonsense():
208206

209207
with deterministic_PRNG():
210208
del r1
209+
del c1
211210
gc_collect()
212211
assert k not in entropy.RANDOMS_TO_MANAGE, "r1 has been garbage-collected"
213212
assert len(entropy.RANDOMS_TO_MANAGE) == n_registered + 1
@@ -230,6 +229,7 @@ def test_passing_unreferenced_instance_raises():
230229
register_random(random.Random(0))
231230

232231

232+
@xfail_if_gil_disabled
233233
@pytest.mark.skipif(
234234
PYPY, reason="We can't guard against bad no-reference patterns in pypy."
235235
)
@@ -240,44 +240,54 @@ def f():
240240
with pytest.raises(ReferenceError):
241241
f()
242242

243+
# we have two error paths for register_random: one which warns and one which
244+
# errors. We use an alias to bump the refcount while not adding a gc referrer,
245+
# which covers the warning path.
246+
def f():
247+
r = random.Random(0)
248+
_r2 = r
249+
register_random(r)
250+
251+
with pytest.warns(
252+
HypothesisWarning,
253+
match="It looks like `register_random` was passed an object that could be"
254+
" garbage collected",
255+
):
256+
f()
257+
243258

244259
@xfail_if_gil_disabled
245260
@pytest.mark.skipif(
246261
PYPY, reason="We can't guard against bad no-reference patterns in pypy."
247262
)
248263
@pytest.mark.skipif(
249-
sys.version_info[:2] == (3, 14),
250-
reason="TODO_314: is this intentional semantics of the new gc?",
264+
sys.version_info[:2] < (3, 14),
265+
reason="warns instead of raises on 3.13 or earlier due to gc changes",
251266
)
252-
def test_passing_referenced_instance_within_function_scope_warns():
267+
def test_passing_referenced_instance_within_function_scope_raises():
253268
def f():
254269
r = random.Random(0)
255270
register_random(r)
256271

257-
with pytest.warns(
258-
HypothesisWarning,
259-
match="It looks like `register_random` was passed an object that could be"
260-
" garbage collected",
272+
with pytest.raises(
273+
ReferenceError,
274+
match=r"`register_random` was passed .* which will be garbage collected",
261275
):
262276
f()
263277

264278

265-
@pytest.mark.filterwarnings(
266-
"ignore:It looks like `register_random` was passed an object that could be garbage collected"
267-
)
268279
@pytest.mark.skipif(
269280
PYPY, reason="We can't guard against bad no-reference patterns in pypy."
270281
)
271-
@pytest.mark.skipif(
272-
sys.version_info[:2] == (3, 14),
273-
reason="TODO_314: is this intentional semantics of the new gc?",
274-
)
275282
@skipif_threading # we assume we're the only writer to entropy.RANDOMS_TO_MANAGE
276283
def test_register_random_within_nested_function_scope():
277284
n_registered = len(entropy.RANDOMS_TO_MANAGE)
278285

279286
def f():
280-
r = random.Random()
287+
# put inside a list to increment ref count and avoid our warning/error about no
288+
# referrers
289+
container = [random.Random()]
290+
r = container[0]
281291
register_random(r)
282292
assert len(entropy.RANDOMS_TO_MANAGE) == n_registered + 1
283293

hypothesis-python/tests/cover/test_randoms.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ def test_handles_singleton_regions_of_triangular_correctly(rnd):
236236
assert rnd.triangular(0.0, -0.0) == 0.0
237237

238238

239+
@given(st.randoms(use_true_random=False))
240+
def test_triangular_with_mode(rnd):
241+
x = rnd.triangular(0.0, 1.0, mode=0.5)
242+
assert 0.0 <= x <= 1.0
243+
244+
239245
@pytest.mark.parametrize("use_true_random", [False, True])
240246
def test_outputs_random_calls(use_true_random):
241247
@given(st.randoms(use_true_random=use_true_random, note_method_calls=True))

hypothesis-python/tests/redis/test_redis_exampledatabase.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ def test_redis_move_into_key_with_value():
164164
db.move(b"a", b"b", b"x")
165165

166166

167+
def test_redis_move_to_same_key():
168+
# explicit covering test for:
169+
# * moving a value where src == dest
170+
redis = FakeRedis()
171+
db = RedisExampleDatabase(redis)
172+
db.move(b"a", b"a", b"x")
173+
assert list(db.fetch(b"a")) == [b"x"]
174+
175+
167176
def test_redis_equality():
168177
redis = FakeRedis()
169178
assert RedisExampleDatabase(redis) == RedisExampleDatabase(redis)

hypothesis-python/tests/test_annotated_types.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from hypothesis.strategies._internal.strategies import FilteredStrategy
2121
from hypothesis.strategies._internal.types import _get_constraints
2222

23-
from tests.common.debug import check_can_generate_examples
23+
from tests.common.debug import assert_simple_property, check_can_generate_examples
2424

2525
try:
2626
import annotated_types as at
@@ -118,6 +118,11 @@ def test_collection_size_from_slice(data):
118118
assert 1 <= len(value) <= 10
119119

120120

121+
def test_unhashable_annotated_metadata():
122+
t = Annotated[int, {"key": "value"}]
123+
assert_simple_property(st.from_type(t), lambda x: isinstance(x, int))
124+
125+
121126
class GroupedStuff:
122127
__is_annotated_types_grouped_metadata__ = True
123128

tooling/src/hypothesistooling/installers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ def ensure_stack():
5656
if os.path.exists(STACK):
5757
return
5858
subprocess.check_call("mkdir -p ~/.local/bin", shell=True)
59+
# if you're on macos, this will error with "--wildcards is not supported"
60+
# or similar. You should put shellcheck on your PATH with your package
61+
# manager of choice; eg `brew install shellcheck`.
5962
subprocess.check_call(
6063
"curl -L https://www.stackage.org/stack/linux-x86_64 "
6164
"| tar xz --wildcards --strip-components=1 -C $HOME"

whole_repo_tests/types/test_mypy.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@ def get_mypy_output(fname, *extra_args):
6060
def get_mypy_analysed_type(fname):
6161
attempts = 0
6262
while True:
63-
out = get_mypy_output(fname).rstrip()
64-
msg = "Success: no issues found in 1 source file"
65-
if out.endswith(msg):
66-
out = out[: -len(msg)]
63+
out = (
64+
get_mypy_output(fname)
65+
.rstrip()
66+
.removesuffix("Success: no issues found in 1 source file")
67+
)
6768
# we've noticed some flakiness in getting an empty output here. Give it
6869
# a couple tries.
6970
if len(out.splitlines()) == 0:

0 commit comments

Comments
 (0)