Skip to content

Commit a8d22d3

Browse files
authored
Merge pull request #34 from touilleMan/async-gen-fixture-crash-teardown
Add test_async_yield_fixture_crashed_teardown_allow_other_teardowns
2 parents 9264913 + 21b5625 commit a8d22d3

File tree

3 files changed

+132
-22
lines changed

3 files changed

+132
-22
lines changed

pytest_trio/_tests/test_async_yield_fixture.py

+50
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,53 @@ async def test_actual_test(server):
261261
result = testdir.runpytest()
262262

263263
result.assert_outcomes(passed=1)
264+
265+
266+
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6")
267+
def test_async_yield_fixture_crashed_teardown_allow_other_teardowns(testdir):
268+
269+
testdir.makepyfile(
270+
"""
271+
import pytest
272+
import trio
273+
274+
events = []
275+
276+
@pytest.fixture
277+
async def good_fixture():
278+
async with trio.open_nursery() as nursery:
279+
events.append('good_fixture setup')
280+
yield
281+
events.append('good_fixture teardown')
282+
283+
@pytest.fixture
284+
async def bad_fixture():
285+
async with trio.open_nursery() as nursery:
286+
events.append('bad_fixture setup')
287+
yield
288+
events.append('bad_fixture teardown')
289+
raise RuntimeError('Crash during fixture teardown')
290+
291+
def test_before():
292+
assert not events
293+
294+
@pytest.mark.trio
295+
async def test_actual_test(bad_fixture, good_fixture):
296+
pass
297+
298+
def test_after():
299+
assert events == [
300+
'good_fixture setup',
301+
'bad_fixture setup',
302+
'bad_fixture teardown',
303+
'good_fixture teardown',
304+
]
305+
"""
306+
)
307+
308+
result = testdir.runpytest()
309+
310+
result.assert_outcomes(failed=1, passed=2)
311+
result.stdout.re_match_lines(
312+
[r'E\W+RuntimeError: Crash during fixture teardown']
313+
)

pytest_trio/_tests/test_sync_fixture.py

+51
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,54 @@ def test_after():
8787
result = testdir.runpytest()
8888

8989
result.assert_outcomes(passed=3)
90+
91+
92+
def test_sync_yield_fixture_crashed_teardown_allow_other_teardowns(testdir):
93+
94+
testdir.makepyfile(
95+
"""
96+
import pytest
97+
import trio
98+
99+
events = []
100+
101+
@pytest.fixture
102+
async def force_async_fixture():
103+
pass
104+
105+
@pytest.fixture
106+
def good_fixture(force_async_fixture):
107+
events.append('good_fixture setup')
108+
yield
109+
events.append('good_fixture teardown')
110+
111+
@pytest.fixture
112+
def bad_fixture(force_async_fixture):
113+
events.append('bad_fixture setup')
114+
yield
115+
events.append('bad_fixture teardown')
116+
raise RuntimeError('Crash during fixture teardown')
117+
118+
def test_before():
119+
assert not events
120+
121+
@pytest.mark.trio
122+
async def test_actual_test(bad_fixture, good_fixture):
123+
pass
124+
125+
def test_after():
126+
assert events == [
127+
'good_fixture setup',
128+
'bad_fixture setup',
129+
'bad_fixture teardown',
130+
'good_fixture teardown',
131+
]
132+
"""
133+
)
134+
135+
result = testdir.runpytest()
136+
137+
result.assert_outcomes(failed=1, passed=2)
138+
result.stdout.re_match_lines(
139+
[r'E\W+RuntimeError: Crash during fixture teardown']
140+
)

pytest_trio/plugin.py

+31-22
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
"""pytest-trio implementation."""
2+
import sys
23
from traceback import format_exception
34
from inspect import iscoroutinefunction, isgeneratorfunction
4-
try:
5-
from inspect import isasyncgenfunction
6-
except ImportError:
7-
# `inspect.isasyncgenfunction` not available with Python<3.6
8-
def isasyncgenfunction(x):
9-
return False
10-
11-
125
import pytest
136
import trio
147
from trio._util import acontextmanager
158
from trio.testing import MockClock, trio_test
169
from async_generator import async_generator, yield_
1710

11+
if sys.version_info >= (3, 6):
12+
from inspect import isasyncgenfunction
13+
ORDERED_DICTS = True
14+
else:
15+
# `inspect.isasyncgenfunction` not available with Python<3.6
16+
def isasyncgenfunction(x):
17+
return False
18+
19+
# Ordered dict (and **kwargs) not available with Python<3.6
20+
ORDERED_DICTS = False
21+
1822

1923
def pytest_configure(config):
2024
"""Inject documentation."""
@@ -69,6 +73,9 @@ async def _setup_async_fixtures_in(deps):
6973
need_resolved_deps_stack = [
7074
(k, v) for k, v in deps.items() if isinstance(v, BaseAsyncFixture)
7175
]
76+
if not ORDERED_DICTS:
77+
# Make the fixture resolution order determinist
78+
need_resolved_deps_stack = sorted(need_resolved_deps_stack)
7279

7380
if not need_resolved_deps_stack:
7481
await yield_(deps)
@@ -134,14 +141,15 @@ async def _setup(self, resolved_deps):
134141
__tracebackhide__ = True
135142
agen = self.fixturedef.func(**resolved_deps)
136143

137-
await yield_(await agen.asend(None))
138-
139144
try:
140-
await agen.asend(None)
141-
except StopAsyncIteration:
142-
pass
143-
else:
144-
raise RuntimeError('Only one yield in fixture is allowed')
145+
await yield_(await agen.asend(None))
146+
finally:
147+
try:
148+
await agen.asend(None)
149+
except StopAsyncIteration:
150+
pass
151+
else:
152+
raise RuntimeError('Only one yield in fixture is allowed')
145153

146154

147155
class SyncFixtureWithAsyncDeps(BaseAsyncFixture):
@@ -167,14 +175,15 @@ async def _setup(self, resolved_deps):
167175
__tracebackhide__ = True
168176
gen = self.fixturedef.func(**resolved_deps)
169177

170-
await yield_(gen.send(None))
171-
172178
try:
173-
gen.send(None)
174-
except StopIteration:
175-
pass
176-
else:
177-
raise RuntimeError('Only one yield in fixture is allowed')
179+
await yield_(gen.send(None))
180+
finally:
181+
try:
182+
gen.send(None)
183+
except StopIteration:
184+
pass
185+
else:
186+
raise RuntimeError('Only one yield in fixture is allowed')
178187

179188

180189
class AsyncFixture(BaseAsyncFixture):

0 commit comments

Comments
 (0)