From ee02dc161a480fa0fd7e8d343b901243c6f9bc0c Mon Sep 17 00:00:00 2001 From: Yannick PEROUX Date: Fri, 25 Aug 2017 19:19:11 +0200 Subject: [PATCH] Accept async fixtures in pytest plugin (#2223) --- aiohttp/helpers.py | 7 ++++++ aiohttp/pytest_plugin.py | 48 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index 519b610d339..4ec2faa5037 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -6,6 +6,7 @@ import cgi import datetime import functools +import inspect import os import re import sys @@ -821,3 +822,9 @@ def update_cookies(self, cookies, response_url=None): def filter_cookies(self, request_url): return None + + +def isasyncgenfunction(obj): + if hasattr(inspect, 'isasyncgenfunction'): + return inspect.isasyncgenfunction(obj) + return False diff --git a/aiohttp/pytest_plugin.py b/aiohttp/pytest_plugin.py index ff1e23575a2..b79151124f7 100644 --- a/aiohttp/pytest_plugin.py +++ b/aiohttp/pytest_plugin.py @@ -7,6 +7,7 @@ import pytest from py import path +from aiohttp.helpers import isasyncgenfunction from aiohttp.web import Application from .test_utils import unused_port as _unused_port @@ -37,6 +38,53 @@ def pytest_addoption(parser): help='enable event loop debug mode') +def pytest_fixture_setup(fixturedef, request): + """ + Allow fixtures to be coroutines. Run coroutine fixtures in an event loop. + """ + func = fixturedef.func + + if isasyncgenfunction(func): + # async generator fixture + is_async_gen = True + elif asyncio.iscoroutinefunction(func): + # regular async fixture + is_async_gen = False + else: + # not an async fixture, nothing to do + return + + # if neither the fixture nor the test use the 'loop' fixture, + # 'getfixturevalue' will fail because the test is not parameterized + # (this can be removed someday if 'loop' is no longer parameterized) + if 'loop' not in request.fixturenames: + raise Exception( + "Asynchronous fixtures must depend on the 'loop' fixture or be " + "used in tests depending from it." + ) + + def wrapper(*args, **kwargs): + _loop = request.getfixturevalue('loop') + + if is_async_gen: + # for async generators, we need to advance the generator once, + # then advance it again in a finalizer + gen = func(*args, **kwargs) + + def finalizer(): + try: + return _loop.run_until_complete(gen.__anext__()) + except StopAsyncIteration: + pass + + request.addfinalizer(finalizer) + return _loop.run_until_complete(gen.__anext__()) + else: + return _loop.run_until_complete(func(*args, **kwargs)) + + fixturedef.func = wrapper + + @pytest.fixture def fast(request): """ --fast config option """