Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor BundleReferenceStrategy into Bundle #4124

Merged
merged 5 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RELEASE_TYPE: patch

This release refactors internals of :class:`hypothesis.stateful.Bundle` to have a more consistent representation internally.
2 changes: 1 addition & 1 deletion hypothesis-python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def local_file(name):
"pytest": ["pytest>=4.6"],
"dpcontracts": ["dpcontracts>=0.4"],
"redis": ["redis>=3.0.0"],
"crosshair": ["hypothesis-crosshair>=0.0.14", "crosshair-tool>=0.0.72"],
"crosshair": ["hypothesis-crosshair>=0.0.14", "crosshair-tool>=0.0.73"],
# zoneinfo is an odd one: every dependency is conditional, because they're
# only necessary on old versions of Python or Windows systems or emscripten.
"zoneinfo": [
Expand Down
65 changes: 31 additions & 34 deletions hypothesis-python/src/hypothesis/stateful.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ def output(s):
for k, v in list(data.items()):
if isinstance(v, VarReference):
data[k] = machine.names_to_values[v.name]
elif isinstance(v, list) and all(
isinstance(item, VarReference) for item in v
):
data[k] = [machine.names_to_values[item.name] for item in v]

label = f"execute:rule:{rule.function.__name__}"
start = perf_counter()
Expand Down Expand Up @@ -300,6 +304,10 @@ def __init__(self) -> None:
def _pretty_print(self, value):
if isinstance(value, VarReference):
return value.name
elif isinstance(value, list) and all(
isinstance(item, VarReference) for item in value
):
return "[" + ", ".join([item.name for item in value]) + "]"
self.__stream.seek(0)
self.__stream.truncate(0)
self.__printer.output_width = 0
Expand Down Expand Up @@ -457,11 +465,8 @@ def __attrs_post_init__(self):
self.arguments_strategies = {}
bundles = []
for k, v in sorted(self.arguments.items()):
assert not isinstance(v, BundleReferenceStrategy)
if isinstance(v, Bundle):
bundles.append(v)
consume = isinstance(v, BundleConsumer)
v = BundleReferenceStrategy(v.name, consume=consume)
self.arguments_strategies[k] = v
self.bundles = tuple(bundles)

Expand All @@ -474,26 +479,6 @@ def __repr__(self) -> str:
self_strategy = st.runner()


class BundleReferenceStrategy(SearchStrategy):
def __init__(self, name: str, *, consume: bool = False):
self.name = name
self.consume = consume

def do_draw(self, data):
machine = data.draw(self_strategy)
bundle = machine.bundle(self.name)
if not bundle:
data.mark_invalid(f"Cannot draw from empty bundle {self.name!r}")
# Shrink towards the right rather than the left. This makes it easier
# to delete data generated earlier, as when the error is towards the
# end there can be a lot of hard to remove padding.
position = data.draw_integer(0, len(bundle) - 1, shrink_towards=len(bundle))
if self.consume:
return bundle.pop(position) # pragma: no cover # coverage is flaky here
else:
return bundle[position]


class Bundle(SearchStrategy[Ex]):
"""A collection of values for use in stateful testing.

Expand All @@ -518,15 +503,29 @@ class MyStateMachine(RuleBasedStateMachine):

def __init__(self, name: str, *, consume: bool = False) -> None:
self.name = name
self.__reference_strategy = BundleReferenceStrategy(name, consume=consume)
self.consume = consume

def do_draw(self, data):
machine = data.draw(self_strategy)
reference = data.draw(self.__reference_strategy)
return machine.names_to_values[reference.name]

bundle = machine.bundle(self.name)
if not bundle:
data.mark_invalid(f"Cannot draw from empty bundle {self.name!r}")
# Shrink towards the right rather than the left. This makes it easier
# to delete data generated earlier, as when the error is towards the
# end there can be a lot of hard to remove padding.
position = data.draw_integer(0, len(bundle) - 1, shrink_towards=len(bundle))
if self.consume:
reference = bundle.pop(
position
) # pragma: no cover # coverage is flaky here
else:
reference = bundle[position]

return reference

def __repr__(self):
consume = self.__reference_strategy.consume
consume = self.consume
if consume is False:
return f"Bundle(name={self.name!r})"
return f"Bundle(name={self.name!r}, {consume=})"
Expand All @@ -543,11 +542,6 @@ def available(self, data):
return bool(machine.bundle(self.name))


class BundleConsumer(Bundle[Ex]):
def __init__(self, bundle: Bundle[Ex]) -> None:
super().__init__(bundle.name, consume=True)


def consumes(bundle: Bundle[Ex]) -> SearchStrategy[Ex]:
"""When introducing a rule in a RuleBasedStateMachine, this function can
be used to mark bundles from which each value used in a step with the
Expand All @@ -563,7 +557,10 @@ def consumes(bundle: Bundle[Ex]) -> SearchStrategy[Ex]:
"""
if not isinstance(bundle, Bundle):
raise TypeError("Argument to be consumed must be a bundle.")
return BundleConsumer(bundle)
return type(bundle)(
name=bundle.name,
consume=True,
)


@attr.s()
Expand Down Expand Up @@ -610,7 +607,7 @@ def _convert_targets(targets, target):
)
raise InvalidArgument(msg % (t, type(t)))
while isinstance(t, Bundle):
if isinstance(t, BundleConsumer):
if t.consume:
note_deprecation(
f"Using consumes({t.name}) doesn't makes sense in this context. "
"This will be an error in a future version of Hypothesis.",
Expand Down
8 changes: 4 additions & 4 deletions requirements/coverage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ exceptiongroup==1.2.2 ; python_version < "3.11"
# pytest
execnet==2.1.1
# via pytest-xdist
fakeredis==2.25.0
fakeredis==2.25.1
# via -r requirements/coverage.in
iniconfig==2.0.0
# via pytest
Expand All @@ -38,7 +38,7 @@ libcst==1.4.0
# via -r requirements/coverage.in
mypy-extensions==1.0.0
# via black
numpy==2.1.1
numpy==2.1.2
# via
# -r requirements/coverage.in
# pandas
Expand Down Expand Up @@ -80,15 +80,15 @@ pytz==2024.2
# pandas
pyyaml==6.0.2
# via libcst
redis==5.1.0
redis==5.1.1
# via fakeredis
six==1.16.0
# via python-dateutil
sortedcontainers==2.4.0
# via
# fakeredis
# hypothesis (hypothesis-python/setup.py)
tomli==2.0.1
tomli==2.0.2
# via
# black
# coverage
Expand Down
13 changes: 7 additions & 6 deletions requirements/fuzzing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ exceptiongroup==1.2.2 ; python_version < "3.11"
# pytest
execnet==2.1.1
# via pytest-xdist
fakeredis==2.25.0
fakeredis==2.25.1
# via -r requirements/coverage.in
flask==3.0.3
# via dash
hypofuzz==24.9.1
# via -r requirements/fuzzing.in
hypothesis[cli]==6.112.1
hypothesis[cli]==6.112.2
# via hypofuzz
idna==3.10
# via requests
Expand Down Expand Up @@ -87,7 +87,7 @@ mypy-extensions==1.0.0
# via black
nest-asyncio==1.6.0
# via dash
numpy==2.1.1
numpy==2.1.2
# via
# -r requirements/coverage.in
# pandas
Expand Down Expand Up @@ -139,15 +139,15 @@ pytz==2024.2
# pandas
pyyaml==6.0.2
# via libcst
redis==5.1.0
redis==5.1.1
# via fakeredis
requests==2.32.3
# via
# dash
# hypofuzz
retrying==1.3.4
# via dash
rich==13.8.1
rich==13.9.2
# via hypothesis
six==1.16.0
# via
Expand All @@ -160,7 +160,7 @@ sortedcontainers==2.4.0
# hypothesis (hypothesis-python/setup.py)
tenacity==9.0.0
# via plotly
tomli==2.0.1
tomli==2.0.2
# via
# black
# coverage
Expand All @@ -171,6 +171,7 @@ typing-extensions==4.12.2
# black
# dash
# fakeredis
# rich
tzdata==2024.2
# via pandas
urllib3==2.2.3
Expand Down
2 changes: 1 addition & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ pytest-xdist==3.6.1
# via -r requirements/test.in
sortedcontainers==2.4.0
# via hypothesis (hypothesis-python/setup.py)
tomli==2.0.1
tomli==2.0.2
# via pytest
22 changes: 12 additions & 10 deletions requirements/tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ importlib-metadata==8.5.0
# twine
iniconfig==2.0.0
# via pytest
ipython==8.27.0
ipython==8.28.0
# via -r requirements/tools.in
isort==5.13.2
# via shed
Expand Down Expand Up @@ -151,7 +151,7 @@ nh3==0.2.18
# via readme-renderer
nodeenv==1.9.1
# via pyright
numpy==2.1.1
numpy==2.1.2
# via -r requirements/tools.in
ordered-set==4.1.0
# via pelican
Expand Down Expand Up @@ -203,11 +203,11 @@ pygments==2.18.0
# sphinx
pyproject-api==1.8.0
# via tox
pyproject-hooks==1.1.0
pyproject-hooks==1.2.0
# via
# build
# pip-tools
pyright==1.1.382.post1
pyright==1.1.383
# via -r requirements/tools.in
pytest==8.3.3
# via -r requirements/tools.in
Expand Down Expand Up @@ -238,11 +238,11 @@ restructuredtext-lint==1.4.0
# via -r requirements/tools.in
rfc3986==2.0.0
# via twine
rich==13.8.1
rich==13.9.2
# via
# pelican
# twine
ruff==0.6.8
ruff==0.6.9
# via -r requirements/tools.in
secretstorage==3.3.3
# via keyring
Expand Down Expand Up @@ -299,7 +299,7 @@ stack-data==0.6.3
# via ipython
tokenize-rt==6.0.0
# via pyupgrade
tomli==2.0.1
tomli==2.0.2
# via
# autoflake
# black
Expand All @@ -310,7 +310,7 @@ tomli==2.0.1
# pytest
# sphinx
# tox
tox==4.20.0
tox==4.21.2
# via -r requirements/tools.in
traitlets==5.14.3
# via
Expand All @@ -324,9 +324,9 @@ types-click==7.1.8
# via -r requirements/tools.in
types-pyopenssl==24.1.0.20240722
# via types-redis
types-pytz==2024.2.0.20240913
types-pytz==2024.2.0.20241003
# via -r requirements/tools.in
types-redis==4.6.0.20240903
types-redis==4.6.0.20241004
# via -r requirements/tools.in
types-setuptools==75.1.0.20240917
# via types-cffi
Expand All @@ -339,6 +339,8 @@ typing-extensions==4.12.2
# ipython
# mypy
# pyright
# rich
# tox
unidecode==1.3.8
# via pelican
urllib3==2.2.3
Expand Down
4 changes: 2 additions & 2 deletions tooling/src/hypothesistooling/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,8 @@ def run_tox(task, version, *args):
"3.9": "3.9.20",
"3.10": "3.10.15",
"3.11": "3.11.10",
"3.12": "3.12.6",
"3.13": "3.13.0rc2",
"3.12": "3.12.7",
"3.13": "3.13.0rc3",
"3.13t": "3.13t-dev",
"3.14": "3.14-dev",
"3.14t": "3.14t-dev",
Expand Down
Loading