Skip to content

Commit 79bfb5d

Browse files
authored
Merge pull request #1666 from Zac-HD/warn-collected-strategies
Warn on strategies collected as tests
2 parents 154a27d + 4c5997f commit 79bfb5d

File tree

4 files changed

+40
-1
lines changed

4 files changed

+40
-1
lines changed

hypothesis-python/RELEASE.rst

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
RELEASE_TYPE: minor
2+
3+
Our pytest plugin now warns you when strategy functions have been collected
4+
as tests, which may happen when e.g. using the
5+
:func:`@composite <hypothesis.strategies.composite>` decorator when you
6+
should be using ``@given(st.data())`` for inline draws.
7+
Such functions *always* pass when treated as tests, because the lazy creation
8+
of strategies mean that the function body is never actually executed!

hypothesis-python/src/hypothesis/extra/pytestplugin.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import pytest
2323

2424
from hypothesis import Verbosity, core, settings
25+
from hypothesis._settings import note_deprecation
2526
from hypothesis.reporting import default as default_reporter
2627
from hypothesis.reporting import with_reporter
2728
from hypothesis.statistics import collector
@@ -183,8 +184,19 @@ def pytest_collection_modifyitems(items):
183184
for item in items:
184185
if not isinstance(item, pytest.Function):
185186
continue
186-
if getattr(item.function, 'is_hypothesis_test', False):
187+
if is_hypothesis_test(item.obj):
187188
item.add_marker('hypothesis')
189+
if getattr(item.obj, 'is_hypothesis_strategy_function', False):
190+
def note_strategy_is_not_test(*args, **kwargs):
191+
note_deprecation(
192+
'%s is a function that returns a Hypothesis strategy, '
193+
'but pytest has collected it as a test function. This '
194+
'is useless as the function body will never be executed.'
195+
'To define a test function, use @given instead of '
196+
'@composite.' % (item.nodeid,)
197+
)
198+
199+
item.obj = note_strategy_is_not_test
188200

189201

190202
def load():

hypothesis-python/src/hypothesis/strategies.py

+2
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ def accept(*args, **kwargs):
196196
result.force_has_reusable_values = True
197197
assert result.has_reusable_values
198198
return result
199+
200+
accept.is_hypothesis_strategy_function = True
199201
return accept
200202
return decorator
201203

hypothesis-python/tests/pytest/test_capture.py

+17
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,20 @@ def test_healthcheck_traceback_is_hidden(testdir):
152152
def_line = get_line_num(def_token, result)
153153
timeout_line = get_line_num(timeout_token, result)
154154
assert timeout_line - def_line == 6
155+
156+
157+
COMPOSITE_IS_NOT_A_TEST = """
158+
from hypothesis.strategies import composite
159+
@composite
160+
def test_data_factory(draw):
161+
assert False, 'Unreachable due to lazy construction'
162+
"""
163+
164+
165+
@pytest.mark.skipif(pytest.__version__[:3] == '3.0', reason='very very old')
166+
def test_deprecation_of_strategies_as_tests(testdir):
167+
script = testdir.makepyfile(COMPOSITE_IS_NOT_A_TEST)
168+
testdir.runpytest(script, '-Werror').assert_outcomes(failed=1)
169+
result = testdir.runpytest(script)
170+
result.assert_outcomes(passed=1)
171+
result.stdout.fnmatch_lines(['*HypothesisDeprecationWarning*'])

0 commit comments

Comments
 (0)