Skip to content

Commit

Permalink
Merge pull request #4165 from Zac-HD/small-fixes
Browse files Browse the repository at this point in the history
Various small fixes
  • Loading branch information
Zac-HD authored Nov 10, 2024
2 parents a2ad4b4 + 8668dd9 commit 8aa9abf
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 56 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ jobs:
- check-py39-pytest46
- check-py39-pytest54
- check-pytest62
- check-django51
- check-django50
- check-django42
- check-pandas22
Expand Down
4 changes: 4 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RELEASE_TYPE: patch

This patch avoids computing some string representations we won't need,
giving a small speedup (part of :issue:`4139`).
39 changes: 14 additions & 25 deletions hypothesis-python/src/hypothesis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,9 +477,6 @@ def execute_explicit_examples(state, wrapped_test, arguments, kwargs, original_s
fragments_reported = []
empty_data = ConjectureData.for_buffer(b"")
try:
bits = ", ".join(nicerepr(x) for x in arguments) + ", ".join(
f"{k}={nicerepr(v)}" for k, v in example_kwargs.items()
)
execute_example = partial(
state.execute_once,
empty_data,
Expand All @@ -492,7 +489,9 @@ def execute_explicit_examples(state, wrapped_test, arguments, kwargs, original_s
execute_example()
else:
# @example(...).xfail(...)

bits = ", ".join(nicerepr(x) for x in arguments) + ", ".join(
f"{k}={nicerepr(v)}" for k, v in example_kwargs.items()
)
try:
execute_example()
except failure_exceptions_to_catch() as err:
Expand Down Expand Up @@ -864,15 +863,6 @@ def run(data):
if expected_failure is not None:
nonlocal text_repr
text_repr = repr_call(test, args, kwargs)
if text_repr in self.xfail_example_reprs:
warnings.warn(
f"We generated {text_repr}, which seems identical "
"to one of your `@example(...).xfail()` cases. "
"Revise the strategy to avoid this overlap?",
HypothesisWarning,
# Checked in test_generating_xfailed_examples_warns!
stacklevel=6,
)

if print_example or current_verbosity() >= Verbosity.verbose:
printer = RepresentationPrinter(context=context)
Expand Down Expand Up @@ -1002,18 +992,17 @@ def _execute_once_for_engine(self, data: ConjectureData) -> None:
"""
trace: Trace = set()
try:
if self._should_trace() and Tracer.can_trace(): # pragma: no cover
# This is in fact covered by our *non-coverage* tests, but due to the
# settrace() contention *not* by our coverage tests. Ah well.
with Tracer() as tracer:
try:
result = self.execute_once(data)
if data.status == Status.VALID:
self.explain_traces[None].add(frozenset(tracer.branches))
finally:
trace = tracer.branches
else:
result = self.execute_once(data)
with Tracer(should_trace=self._should_trace()) as tracer:
try:
result = self.execute_once(data)
if (
data.status == Status.VALID and tracer.branches
): # pragma: no cover
# This is in fact covered by our *non-coverage* tests, but due
# to the settrace() contention *not* by our coverage tests.
self.explain_traces[None].add(frozenset(tracer.branches))
finally:
trace = tracer.branches
if result is not None:
fail_health_check(
self.settings,
Expand Down
11 changes: 8 additions & 3 deletions hypothesis-python/src/hypothesis/internal/scrutineer.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ def should_trace_file(fname: str) -> bool:
class Tracer:
"""A super-simple branch coverage tracer."""

__slots__ = ("branches", "_previous_location")
__slots__ = ("branches", "_previous_location", "_should_trace")

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

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

def __enter__(self):
assert self.can_trace() # caller checks in core.py
if not self._should_trace:
return self

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

def __exit__(self, *args, **kwargs):
if not self._should_trace:
return

if sys.version_info[:2] < (3, 12):
sys.settrace(None)
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,21 @@ def accept(self):

mapping = {}
sentinel = object()
hit_recursion = [False]
hit_recursion = False

# For a first pass we do a direct recursive calculation of the
# property, but we block recursively visiting a value in the
# computation of its property: When that happens, we simply
# note that it happened and return the default value.
def recur(strat):
nonlocal hit_recursion
try:
return forced_value(strat)
except AttributeError:
pass
result = mapping.get(strat, sentinel)
if result is calculating:
hit_recursion[0] = True
hit_recursion = True
return default
elif result is sentinel:
mapping[strat] = calculating
Expand All @@ -150,7 +151,7 @@ def recur(strat):
# a more careful fixed point calculation to get the exact
# values. Hopefully our mapping is still pretty good and it
# won't take a large number of updates to reach a fixed point.
if hit_recursion[0]:
if hit_recursion:
needs_update = set(mapping)

# We track which strategies use which in the course of
Expand Down
21 changes: 1 addition & 20 deletions hypothesis-python/tests/cover/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import pytest

from hypothesis import example, given, strategies as st
from hypothesis.errors import HypothesisWarning, InvalidArgument
from hypothesis.errors import InvalidArgument

from tests.common.utils import fails_with

Expand Down Expand Up @@ -117,22 +117,3 @@ def test_error_on_unexpected_pass_single_elem_tuple(x):
@given(st.none())
def test_error_on_unexpected_pass_multi(x):
pass


def test_generating_xfailed_examples_warns():
@given(st.integers())
@example(1)
@example(0).xfail(raises=ZeroDivisionError)
def foo(x):
assert 1 / x

with pytest.warns(
HypothesisWarning,
match=r"Revise the strategy to avoid this overlap",
) as wrec:
with pytest.raises(ZeroDivisionError):
foo()

warning_locations = sorted(w.filename for w in wrec.list)
# See the reference in core.py to this test
assert __file__ in warning_locations, "probable stacklevel bug"
2 changes: 1 addition & 1 deletion hypothesis-python/tests/cover/test_flakiness.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def oops(s):

def test_flaky_with_context_when_fails_only_under_tracing(monkeypatch):
# make anything fail under tracing
monkeypatch.setattr(Tracer, "can_trace", lambda: True)
monkeypatch.setattr(Tracer, "can_trace", staticmethod(lambda: True))
monkeypatch.setattr(Tracer, "__enter__", lambda *_: 1 / 0)
# ensure tracing is always entered inside _execute_once_for_engine
monkeypatch.setattr(StateForActualGivenExecution, "_should_trace", lambda _: True)
Expand Down
11 changes: 9 additions & 2 deletions hypothesis-python/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,21 @@ commands =
setenv=
PYTHONWARNDEFAULTENCODING=1
commands =
pip install django~=4.2.0
pip install django==4.2.16
python -bb -X dev -m tests.django.manage test tests.django {posargs}

[testenv:django50]
setenv=
PYTHONWARNDEFAULTENCODING=1
commands =
pip install django~=5.0.0
pip install django==5.0.9
python -bb -X dev -m tests.django.manage test tests.django {posargs}

[testenv:django51]
setenv=
PYTHONWARNDEFAULTENCODING=1
commands =
pip install django==5.1.3
python -bb -X dev -m tests.django.manage test tests.django {posargs}

[testenv:py{39}-nose]
Expand Down
55 changes: 53 additions & 2 deletions tooling/src/hypothesistooling/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import re
import subprocess
import sys
from datetime import date
from pathlib import Path

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


def task(if_changed=()):
Expand Down Expand Up @@ -321,6 +323,54 @@ def update_python_versions():
build_sh.chmod(0o755)


DJANGO_VERSIONS = {
"4.2": "4.2.16",
"5.0": "5.0.9",
"5.1": "5.1.3",
}


def update_django_versions():
# https://endoflife.date/django makes it easier to track these
releases = requests.get("https://endoflife.date/api/django.json").json()
versions = {r["cycle"]: r["latest"] for r in releases[::-1] if TODAY <= r["eol"]}

if versions == DJANGO_VERSIONS:
return

# Write the new mapping back to this file
thisfile = pathlib.Path(__file__)
before = thisfile.read_text(encoding="utf-8")
after = re.sub(
r"DJANGO_VERSIONS = \{[^{}]+\}",
"DJANGO_VERSIONS = " + repr(versions).replace("}", ",}"),
before,
)
thisfile.write_text(after, encoding="utf-8")
pip_tool("shed", str(thisfile))

# Update the minimum version in setup.py
setup_py = hp.BASE_DIR / "setup.py"
content = re.sub(
r"django>=\d+\.\d+",
f"django>={min(versions, key=float)}",
setup_py.read_text(encoding="utf-8"),
)
setup_py.write_text(content, encoding="utf-8")

# Automatically sync ci_version with the version in build.sh
tox_ini = hp.BASE_DIR / "tox.ini"
content = tox_ini.read_text(encoding="utf-8")
print(versions)
for short, full in versions.items():
content = re.sub(
rf"(pip install django==){short}\.\d+",
rf"\g<1>{full}",
content,
)
tox_ini.write_text(content, encoding="utf-8")


def update_pyodide_versions():
vers_re = r"(\d+\.\d+\.\d+)"
all_versions = re.findall(
Expand Down Expand Up @@ -391,6 +441,7 @@ def upgrade_requirements():
f.write(f"RELEASE_TYPE: patch\n\n{msg}")
update_python_versions()
update_pyodide_versions()
update_django_versions()
subprocess.call(["git", "add", "."], cwd=tools.ROOT)


Expand Down Expand Up @@ -512,8 +563,8 @@ def standard_tox_task(name, py=ci_version):
standard_tox_task("py39-pytest54", py="3.9")
standard_tox_task("pytest62")

for n in [42, 50]:
standard_tox_task(f"django{n}")
for n in DJANGO_VERSIONS:
standard_tox_task(f"django{n.replace('.', '')}")

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

0 comments on commit 8aa9abf

Please sign in to comment.