Skip to content

Commit 8aa9abf

Browse files
authored
Merge pull request #4165 from Zac-HD/small-fixes
Various small fixes
2 parents a2ad4b4 + 8668dd9 commit 8aa9abf

File tree

9 files changed

+95
-56
lines changed

9 files changed

+95
-56
lines changed

.github/workflows/main.yml

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ jobs:
6969
- check-py39-pytest46
7070
- check-py39-pytest54
7171
- check-pytest62
72+
- check-django51
7273
- check-django50
7374
- check-django42
7475
- check-pandas22

hypothesis-python/RELEASE.rst

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
RELEASE_TYPE: patch
2+
3+
This patch avoids computing some string representations we won't need,
4+
giving a small speedup (part of :issue:`4139`).

hypothesis-python/src/hypothesis/core.py

+14-25
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,6 @@ def execute_explicit_examples(state, wrapped_test, arguments, kwargs, original_s
477477
fragments_reported = []
478478
empty_data = ConjectureData.for_buffer(b"")
479479
try:
480-
bits = ", ".join(nicerepr(x) for x in arguments) + ", ".join(
481-
f"{k}={nicerepr(v)}" for k, v in example_kwargs.items()
482-
)
483480
execute_example = partial(
484481
state.execute_once,
485482
empty_data,
@@ -492,7 +489,9 @@ def execute_explicit_examples(state, wrapped_test, arguments, kwargs, original_s
492489
execute_example()
493490
else:
494491
# @example(...).xfail(...)
495-
492+
bits = ", ".join(nicerepr(x) for x in arguments) + ", ".join(
493+
f"{k}={nicerepr(v)}" for k, v in example_kwargs.items()
494+
)
496495
try:
497496
execute_example()
498497
except failure_exceptions_to_catch() as err:
@@ -864,15 +863,6 @@ def run(data):
864863
if expected_failure is not None:
865864
nonlocal text_repr
866865
text_repr = repr_call(test, args, kwargs)
867-
if text_repr in self.xfail_example_reprs:
868-
warnings.warn(
869-
f"We generated {text_repr}, which seems identical "
870-
"to one of your `@example(...).xfail()` cases. "
871-
"Revise the strategy to avoid this overlap?",
872-
HypothesisWarning,
873-
# Checked in test_generating_xfailed_examples_warns!
874-
stacklevel=6,
875-
)
876866

877867
if print_example or current_verbosity() >= Verbosity.verbose:
878868
printer = RepresentationPrinter(context=context)
@@ -1002,18 +992,17 @@ def _execute_once_for_engine(self, data: ConjectureData) -> None:
1002992
"""
1003993
trace: Trace = set()
1004994
try:
1005-
if self._should_trace() and Tracer.can_trace(): # pragma: no cover
1006-
# This is in fact covered by our *non-coverage* tests, but due to the
1007-
# settrace() contention *not* by our coverage tests. Ah well.
1008-
with Tracer() as tracer:
1009-
try:
1010-
result = self.execute_once(data)
1011-
if data.status == Status.VALID:
1012-
self.explain_traces[None].add(frozenset(tracer.branches))
1013-
finally:
1014-
trace = tracer.branches
1015-
else:
1016-
result = self.execute_once(data)
995+
with Tracer(should_trace=self._should_trace()) as tracer:
996+
try:
997+
result = self.execute_once(data)
998+
if (
999+
data.status == Status.VALID and tracer.branches
1000+
): # pragma: no cover
1001+
# This is in fact covered by our *non-coverage* tests, but due
1002+
# to the settrace() contention *not* by our coverage tests.
1003+
self.explain_traces[None].add(frozenset(tracer.branches))
1004+
finally:
1005+
trace = tracer.branches
10171006
if result is not None:
10181007
fail_health_check(
10191008
self.settings,

hypothesis-python/src/hypothesis/internal/scrutineer.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@ def should_trace_file(fname: str) -> bool:
5454
class Tracer:
5555
"""A super-simple branch coverage tracer."""
5656

57-
__slots__ = ("branches", "_previous_location")
57+
__slots__ = ("branches", "_previous_location", "_should_trace")
5858

59-
def __init__(self) -> None:
59+
def __init__(self, *, should_trace: bool) -> None:
6060
self.branches: Trace = set()
6161
self._previous_location: Optional[Location] = None
62+
self._should_trace = should_trace and self.can_trace()
6263

6364
@staticmethod
6465
def can_trace() -> bool:
@@ -92,7 +93,8 @@ def trace_line(self, code: types.CodeType, line_number: int) -> None:
9293
self._previous_location = current_location
9394

9495
def __enter__(self):
95-
assert self.can_trace() # caller checks in core.py
96+
if not self._should_trace:
97+
return self
9698

9799
if sys.version_info[:2] < (3, 12):
98100
sys.settrace(self.trace)
@@ -107,6 +109,9 @@ def __enter__(self):
107109
return self
108110

109111
def __exit__(self, *args, **kwargs):
112+
if not self._should_trace:
113+
return
114+
110115
if sys.version_info[:2] < (3, 12):
111116
sys.settrace(None)
112117
return

hypothesis-python/src/hypothesis/strategies/_internal/strategies.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -121,20 +121,21 @@ def accept(self):
121121

122122
mapping = {}
123123
sentinel = object()
124-
hit_recursion = [False]
124+
hit_recursion = False
125125

126126
# For a first pass we do a direct recursive calculation of the
127127
# property, but we block recursively visiting a value in the
128128
# computation of its property: When that happens, we simply
129129
# note that it happened and return the default value.
130130
def recur(strat):
131+
nonlocal hit_recursion
131132
try:
132133
return forced_value(strat)
133134
except AttributeError:
134135
pass
135136
result = mapping.get(strat, sentinel)
136137
if result is calculating:
137-
hit_recursion[0] = True
138+
hit_recursion = True
138139
return default
139140
elif result is sentinel:
140141
mapping[strat] = calculating
@@ -150,7 +151,7 @@ def recur(strat):
150151
# a more careful fixed point calculation to get the exact
151152
# values. Hopefully our mapping is still pretty good and it
152153
# won't take a large number of updates to reach a fixed point.
153-
if hit_recursion[0]:
154+
if hit_recursion:
154155
needs_update = set(mapping)
155156

156157
# We track which strategies use which in the course of

hypothesis-python/tests/cover/test_example.py

+1-20
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import pytest
1212

1313
from hypothesis import example, given, strategies as st
14-
from hypothesis.errors import HypothesisWarning, InvalidArgument
14+
from hypothesis.errors import InvalidArgument
1515

1616
from tests.common.utils import fails_with
1717

@@ -117,22 +117,3 @@ def test_error_on_unexpected_pass_single_elem_tuple(x):
117117
@given(st.none())
118118
def test_error_on_unexpected_pass_multi(x):
119119
pass
120-
121-
122-
def test_generating_xfailed_examples_warns():
123-
@given(st.integers())
124-
@example(1)
125-
@example(0).xfail(raises=ZeroDivisionError)
126-
def foo(x):
127-
assert 1 / x
128-
129-
with pytest.warns(
130-
HypothesisWarning,
131-
match=r"Revise the strategy to avoid this overlap",
132-
) as wrec:
133-
with pytest.raises(ZeroDivisionError):
134-
foo()
135-
136-
warning_locations = sorted(w.filename for w in wrec.list)
137-
# See the reference in core.py to this test
138-
assert __file__ in warning_locations, "probable stacklevel bug"

hypothesis-python/tests/cover/test_flakiness.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def oops(s):
6161

6262
def test_flaky_with_context_when_fails_only_under_tracing(monkeypatch):
6363
# make anything fail under tracing
64-
monkeypatch.setattr(Tracer, "can_trace", lambda: True)
64+
monkeypatch.setattr(Tracer, "can_trace", staticmethod(lambda: True))
6565
monkeypatch.setattr(Tracer, "__enter__", lambda *_: 1 / 0)
6666
# ensure tracing is always entered inside _execute_once_for_engine
6767
monkeypatch.setattr(StateForActualGivenExecution, "_should_trace", lambda _: True)

hypothesis-python/tox.ini

+9-2
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,21 @@ commands =
161161
setenv=
162162
PYTHONWARNDEFAULTENCODING=1
163163
commands =
164-
pip install django~=4.2.0
164+
pip install django==4.2.16
165165
python -bb -X dev -m tests.django.manage test tests.django {posargs}
166166

167167
[testenv:django50]
168168
setenv=
169169
PYTHONWARNDEFAULTENCODING=1
170170
commands =
171-
pip install django~=5.0.0
171+
pip install django==5.0.9
172+
python -bb -X dev -m tests.django.manage test tests.django {posargs}
173+
174+
[testenv:django51]
175+
setenv=
176+
PYTHONWARNDEFAULTENCODING=1
177+
commands =
178+
pip install django==5.1.3
172179
python -bb -X dev -m tests.django.manage test tests.django {posargs}
173180

174181
[testenv:py{39}-nose]

tooling/src/hypothesistooling/__main__.py

+53-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import re
1414
import subprocess
1515
import sys
16+
from datetime import date
1617
from pathlib import Path
1718

1819
import requests
@@ -30,6 +31,7 @@
3031
os.path.join(tools.ROOT, f)
3132
for f in ["tooling", "requirements", ".github", "hypothesis-python/tox.ini"]
3233
)
34+
TODAY = date.today().isoformat()
3335

3436

3537
def task(if_changed=()):
@@ -321,6 +323,54 @@ def update_python_versions():
321323
build_sh.chmod(0o755)
322324

323325

326+
DJANGO_VERSIONS = {
327+
"4.2": "4.2.16",
328+
"5.0": "5.0.9",
329+
"5.1": "5.1.3",
330+
}
331+
332+
333+
def update_django_versions():
334+
# https://endoflife.date/django makes it easier to track these
335+
releases = requests.get("https://endoflife.date/api/django.json").json()
336+
versions = {r["cycle"]: r["latest"] for r in releases[::-1] if TODAY <= r["eol"]}
337+
338+
if versions == DJANGO_VERSIONS:
339+
return
340+
341+
# Write the new mapping back to this file
342+
thisfile = pathlib.Path(__file__)
343+
before = thisfile.read_text(encoding="utf-8")
344+
after = re.sub(
345+
r"DJANGO_VERSIONS = \{[^{}]+\}",
346+
"DJANGO_VERSIONS = " + repr(versions).replace("}", ",}"),
347+
before,
348+
)
349+
thisfile.write_text(after, encoding="utf-8")
350+
pip_tool("shed", str(thisfile))
351+
352+
# Update the minimum version in setup.py
353+
setup_py = hp.BASE_DIR / "setup.py"
354+
content = re.sub(
355+
r"django>=\d+\.\d+",
356+
f"django>={min(versions, key=float)}",
357+
setup_py.read_text(encoding="utf-8"),
358+
)
359+
setup_py.write_text(content, encoding="utf-8")
360+
361+
# Automatically sync ci_version with the version in build.sh
362+
tox_ini = hp.BASE_DIR / "tox.ini"
363+
content = tox_ini.read_text(encoding="utf-8")
364+
print(versions)
365+
for short, full in versions.items():
366+
content = re.sub(
367+
rf"(pip install django==){short}\.\d+",
368+
rf"\g<1>{full}",
369+
content,
370+
)
371+
tox_ini.write_text(content, encoding="utf-8")
372+
373+
324374
def update_pyodide_versions():
325375
vers_re = r"(\d+\.\d+\.\d+)"
326376
all_versions = re.findall(
@@ -391,6 +441,7 @@ def upgrade_requirements():
391441
f.write(f"RELEASE_TYPE: patch\n\n{msg}")
392442
update_python_versions()
393443
update_pyodide_versions()
444+
update_django_versions()
394445
subprocess.call(["git", "add", "."], cwd=tools.ROOT)
395446

396447

@@ -512,8 +563,8 @@ def standard_tox_task(name, py=ci_version):
512563
standard_tox_task("py39-pytest54", py="3.9")
513564
standard_tox_task("pytest62")
514565

515-
for n in [42, 50]:
516-
standard_tox_task(f"django{n}")
566+
for n in DJANGO_VERSIONS:
567+
standard_tox_task(f"django{n.replace('.', '')}")
517568

518569
for n in [13, 14, 15, 20, 21, 22]:
519570
standard_tox_task(f"pandas{n}")

0 commit comments

Comments
 (0)