扫码登录,获取cookies
This commit is contained in:
262
backend/venv/Lib/site-packages/hypothesis/control.py
Normal file
262
backend/venv/Lib/site-packages/hypothesis/control.py
Normal file
@@ -0,0 +1,262 @@
|
||||
# This file is part of Hypothesis, which may be found at
|
||||
# https://github.com/HypothesisWorks/hypothesis/
|
||||
#
|
||||
# Copyright the Hypothesis Authors.
|
||||
# Individual contributors are listed in AUTHORS.rst and the git log.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
# obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import math
|
||||
from collections import defaultdict
|
||||
from typing import NoReturn, Union
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from hypothesis import Verbosity, settings
|
||||
from hypothesis._settings import note_deprecation
|
||||
from hypothesis.errors import InvalidArgument, UnsatisfiedAssumption
|
||||
from hypothesis.internal.compat import BaseExceptionGroup
|
||||
from hypothesis.internal.conjecture.data import ConjectureData
|
||||
from hypothesis.internal.reflection import get_pretty_function_description
|
||||
from hypothesis.internal.validation import check_type
|
||||
from hypothesis.reporting import report, verbose_report
|
||||
from hypothesis.utils.dynamicvariables import DynamicVariable
|
||||
from hypothesis.vendor.pretty import IDKey
|
||||
|
||||
|
||||
def reject() -> NoReturn:
|
||||
if _current_build_context.value is None:
|
||||
note_deprecation(
|
||||
"Using `reject` outside a property-based test is deprecated",
|
||||
since="2023-09-25",
|
||||
has_codemod=False,
|
||||
)
|
||||
raise UnsatisfiedAssumption
|
||||
|
||||
|
||||
def assume(condition: object) -> bool:
|
||||
"""Calling ``assume`` is like an :ref:`assert <python:assert>` that marks
|
||||
the example as bad, rather than failing the test.
|
||||
|
||||
This allows you to specify properties that you *assume* will be
|
||||
true, and let Hypothesis try to avoid similar examples in future.
|
||||
"""
|
||||
if _current_build_context.value is None:
|
||||
note_deprecation(
|
||||
"Using `assume` outside a property-based test is deprecated",
|
||||
since="2023-09-25",
|
||||
has_codemod=False,
|
||||
)
|
||||
if not condition:
|
||||
raise UnsatisfiedAssumption
|
||||
return True
|
||||
|
||||
|
||||
_current_build_context = DynamicVariable(None)
|
||||
|
||||
|
||||
def currently_in_test_context() -> bool:
|
||||
"""Return ``True`` if the calling code is currently running inside an
|
||||
:func:`@given <hypothesis.given>` or :doc:`stateful <stateful>` test,
|
||||
``False`` otherwise.
|
||||
|
||||
This is useful for third-party integrations and assertion helpers which
|
||||
may be called from traditional or property-based tests, but can only use
|
||||
:func:`~hypothesis.assume` or :func:`~hypothesis.target` in the latter case.
|
||||
"""
|
||||
return _current_build_context.value is not None
|
||||
|
||||
|
||||
def current_build_context() -> "BuildContext":
|
||||
context = _current_build_context.value
|
||||
if context is None:
|
||||
raise InvalidArgument("No build context registered")
|
||||
return context
|
||||
|
||||
|
||||
class BuildContext:
|
||||
def __init__(self, data, *, is_final=False, close_on_capture=True):
|
||||
assert isinstance(data, ConjectureData)
|
||||
self.data = data
|
||||
self.tasks = []
|
||||
self.is_final = is_final
|
||||
self.close_on_capture = close_on_capture
|
||||
self.close_on_del = False
|
||||
# Use defaultdict(list) here to handle the possibility of having multiple
|
||||
# functions registered for the same object (due to caching, small ints, etc).
|
||||
# The printer will discard duplicates which return different representations.
|
||||
self.known_object_printers = defaultdict(list)
|
||||
|
||||
def record_call(self, obj, func, args, kwargs, arg_slices=None):
|
||||
name = get_pretty_function_description(func)
|
||||
self.known_object_printers[IDKey(obj)].append(
|
||||
lambda obj, p, cycle: (
|
||||
p.text("<...>")
|
||||
if cycle
|
||||
else p.repr_call(name, args, kwargs, arg_slices=arg_slices)
|
||||
)
|
||||
)
|
||||
|
||||
def prep_args_kwargs_from_strategies(self, arg_strategies, kwarg_strategies):
|
||||
arg_labels = {}
|
||||
all_s = [(None, s) for s in arg_strategies] + list(kwarg_strategies.items())
|
||||
args = []
|
||||
kwargs = {}
|
||||
for i, (k, s) in enumerate(all_s):
|
||||
start_idx = self.data.index
|
||||
obj = self.data.draw(s)
|
||||
end_idx = self.data.index
|
||||
assert k is not None
|
||||
kwargs[k] = obj
|
||||
|
||||
# This high up the stack, we can't see or really do much with the conjecture
|
||||
# Example objects - not least because they're only materialized after the
|
||||
# test case is completed. Instead, we'll stash the (start_idx, end_idx)
|
||||
# pair on our data object for the ConjectureRunner engine to deal with, and
|
||||
# pass a dict of such out so that the pretty-printer knows where to place
|
||||
# the which-parts-matter comments later.
|
||||
if start_idx != end_idx:
|
||||
arg_labels[k or i] = (start_idx, end_idx)
|
||||
self.data.arg_slices.add((start_idx, end_idx))
|
||||
|
||||
return args, kwargs, arg_labels
|
||||
|
||||
def __enter__(self):
|
||||
self.assign_variable = _current_build_context.with_value(self)
|
||||
self.assign_variable.__enter__()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.assign_variable.__exit__(exc_type, exc_value, tb)
|
||||
errors = []
|
||||
for task in self.tasks:
|
||||
try:
|
||||
task()
|
||||
except BaseException as err:
|
||||
errors.append(err)
|
||||
if errors:
|
||||
if len(errors) == 1:
|
||||
raise errors[0] from exc_value
|
||||
raise BaseExceptionGroup("Cleanup failed", errors) from exc_value
|
||||
|
||||
|
||||
def cleanup(teardown):
|
||||
"""Register a function to be called when the current test has finished
|
||||
executing. Any exceptions thrown in teardown will be printed but not
|
||||
rethrown.
|
||||
|
||||
Inside a test this isn't very interesting, because you can just use
|
||||
a finally block, but note that you can use this inside map, flatmap,
|
||||
etc. in order to e.g. insist that a value is closed at the end.
|
||||
"""
|
||||
context = _current_build_context.value
|
||||
if context is None:
|
||||
raise InvalidArgument("Cannot register cleanup outside of build context")
|
||||
context.tasks.append(teardown)
|
||||
|
||||
|
||||
def should_note():
|
||||
context = _current_build_context.value
|
||||
if context is None:
|
||||
raise InvalidArgument("Cannot make notes outside of a test")
|
||||
return context.is_final or settings.default.verbosity >= Verbosity.verbose
|
||||
|
||||
|
||||
def note(value: str) -> None:
|
||||
"""Report this value for the minimal failing example."""
|
||||
if should_note():
|
||||
report(value)
|
||||
|
||||
|
||||
def event(value: str, payload: Union[str, int, float] = "") -> None:
|
||||
"""Record an event that occurred during this test. Statistics on the number of test
|
||||
runs with each event will be reported at the end if you run Hypothesis in
|
||||
statistics reporting mode.
|
||||
|
||||
Event values should be strings or convertible to them. If an optional
|
||||
payload is given, it will be included in the string for :ref:`statistics`.
|
||||
"""
|
||||
context = _current_build_context.value
|
||||
if context is None:
|
||||
raise InvalidArgument("Cannot make record events outside of a test")
|
||||
|
||||
payload = _event_to_string(payload, (str, int, float))
|
||||
context.data.events[_event_to_string(value)] = payload
|
||||
|
||||
|
||||
_events_to_strings: WeakKeyDictionary = WeakKeyDictionary()
|
||||
|
||||
|
||||
def _event_to_string(event, allowed_types=str):
|
||||
if isinstance(event, allowed_types):
|
||||
return event
|
||||
try:
|
||||
return _events_to_strings[event]
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
result = str(event)
|
||||
try:
|
||||
_events_to_strings[event] = result
|
||||
except TypeError:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
def target(observation: Union[int, float], *, label: str = "") -> Union[int, float]:
|
||||
"""Calling this function with an ``int`` or ``float`` observation gives it feedback
|
||||
with which to guide our search for inputs that will cause an error, in
|
||||
addition to all the usual heuristics. Observations must always be finite.
|
||||
|
||||
Hypothesis will try to maximize the observed value over several examples;
|
||||
almost any metric will work so long as it makes sense to increase it.
|
||||
For example, ``-abs(error)`` is a metric that increases as ``error``
|
||||
approaches zero.
|
||||
|
||||
Example metrics:
|
||||
|
||||
- Number of elements in a collection, or tasks in a queue
|
||||
- Mean or maximum runtime of a task (or both, if you use ``label``)
|
||||
- Compression ratio for data (perhaps per-algorithm or per-level)
|
||||
- Number of steps taken by a state machine
|
||||
|
||||
The optional ``label`` argument can be used to distinguish between
|
||||
and therefore separately optimise distinct observations, such as the
|
||||
mean and standard deviation of a dataset. It is an error to call
|
||||
``target()`` with any label more than once per test case.
|
||||
|
||||
.. note::
|
||||
**The more examples you run, the better this technique works.**
|
||||
|
||||
As a rule of thumb, the targeting effect is noticeable above
|
||||
:obj:`max_examples=1000 <hypothesis.settings.max_examples>`,
|
||||
and immediately obvious by around ten thousand examples
|
||||
*per label* used by your test.
|
||||
|
||||
:ref:`statistics` include the best score seen for each label,
|
||||
which can help avoid `the threshold problem
|
||||
<https://hypothesis.works/articles/threshold-problem/>`__ when the minimal
|
||||
example shrinks right down to the threshold of failure (:issue:`2180`).
|
||||
"""
|
||||
check_type((int, float), observation, "observation")
|
||||
if not math.isfinite(observation):
|
||||
raise InvalidArgument(f"{observation=} must be a finite float.")
|
||||
check_type(str, label, "label")
|
||||
|
||||
context = _current_build_context.value
|
||||
if context is None:
|
||||
raise InvalidArgument(
|
||||
"Calling target() outside of a test is invalid. "
|
||||
"Consider guarding this call with `if currently_in_test_context(): ...`"
|
||||
)
|
||||
verbose_report(f"Saw target({observation!r}, {label=})")
|
||||
|
||||
if label in context.data.target_observations:
|
||||
raise InvalidArgument(
|
||||
f"Calling target({observation!r}, {label=}) would overwrite "
|
||||
f"target({context.data.target_observations[label]!r}, {label=})"
|
||||
)
|
||||
else:
|
||||
context.data.target_observations[label] = observation
|
||||
|
||||
return observation
|
||||
Reference in New Issue
Block a user