Skip to content

Commit 0016ad0

Browse files
authored
Merge pull request #4695 from HypothesisWorks/DRMacIver/syrupy
Add snapshot testing with syrupy
2 parents 86de995 + c43f475 commit 0016ad0

23 files changed

Lines changed: 1120 additions & 55 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ jobs:
278278
run: |
279279
pip install --upgrade setuptools pip wheel build
280280
python -m build --wheel hypothesis-python --outdir dist/
281-
pip download --dest=dist/ hypothesis-python/ pytest tzdata # fetch all the wheels
281+
pip download --dest=dist/ hypothesis-python/ pytest tzdata syrupy # fetch all the wheels
282282
rm dist/packaging-*.whl # fails with `invalid metadata entry 'name'`
283283
pyodide venv .venv-pyodide
284284
source .venv-pyodide/bin/activate

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+
This release improves |Phase.explain| output by excluding pytest-related :pypi:`syrupy` files as a possible source of variation.

hypothesis-python/src/hypothesis/internal/scrutineer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ def __exit__(self, *args, **kwargs):
167167
"/typing.py",
168168
"/conftest.py",
169169
"/pprint.py",
170+
# syrupy registers a pytest_assertrepr_compare hook, which only runs when
171+
# assertions fail — making it appear as always-failing-never-passing.
172+
"/syrupy/__init__.py",
170173
)
171174

172175

hypothesis-python/tests/common/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,9 @@ def wait_for(condition, *, timeout=1, interval=0.01):
333333
f"timing out after waiting {timeout}s for condition "
334334
f"{get_pretty_function_description(condition)}"
335335
)
336+
337+
338+
def run_test_for_falsifying_example(test_fn):
339+
with pytest.raises(AssertionError) as err:
340+
test_fn()
341+
return "\n".join(err.value.__notes__).strip()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# serializer version: 1
2+
# name: test_map_to_bytes_prints_as_repr
3+
'''
4+
Falsifying example: inner(
5+
b=b"\xe3\xb0\xc4B\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99o\xb9$'\xaeA\xe4d\x9b\x93L\xa4\x95\x99\x1bxR\xb8U",
6+
)
7+
'''
8+
# ---
9+
# name: test_map_to_str_prints_as_repr
10+
'''
11+
Falsifying example: inner(
12+
s='0',
13+
)
14+
'''
15+
# ---
16+
# name: test_reprs_as_created
17+
'''
18+
Falsifying example: inner(
19+
foo=Foo(x=1),
20+
bar=Bar(x=-1),
21+
baz=Foo(None),
22+
)
23+
'''
24+
# ---
25+
# name: test_reprs_as_created_consistent_calls_despite_indentation
26+
'''
27+
Falsifying example: inner(
28+
a=some_foo('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
29+
b=Bar(
30+
some_foo(
31+
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
32+
),
33+
),
34+
)
35+
'''
36+
# ---
37+
# name: test_reprs_as_created_interactive
38+
'''
39+
Falsifying example: inner(
40+
data=data(...),
41+
)
42+
Draw 1: Bar(10)
43+
'''
44+
# ---

hypothesis-python/tests/cover/test_custom_reprs.py

Lines changed: 16 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
from hypothesis.strategies._internal.lazy import unwrap_strategies
1818

1919

20+
def _get_output(test_fn):
21+
with pytest.raises(AssertionError) as err:
22+
test_fn()
23+
return "\n".join(err.value.__notes__).strip()
24+
25+
2026
def test_includes_non_default_args_in_repr():
2127
assert repr(st.integers()) == "integers()"
2228
assert repr(st.integers(min_value=1)) == "integers(min_value=1)"
@@ -79,41 +85,24 @@ class Bar(Foo):
7985
pass
8086

8187

82-
def test_reprs_as_created():
88+
def test_reprs_as_created(snapshot):
8389
@given(foo=st.builds(Foo), bar=st.from_type(Bar), baz=st.none().map(Foo))
8490
@settings(print_blob=False, max_examples=10_000, derandomize=True)
8591
def inner(foo, bar, baz):
8692
assert baz.x is None
8793
assert foo.x <= 0 or bar.x >= 0
8894

89-
with pytest.raises(AssertionError) as err:
90-
inner()
91-
expected = """
92-
Falsifying example: inner(
93-
foo=Foo(x=1),
94-
bar=Bar(x=-1),
95-
baz=Foo(None),
96-
)
97-
"""
98-
assert "\n".join(err.value.__notes__).strip() == expected.strip()
95+
assert _get_output(inner) == snapshot
9996

10097

101-
def test_reprs_as_created_interactive():
98+
def test_reprs_as_created_interactive(snapshot):
10299
@given(st.data())
103100
@settings(print_blob=False, max_examples=10_000)
104101
def inner(data):
105102
bar = data.draw(st.builds(Bar, st.just(10)))
106103
assert not bar.x
107104

108-
with pytest.raises(AssertionError) as err:
109-
inner()
110-
expected = """
111-
Falsifying example: inner(
112-
data=data(...),
113-
)
114-
Draw 1: Bar(10)
115-
"""
116-
assert "\n".join(err.value.__notes__).strip() == expected.strip()
105+
assert _get_output(inner) == snapshot
117106

118107

119108
CONSTANT_FOO = Foo(None)
@@ -143,7 +132,7 @@ def inner(a, b):
143132
assert re.fullmatch(expected_re, got), got
144133

145134

146-
def test_reprs_as_created_consistent_calls_despite_indentation():
135+
def test_reprs_as_created_consistent_calls_despite_indentation(snapshot):
147136
aas = "a" * 60
148137
strat = st.builds(some_foo, st.just(aas))
149138

@@ -154,19 +143,7 @@ def test_reprs_as_created_consistent_calls_despite_indentation():
154143
def inner(a, b):
155144
assert a == b
156145

157-
with pytest.raises(AssertionError) as err:
158-
inner()
159-
expected = f"""
160-
Falsifying example: inner(
161-
a=some_foo({aas!r}),
162-
b=Bar(
163-
some_foo(
164-
{aas!r},
165-
),
166-
),
167-
)
168-
"""
169-
assert "\n".join(err.value.__notes__).strip() == expected.strip()
146+
assert _get_output(inner) == snapshot
170147

171148

172149
@pytest.mark.parametrize(
@@ -199,33 +176,19 @@ def test_characters_repr(strategy, expected_repr):
199176
assert repr(unwrap_strategies(strategy)) == expected_repr
200177

201178

202-
def test_map_to_str_prints_as_repr():
179+
def test_map_to_str_prints_as_repr(snapshot):
203180
@given(s=st.integers().map(str))
204181
@settings(phases=[Phase.generate, Phase.shrink], print_blob=False)
205182
def inner(s):
206183
raise AssertionError
207184

208-
with pytest.raises(AssertionError) as err:
209-
inner()
210-
expected = """
211-
Falsifying example: inner(
212-
s='0',
213-
)
214-
"""
215-
assert "\n".join(err.value.__notes__).strip() == expected.strip()
185+
assert _get_output(inner) == snapshot
216186

217187

218-
def test_map_to_bytes_prints_as_repr():
188+
def test_map_to_bytes_prints_as_repr(snapshot):
219189
@given(b=st.binary().map(lambda b: hashlib.sha256(b).digest()))
220190
@settings(phases=[Phase.generate, Phase.shrink], print_blob=False)
221191
def inner(b):
222192
raise AssertionError
223193

224-
with pytest.raises(AssertionError) as err:
225-
inner()
226-
expected = f"""
227-
Falsifying example: inner(
228-
b={hashlib.sha256(b"").digest()!r},
229-
)
230-
"""
231-
assert "\n".join(err.value.__notes__).strip() == expected.strip()
194+
assert _get_output(inner) == snapshot
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# This file is part of Hypothesis, which may be found at
2+
# https://github.com/HypothesisWorks/hypothesis/
3+
#
4+
# Copyright the Hypothesis Authors.
5+
# Individual contributors are listed in AUTHORS.rst and the git log.
6+
#
7+
# This Source Code Form is subject to the terms of the Mozilla Public License,
8+
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9+
# obtain one at https://mozilla.org/MPL/2.0/.

0 commit comments

Comments
 (0)