Skip to content

Commit f97e900

Browse files
alblascoTinche
authored andcommitted
1) Test case (test_async_fixtures_with_finalizer) refactoring to pass on python 3.6 & 3.5
2) An additional test case (test_module_with_get_event_loop_finalizer). Refactoring previous test case I got to another potential issue: Finalizers using get_event_loop() instead of event_loop [on a module scope fixture] To be able to pass this new test case I needed some additional changes on next event_loop fixture block( https://github.com/pytest-dev/pytest-asyncio/blob/v0.12.0/pytest_asyncio/plugin.py#L54-L66): - This block needs to apply on all event_loop fixture scopes (so I remove 'asyncio' in request.keywords, that only apply to function scope) - The get_event_loop() method inside this block has following side effects (See https://github.com/python/cpython/blob/3.8/Lib/asyncio/events.py#L636): - As no loop is still set, then L636 is executed, and so a new event_loop (let's call it L2) is first created and then set. - And then finalizer will restore L2 at the end of test. What has no sense, to restore L2 that wasn't before the test. And additionally this L2 is never closed. So it seeems that get_event_loop() should be removed, and so I see no reasons for any finalizer. 3) DRY: New FixtureStripper to avoid repeating code
1 parent c1131f8 commit f97e900

File tree

3 files changed

+86
-67
lines changed

3 files changed

+86
-67
lines changed

pytest_asyncio/plugin.py

+35-29
Original file line numberDiff line numberDiff line change
@@ -48,43 +48,53 @@ def pytest_pycollect_makeitem(collector, name, obj):
4848
return list(collector._genfunctions(name, obj))
4949

5050

51+
class FixtureStripper:
52+
"""Include additional Fixture, and then strip them"""
53+
REQUEST = "request"
54+
EVENT_LOOP = "event_loop"
55+
56+
def __init__(self, fixturedef):
57+
self.fixturedef = fixturedef
58+
self.to_strip = set()
59+
60+
def add(self, name):
61+
"""Add fixture name to fixturedef
62+
and record in to_strip list (If not previously included)"""
63+
if name in self.fixturedef.argnames:
64+
return
65+
self.fixturedef.argnames += (name, )
66+
self.to_strip.add(name)
67+
68+
def get_and_strip_from(self, name, data_dict):
69+
"""Strip name from data, and return value"""
70+
result = data_dict[name]
71+
if name in self.to_strip:
72+
del data_dict[name]
73+
return result
74+
75+
5176
@pytest.hookimpl(hookwrapper=True)
5277
def pytest_fixture_setup(fixturedef, request):
5378
"""Adjust the event loop policy when an event loop is produced."""
54-
if fixturedef.argname == "event_loop" and 'asyncio' in request.keywords:
79+
if fixturedef.argname == "event_loop":
5580
outcome = yield
5681
loop = outcome.get_result()
5782
policy = asyncio.get_event_loop_policy()
58-
try:
59-
old_loop = policy.get_event_loop()
60-
except RuntimeError as exc:
61-
if 'no current event loop' not in str(exc):
62-
raise
63-
old_loop = None
6483
policy.set_event_loop(loop)
65-
fixturedef.addfinalizer(lambda: policy.set_event_loop(old_loop))
6684
return
6785

6886
if isasyncgenfunction(fixturedef.func):
6987
# This is an async generator function. Wrap it accordingly.
7088
generator = fixturedef.func
7189

72-
strip_event_loop = False
73-
if 'event_loop' not in fixturedef.argnames:
74-
fixturedef.argnames += ('event_loop', )
75-
strip_event_loop = True
76-
strip_request = False
77-
if 'request' not in fixturedef.argnames:
78-
fixturedef.argnames += ('request', )
79-
strip_request = True
90+
fixture_stripper = FixtureStripper(fixturedef)
91+
fixture_stripper.add(FixtureStripper.EVENT_LOOP)
92+
fixture_stripper.add(FixtureStripper.REQUEST)
93+
8094

8195
def wrapper(*args, **kwargs):
82-
loop = kwargs['event_loop']
83-
request = kwargs['request']
84-
if strip_event_loop:
85-
del kwargs['event_loop']
86-
if strip_request:
87-
del kwargs['request']
96+
loop = fixture_stripper.get_and_strip_from(FixtureStripper.EVENT_LOOP, kwargs)
97+
request = fixture_stripper.get_and_strip_from(FixtureStripper.REQUEST, kwargs)
8898

8999
gen_obj = generator(*args, **kwargs)
90100

@@ -112,15 +122,11 @@ async def async_finalizer():
112122
elif inspect.iscoroutinefunction(fixturedef.func):
113123
coro = fixturedef.func
114124

115-
strip_event_loop = False
116-
if 'event_loop' not in fixturedef.argnames:
117-
fixturedef.argnames += ('event_loop', )
118-
strip_event_loop = True
125+
fixture_stripper = FixtureStripper(fixturedef)
126+
fixture_stripper.add(FixtureStripper.EVENT_LOOP)
119127

120128
def wrapper(*args, **kwargs):
121-
loop = kwargs['event_loop']
122-
if strip_event_loop:
123-
del kwargs['event_loop']
129+
loop = fixture_stripper.get_and_strip_from(FixtureStripper.EVENT_LOOP, kwargs)
124130

125131
async def setup():
126132
res = await coro(*args, **kwargs)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import asyncio
2+
import contextlib
3+
import functools
4+
import pytest
5+
6+
7+
@pytest.mark.asyncio
8+
async def test_module_with_event_loop_finalizer(port1):
9+
await asyncio.sleep(0.01)
10+
assert port1
11+
12+
@pytest.mark.asyncio
13+
async def test_module_with_get_event_loop_finalizer(port2):
14+
await asyncio.sleep(0.01)
15+
assert port2
16+
17+
@pytest.fixture(scope="module")
18+
def event_loop():
19+
"""Change event_loop fixture to module level."""
20+
policy = asyncio.get_event_loop_policy()
21+
loop = policy.new_event_loop()
22+
yield loop
23+
loop.close()
24+
25+
26+
@pytest.fixture(scope="module")
27+
async def port1(request, event_loop):
28+
def port_finalizer(finalizer):
29+
async def port_afinalizer():
30+
# await task inside get_event_loop()
31+
# RantimeError is raised if task is created on a different loop
32+
await finalizer
33+
event_loop.run_until_complete(port_afinalizer())
34+
35+
worker = asyncio.create_task(asyncio.sleep(0.2))
36+
request.addfinalizer(functools.partial(port_finalizer, worker))
37+
return True
38+
39+
40+
@pytest.fixture(scope="module")
41+
async def port2(request, event_loop):
42+
def port_finalizer(finalizer):
43+
async def port_afinalizer():
44+
# await task inside get_event_loop()
45+
# if loop is different a RuntimeError is raised
46+
await finalizer
47+
asyncio.get_event_loop().run_until_complete(port_afinalizer())
48+
49+
worker = asyncio.create_task(asyncio.sleep(0.2))
50+
request.addfinalizer(functools.partial(port_finalizer, worker))
51+
return True

tests/async_fixtures/test_async_fixtures_with_finalizer_scope.py

-38
This file was deleted.

0 commit comments

Comments
 (0)