Skip to content

Commit de4764d

Browse files
committed
use dataclasses for observations in-memory
1 parent 4bba6c1 commit de4764d

File tree

11 files changed

+248
-138
lines changed

11 files changed

+248
-138
lines changed

hypothesis-python/RELEASE.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RELEASE_TYPE: patch
2+
3+
The observations passed to |TESTCASE_CALLBACKS| are now dataclasses, rather than dictionaries. The content written to ``.hypothesis/observed`` under ``HYPOTHESIS_EXPERIMENTAL_OBSERVABILITY`` remains the same.

hypothesis-python/docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ def setup(app):
286286
.. |PrimitiveProvider.draw_bytes| replace:: :func:`~hypothesis.internal.conjecture.providers.PrimitiveProvider.draw_bytes`
287287
288288
.. |AVAILABLE_PROVIDERS| replace:: :data:`~hypothesis.internal.conjecture.providers.AVAILABLE_PROVIDERS`
289+
.. |TESTCASE_CALLBACKS| replace:: :data:`~hypothesis.internal.observability.TESTCASE_CALLBACKS`
289290
.. |BUFFER_SIZE| replace:: :data:`~hypothesis.internal.conjecture.engine.BUFFER_SIZE`
290291
.. |MAX_SHRINKS| replace:: :data:`~hypothesis.internal.conjecture.engine.MAX_SHRINKS`
291292
.. |MAX_SHRINKING_SECONDS| replace:: :data:`~hypothesis.internal.conjecture.engine.MAX_SHRINKING_SECONDS`

hypothesis-python/src/hypothesis/control.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ def reject() -> NoReturn:
4444
)
4545
where = _calling_function_location("reject", inspect.currentframe())
4646
if currently_in_test_context():
47-
count = current_build_context().data._observability_predicates[where]
48-
count["unsatisfied"] += 1
47+
counts = current_build_context().data._observability_predicates[where]
48+
counts.update_count(False)
4949
raise UnsatisfiedAssumption(where)
5050

5151

@@ -65,8 +65,8 @@ def assume(condition: object) -> bool:
6565
if TESTCASE_CALLBACKS or not condition:
6666
where = _calling_function_location("assume", inspect.currentframe())
6767
if TESTCASE_CALLBACKS and currently_in_test_context():
68-
predicates = current_build_context().data._observability_predicates
69-
predicates[where]["satisfied" if condition else "unsatisfied"] += 1
68+
counts = current_build_context().data._observability_predicates[where]
69+
counts.update_count(bool(condition))
7070
if not condition:
7171
raise UnsatisfiedAssumption(f"failed to satisfy {where}")
7272
return True

hypothesis-python/src/hypothesis/core.py

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@
101101
from hypothesis.internal.observability import (
102102
OBSERVABILITY_COLLECT_COVERAGE,
103103
TESTCASE_CALLBACKS,
104-
_system_metadata,
105-
deliver_json_blob,
104+
InfoObservation,
105+
InfoObservationType,
106+
deliver_observation,
106107
make_testcase,
107108
)
108109
from hypothesis.internal.reflection import (
@@ -613,14 +614,14 @@ def execute_explicit_examples(state, wrapped_test, arguments, kwargs, original_s
613614
)
614615

615616
tc = make_testcase(
616-
start_timestamp=state._start_timestamp,
617-
test_name_or_nodeid=state.test_identifier,
617+
run_start=state._start_timestamp,
618+
property=state.test_identifier,
618619
data=empty_data,
619620
how_generated="explicit example",
620-
string_repr=state._string_repr,
621+
representation=state._string_repr,
621622
timing=state._timing_features,
622623
)
623-
deliver_json_blob(tc)
624+
deliver_observation(tc)
624625

625626
if fragments_reported:
626627
verbose_report(fragments_reported[0].replace("Falsifying", "Trying", 1))
@@ -1225,35 +1226,35 @@ def _execute_once_for_engine(self, data: ConjectureData) -> None:
12251226
self._string_repr = "<backend failed to realize symbolic arguments>"
12261227

12271228
tc = make_testcase(
1228-
start_timestamp=self._start_timestamp,
1229-
test_name_or_nodeid=self.test_identifier,
1229+
run_start=self._start_timestamp,
1230+
property=self.test_identifier,
12301231
data=data,
12311232
how_generated=f"during {phase} phase{backend_desc}",
1232-
string_repr=self._string_repr,
1233+
representation=self._string_repr,
12331234
arguments=data._observability_args,
12341235
timing=self._timing_features,
12351236
coverage=tractable_coverage_report(trace) or None,
12361237
phase=phase,
12371238
backend_metadata=data.provider.observe_test_case(),
12381239
)
1239-
deliver_json_blob(tc)
1240+
deliver_observation(tc)
12401241
for msg in data.provider.observe_information_messages(
12411242
lifetime="test_case"
12421243
):
12431244
self._deliver_information_message(**msg)
12441245
self._timing_features = {}
12451246

12461247
def _deliver_information_message(
1247-
self, *, type: str, title: str, content: Union[str, dict]
1248+
self, *, type: InfoObservationType, title: str, content: Union[str, dict]
12481249
) -> None:
1249-
deliver_json_blob(
1250-
{
1251-
"type": type,
1252-
"run_start": self._start_timestamp,
1253-
"property": self.test_identifier,
1254-
"title": title,
1255-
"content": content,
1256-
}
1250+
deliver_observation(
1251+
InfoObservation(
1252+
type=type,
1253+
run_start=self._start_timestamp,
1254+
property=self.test_identifier,
1255+
title=title,
1256+
content=content,
1257+
)
12571258
)
12581259

12591260
def run_engine(self):
@@ -1421,31 +1422,20 @@ def run_engine(self):
14211422
raise NotImplementedError("This should be unreachable")
14221423
finally:
14231424
# log our observability line for the final failing example
1424-
tc = {
1425-
"type": "test_case",
1426-
"run_start": self._start_timestamp,
1427-
"property": self.test_identifier,
1428-
"status": "passed" if sys.exc_info()[0] else "failed",
1429-
"status_reason": str(origin or "unexpected/flaky pass"),
1430-
"representation": self._string_repr,
1431-
"arguments": ran_example._observability_args,
1432-
"how_generated": "minimal failing example",
1433-
"features": {
1434-
**{
1435-
f"target:{k}".strip(":"): v
1436-
for k, v in ran_example.target_observations.items()
1437-
},
1438-
**ran_example.events,
1439-
},
1440-
"timing": self._timing_features,
1441-
"coverage": None, # Not recorded when we're replaying the MFE
1442-
"metadata": {
1443-
"traceback": tb,
1444-
"predicates": dict(ran_example._observability_predicates),
1445-
**_system_metadata(),
1446-
},
1447-
}
1448-
deliver_json_blob(tc)
1425+
tc = make_testcase(
1426+
run_start=self._start_timestamp,
1427+
property=self.test_identifier,
1428+
data=ran_example,
1429+
how_generated="minimal failing example",
1430+
representation=self._string_repr,
1431+
arguments=ran_example._observability_args,
1432+
timing=self._timing_features,
1433+
coverage=None, # Not recorded when we're replaying the MFE
1434+
status="passed" if sys.exc_info()[0] else "failed",
1435+
status_reason=str(origin or "unexpected/flaky pass"),
1436+
metadata={"traceback": tb},
1437+
)
1438+
deliver_observation(tc)
14491439
# Whether or not replay actually raised the exception again, we want
14501440
# to print the reproduce_failure decorator for the failing example.
14511441
if self.settings.print_blob:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ def __init__(
713713
self.slice_comments: dict[tuple[int, int], str] = {}
714714
self._observability_args: dict[str, Any] = {}
715715
self._observability_predicates: defaultdict[str, PredicateCounts] = defaultdict(
716-
lambda: {"satisfied": 0, "unsatisfied": 0}
716+
PredicateCounts
717717
)
718718
self._sampled_from_all_strategies_elements_message: Optional[
719719
tuple[str, object]

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
next_up,
6363
)
6464
from hypothesis.internal.intervalsets import IntervalSet
65+
from hypothesis.internal.observability import InfoObservationType
6566

6667
if TYPE_CHECKING:
6768
from typing import TypeAlias
@@ -294,7 +295,7 @@ def _get_local_constants() -> Constants:
294295

295296

296297
class _BackendInfoMsg(TypedDict):
297-
type: str
298+
type: InfoObservationType
298299
title: str
299300
content: Union[str, dict[str, Any]]
300301

0 commit comments

Comments
 (0)