Skip to content

Commit f9d93e2

Browse files
authored
Merge pull request #3799 from HypothesisWorks/create-pull-request/patch
Update pinned dependencies
2 parents 47c286d + a3d9623 commit f9d93e2

File tree

26 files changed

+158
-177
lines changed

26 files changed

+158
-177
lines changed

hypothesis-python/.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ exclude_lines =
2828
if TYPE_CHECKING:
2929
if sys\.version_info
3030
if "[\w\.]+" in sys\.modules:
31+
if .+ := sys\.modules\.get\("[\w\.]+"\)

hypothesis-python/RELEASE.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
RELEASE_TYPE: minor
2+
3+
This release adds an optional ``payload`` argument to :func:`hypothesis.event`,
4+
so that you can clearly express the difference between the label and the value
5+
of an observation. :ref:`statistics` will still summarize it as a string, but
6+
future observability options can preserve the distinction.

hypothesis-python/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ def local_file(name):
117117
"Programming Language :: Python :: 3.9",
118118
"Programming Language :: Python :: 3.10",
119119
"Programming Language :: Python :: 3.11",
120+
"Programming Language :: Python :: 3.12",
120121
"Programming Language :: Python :: Implementation :: CPython",
121122
"Programming Language :: Python :: Implementation :: PyPy",
122123
"Topic :: Education :: Testing",

hypothesis-python/src/hypothesis/control.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import math
1212
from collections import defaultdict
1313
from typing import NoReturn, Union
14+
from weakref import WeakKeyDictionary
1415

1516
from hypothesis import Verbosity, settings
1617
from hypothesis._settings import note_deprecation
@@ -168,18 +169,38 @@ def note(value: str) -> None:
168169
report(value)
169170

170171

171-
def event(value: str) -> None:
172-
"""Record an event that occurred this test. Statistics on number of test
172+
def event(value: str, payload: Union[str, int, float] = "") -> None:
173+
"""Record an event that occurred during this test. Statistics on the number of test
173174
runs with each event will be reported at the end if you run Hypothesis in
174175
statistics reporting mode.
175176
176-
Events should be strings or convertible to them.
177+
Event values should be strings or convertible to them. If an optional
178+
payload is given, it will be included in the string for :ref:`statistics`.
177179
"""
178180
context = _current_build_context.value
179181
if context is None:
180182
raise InvalidArgument("Cannot make record events outside of a test")
181183

182-
context.data.note_event(value)
184+
payload = _event_to_string(payload, (str, int, float))
185+
context.data.events[_event_to_string(value)] = payload
186+
187+
188+
_events_to_strings: WeakKeyDictionary = WeakKeyDictionary()
189+
190+
191+
def _event_to_string(event, allowed_types=str):
192+
if isinstance(event, allowed_types):
193+
return event
194+
try:
195+
return _events_to_strings[event]
196+
except (KeyError, TypeError):
197+
pass
198+
result = str(event)
199+
try:
200+
_events_to_strings[event] = result
201+
except TypeError:
202+
pass
203+
return result
183204

184205

185206
def target(observation: Union[int, float], *, label: str = "") -> Union[int, float]:

hypothesis-python/src/hypothesis/core.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@
8080
from hypothesis.internal.conjecture.shrinker import sort_key
8181
from hypothesis.internal.entropy import deterministic_PRNG
8282
from hypothesis.internal.escalation import (
83+
InterestingOrigin,
8384
current_pytest_item,
8485
escalate_hypothesis_internal_error,
8586
format_exception,
86-
get_interesting_origin,
8787
get_trimmed_traceback,
8888
)
8989
from hypothesis.internal.healthcheck import fail_health_check
@@ -970,7 +970,7 @@ def _execute_once_for_engine(self, data):
970970

971971
self.failed_normally = True
972972

973-
interesting_origin = get_interesting_origin(e)
973+
interesting_origin = InterestingOrigin.from_exception(e)
974974
if trace: # pragma: no cover
975975
# Trace collection is explicitly disabled under coverage.
976976
self.explain_traces[interesting_origin].add(trace)
@@ -1037,7 +1037,9 @@ def run_engine(self):
10371037
info = falsifying_example.extra_information
10381038
fragments = []
10391039

1040-
ran_example = ConjectureData.for_buffer(falsifying_example.buffer)
1040+
ran_example = runner.new_conjecture_data_for_buffer(
1041+
falsifying_example.buffer
1042+
)
10411043
ran_example.slice_comments = falsifying_example.slice_comments
10421044
assert info.__expected_exception is not None
10431045
try:

hypothesis-python/src/hypothesis/internal/conjecture/data.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
Callable,
2121
Dict,
2222
FrozenSet,
23-
Hashable,
2423
Iterable,
2524
Iterator,
2625
List,
@@ -1367,7 +1366,7 @@ def __init__(
13671366
self.testcounter = global_test_counter
13681367
global_test_counter += 1
13691368
self.start_time = time.perf_counter()
1370-
self.events: "Union[Set[Hashable], FrozenSet[Hashable]]" = set()
1369+
self.events: Dict[str, Union[str, int, float]] = {}
13711370
self.forced_indices: "Set[int]" = set()
13721371
self.interesting_origin: Optional[InterestingOrigin] = None
13731372
self.draw_times: "List[float]" = []
@@ -1615,10 +1614,6 @@ def stop_example(self, *, discard: bool = False) -> None:
16151614

16161615
self.observer.kill_branch()
16171616

1618-
def note_event(self, event: Hashable) -> None:
1619-
assert isinstance(self.events, set)
1620-
self.events.add(event)
1621-
16221617
@property
16231618
def examples(self) -> Examples:
16241619
assert self.frozen
@@ -1643,7 +1638,6 @@ def freeze(self) -> None:
16431638
self.frozen = True
16441639

16451640
self.buffer = bytes(self.buffer)
1646-
self.events = frozenset(self.events)
16471641
self.observer.conclude_test(self.status, self.interesting_origin)
16481642

16491643
def draw_bits(self, n: int, *, forced: Optional[int] = None) -> int:
@@ -1729,7 +1723,7 @@ def mark_interesting(
17291723

17301724
def mark_invalid(self, why: Optional[str] = None) -> NoReturn:
17311725
if why is not None:
1732-
self.note_event(why)
1726+
self.events["invalid because"] = why
17331727
self.conclude_test(Status.INVALID)
17341728

17351729
def mark_overrun(self) -> NoReturn:

hypothesis-python/src/hypothesis/internal/conjecture/engine.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from datetime import timedelta
1616
from enum import Enum
1717
from random import Random, getrandbits
18-
from weakref import WeakKeyDictionary
1918

2019
import attr
2120

@@ -101,8 +100,6 @@ def __init__(
101100
self.statistics = {}
102101
self.stats_per_test_case = []
103102

104-
self.events_to_strings = WeakKeyDictionary()
105-
106103
self.interesting_examples = {}
107104
# We use call_count because there may be few possible valid_examples.
108105
self.first_bug_found_at = None
@@ -209,7 +206,9 @@ def test_function(self, data):
209206
"status": data.status.name.lower(),
210207
"runtime": data.finish_time - data.start_time,
211208
"drawtime": math.fsum(data.draw_times),
212-
"events": sorted({self.event_to_string(e) for e in data.events}),
209+
"events": sorted(
210+
k if v == "" else f"{k}: {v}" for k, v in data.events.items()
211+
),
213212
}
214213
self.stats_per_test_case.append(call_stats)
215214
self.__data_cache[data.buffer] = data.as_result()
@@ -1055,20 +1054,6 @@ def kill_branch(self):
10551054
self.__data_cache[buffer] = result
10561055
return result
10571056

1058-
def event_to_string(self, event):
1059-
if isinstance(event, str):
1060-
return event
1061-
try:
1062-
return self.events_to_strings[event]
1063-
except (KeyError, TypeError):
1064-
pass
1065-
result = str(event)
1066-
try:
1067-
self.events_to_strings[event] = result
1068-
except TypeError:
1069-
pass
1070-
return result
1071-
10721057
def passing_buffers(self, prefix=b""):
10731058
"""Return a collection of bytestrings which cause the test to pass.
10741059

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

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
import contextlib
1212
import os
1313
import sys
14+
import textwrap
1415
import traceback
1516
from inspect import getframeinfo
1617
from pathlib import Path
17-
from typing import Dict
18+
from typing import Dict, NamedTuple, Optional, Type
1819

1920
import hypothesis
2021
from hypothesis.errors import (
@@ -105,32 +106,46 @@ def get_trimmed_traceback(exception=None):
105106
return tb
106107

107108

108-
def get_interesting_origin(exception):
109+
class InterestingOrigin(NamedTuple):
109110
# The `interesting_origin` is how Hypothesis distinguishes between multiple
110111
# failures, for reporting and also to replay from the example database (even
111112
# if report_multiple_bugs=False). We traditionally use the exception type and
112113
# location, but have extracted this logic in order to see through `except ...:`
113114
# blocks and understand the __cause__ (`raise x from y`) or __context__ that
114115
# first raised an exception as well as PEP-654 exception groups.
115-
tb = get_trimmed_traceback(exception)
116-
if tb is None:
116+
exc_type: Type[BaseException]
117+
filename: Optional[str]
118+
lineno: Optional[int]
119+
context: "InterestingOrigin | tuple[()]"
120+
group_elems: "tuple[InterestingOrigin, ...]"
121+
122+
def __str__(self) -> str:
123+
ctx = ""
124+
if self.context:
125+
ctx = textwrap.indent(f"\ncontext: {self.context}", prefix=" ")
126+
group = ""
127+
if self.group_elems:
128+
chunks = "\n ".join(str(x) for x in self.group_elems)
129+
group = textwrap.indent(f"\nchild exceptions:\n {chunks}", prefix=" ")
130+
return f"{self.exc_type.__name__} at {self.filename}:{self.lineno}{ctx}{group}"
131+
132+
@classmethod
133+
def from_exception(cls, exception: BaseException, /) -> "InterestingOrigin":
117134
filename, lineno = None, None
118-
else:
119-
filename, lineno, *_ = traceback.extract_tb(tb)[-1]
120-
return (
121-
type(exception),
122-
filename,
123-
lineno,
124-
# Note that if __cause__ is set it is always equal to __context__, explicitly
125-
# to support introspection when debugging, so we can use that unconditionally.
126-
get_interesting_origin(exception.__context__) if exception.__context__ else (),
127-
# We distinguish exception groups by the inner exceptions, as for __context__
128-
tuple(
129-
map(get_interesting_origin, exception.exceptions)
135+
if tb := get_trimmed_traceback(exception):
136+
filename, lineno, *_ = traceback.extract_tb(tb)[-1]
137+
return cls(
138+
type(exception),
139+
filename,
140+
lineno,
141+
# Note that if __cause__ is set it is always equal to __context__, explicitly
142+
# to support introspection when debugging, so we can use that unconditionally.
143+
cls.from_exception(exception.__context__) if exception.__context__ else (),
144+
# We distinguish exception groups by the inner exceptions, as for __context__
145+
tuple(map(cls.from_exception, exception.exceptions))
130146
if isinstance(exception, BaseExceptionGroup)
131-
else []
132-
),
133-
)
147+
else (),
148+
)
134149

135150

136151
current_pytest_item = DynamicVariable(None)

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

Lines changed: 0 additions & 33 deletions
This file was deleted.

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,10 @@ def draw_naive_datetime_and_combine(self, data, tz):
163163
try:
164164
return replace_tzinfo(dt.datetime(**result), timezone=tz)
165165
except (ValueError, OverflowError):
166-
msg = "Failed to draw a datetime between %r and %r with timezone from %r."
167-
data.mark_invalid(msg % (self.min_value, self.max_value, self.tz_strat))
166+
data.mark_invalid(
167+
f"Failed to draw a datetime between {self.min_value!r} and "
168+
f"{self.max_value!r} with timezone from {self.tz_strat!r}."
169+
)
168170

169171

170172
@defines_strategy(force_reusable_values=True)

0 commit comments

Comments
 (0)