Skip to content

Commit 8a0779f

Browse files
authored
Merge pull request #51 from tybug/stateful
Initial support for stateful testing
2 parents 412015d + c2877cb commit 8a0779f

File tree

4 files changed

+57
-8
lines changed

4 files changed

+57
-8
lines changed

docs-src/changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
HypoFuzz uses [calendar-based versioning](https://calver.org/), with a
33
`YY-MM-patch` format.
44

5+
## 25.02.2
6+
7+
Initial support for [stateful tests](https://hypothesis.readthedocs.io/en/latest/stateful.html).
8+
59
## 25.02.1
610

711
Use a new mutator based on the typed choice sequence (https://github.com/HypothesisWorks/hypothesis/issues/3921), bringing back compatibility with new Hypothesis versions.

src/hypofuzz/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
"""Adaptive fuzzing for property-based tests using Hypothesis."""
22

3-
__version__ = "25.02.1"
3+
__version__ = "25.02.2"
44
__all__: list = []

src/hypofuzz/interface.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@
44
import sys
55
from collections.abc import Iterable
66
from contextlib import redirect_stdout
7-
from functools import partial
87
from inspect import signature
98
from typing import TYPE_CHECKING, get_type_hints
109

1110
import pytest
1211
from _pytest.nodes import Item, Node
1312
from _pytest.skipping import evaluate_condition
14-
from hypothesis.stateful import RuleBasedStateMachine, run_state_machine_as_test
13+
from hypothesis.stateful import get_state_machine_test
14+
from packaging import version
1515

1616
if TYPE_CHECKING:
1717
# We have to defer imports to within functions here, because this module
1818
# is a Hypothesis entry point and is thus imported earlier than the others.
1919
from .hy import FuzzProcess
2020

21+
pytest8 = version.parse(pytest.__version__) >= version.parse("8.0.0")
22+
2123

2224
def has_true_skipif(item: Item) -> bool:
2325
# multiple @skipif decorators are treated as an OR.
@@ -77,6 +79,7 @@ def pytest_collection_finish(self, session: pytest.Session) -> None:
7779
# values directly, so we can pass them as extra kwargs to FuzzProcess.
7880
params = item.callspec.params if hasattr(item, "callspec") else {}
7981
param_names = set(params)
82+
extra_kw = params
8083

8184
# Skip any test which:
8285
# - directly requests a non autouse fixture, or
@@ -101,16 +104,32 @@ def pytest_collection_finish(self, session: pytest.Session) -> None:
101104
flush=True,
102105
)
103106
continue
104-
105107
# Wrap it up in a FuzzTarget and we're done!
106108
try:
107-
# Skip state-machine classes, since they're not
108-
if isinstance(item.obj, RuleBasedStateMachine.TestCase):
109-
target = partial(run_state_machine_as_test, item.obj)
109+
if hasattr(item.obj, "_hypothesis_state_machine_class"):
110+
assert (
111+
extra_kw == {}
112+
), "Not possible for RuleBasedStateMachine.TestCase to be parametrized"
113+
runTest = item.obj
114+
StateMachineClass = runTest._hypothesis_state_machine_class
115+
target = get_state_machine_test( # type: ignore
116+
StateMachineClass,
117+
# runTest is a function, not a bound method, under pyest7.
118+
# I wonder if something about TestCase instantiation order
119+
# changed in pytest 8? Either way, we can't access
120+
# __self__.settings under pytest 7.
121+
#
122+
# I am going to call this an acceptably rare bug for now,
123+
# because it should only manifest if the user sets a custom
124+
# database on a stateful test under pytest 7 (all non-db
125+
# settings are ignored by hypofuzz).
126+
settings=runTest.__self__.settings if pytest8 else None,
127+
)
128+
extra_kw = {"factory": StateMachineClass}
110129
else:
111130
target = item.obj
112131
fuzz = FuzzProcess.from_hypothesis_test(
113-
target, nodeid=item.nodeid, extra_kw=params
132+
target, nodeid=item.nodeid, extra_kw=extra_kw
114133
)
115134
self.fuzz_targets.append(fuzz)
116135
except Exception as err:

tests/test_collection.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def collect(code: str) -> list[FuzzProcess]:
1616
"""
1717
import pytest
1818
from hypothesis import given, strategies as st
19+
from hypothesis.stateful import RuleBasedStateMachine, Bundle, initialize, rule
1920
"""
2021
)
2122
+ "\n"
@@ -152,3 +153,28 @@ def test_a(n):
152153
pass
153154
"""
154155
assert not collect_names(code)
156+
157+
158+
def test_collects_stateful_test():
159+
code = """
160+
names = st.text(min_size=1).filter(lambda x: "/" not in x)
161+
162+
class NumberModifier(RuleBasedStateMachine):
163+
folders = Bundle("folders")
164+
files = Bundle("files")
165+
166+
@initialize(target=folders)
167+
def init_folders(self):
168+
return "/"
169+
170+
@rule(target=folders, parent=folders, name=names)
171+
def create_folder(self, parent, name):
172+
return f"{parent}/{name}"
173+
174+
@rule(target=files, parent=folders, name=names)
175+
def create_file(self, parent, name):
176+
return f"{parent}/{name}"
177+
178+
NumberModifierTest = NumberModifier.TestCase
179+
"""
180+
assert collect_names(code) == {"run_state_machine"}

0 commit comments

Comments
 (0)