Skip to content

Commit b9d94da

Browse files
committed
Implement pytestmark.
1 parent da262ec commit b9d94da

File tree

7 files changed

+182
-8
lines changed

7 files changed

+182
-8
lines changed

README.rst

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ a coroutine. You'll need to interact with the event loop directly, using methods
6969
like ``event_loop.run_until_complete``. See the ``pytest.mark.asyncio`` marker
7070
for treating test functions like coroutines.
7171

72+
Simply using this fixture will not set the generated event loop as the
73+
default asyncio event loop, or change the asyncio event loop policy in any way.
74+
Use ``pytest.mark.asyncio`` for this purpose.
75+
7276
.. code-block:: python
7377
7478
def test_http_client(event_loop):
@@ -89,11 +93,12 @@ event loop. This will take effect even if you're using the
8993
yield loop
9094
loop.close()
9195
92-
A special pytest hook will ensure the produced loop is either set as the
93-
default global loop, or a special, error-throwing event loop policy is installed
94-
as the default policy (depending on the ``forbid_global_loop`` parameter).
95-
Fixtures depending on the ``event_loop`` fixture can expect the policy to be
96-
properly modified when they run.
96+
If the ``pytest.mark.asyncio`` marker is applied, a pytest hook will
97+
ensure the produced loop is either set as the default global loop, or a special,
98+
error-throwing event loop policy is installed as the default policy (depending
99+
on the ``forbid_global_loop`` parameter). Fixtures depending on the
100+
``event_loop`` fixture can expect the policy to be properly modified when they
101+
run.
97102

98103
``event_loop_process_pool``
99104
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -132,6 +137,26 @@ The event loop used can be overriden by overriding the ``event_loop`` fixture
132137
If ``forbid_global_loop`` is true, ``asyncio.get_event_loop()`` will result
133138
in exceptions, ensuring your tests are always passing the event loop explicitly.
134139

140+
In order to make your test code a little more concise, the pytest |pytestmark|_
141+
feature can be used to mark entire modules or classes with this marker.
142+
Only test coroutines will be affected (by default, coroutines prefixed by
143+
``test_``), so, for example, fixtures are safe to define.
144+
145+
.. code-block:: python
146+
147+
import asyncio
148+
import pytest
149+
150+
# All test coroutines will be treated as marked.
151+
pytestmark = pytest.mark.asyncio(forbid_global_loop=True)
152+
153+
async def test_example(event_loop):
154+
"""No marker!"""
155+
await asyncio.sleep(0, loop=event_loop)
156+
157+
.. |pytestmark| replace:: ``pytestmark``
158+
.. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules
159+
135160
``pytest.mark.asyncio_process_pool(forbid_global_loop=False)``
136161
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
137162
The ``asyncio_process_pool`` marker is almost identical to the ``asyncio``
@@ -141,6 +166,10 @@ marker, except the event loop used will have a
141166
Changelog
142167
---------
143168

169+
0.6.0 (UNRELEASED)
170+
~~~~~~~~~~~~~~~~~~
171+
- ``pytestmark`` now works on both module and class level.
172+
144173
0.5.0 (2016-09-07)
145174
~~~~~~~~~~~~~~~~~~
146175
- Introduced a changelog.

pytest_asyncio/plugin.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import asyncio
2-
from concurrent.futures import ProcessPoolExecutor
3-
from contextlib import closing
42
import inspect
53
import socket
4+
5+
from concurrent.futures import ProcessPoolExecutor
6+
from contextlib import closing
7+
68
import pytest
79

10+
from _pytest.python import transfer_markers
11+
812

913
class ForbiddenEventLoopPolicy(asyncio.AbstractEventLoopPolicy):
1014
"""An event loop policy that raises errors on any operation."""
@@ -30,10 +34,18 @@ def pytest_configure(config):
3034

3135
@pytest.mark.tryfirst
3236
def pytest_pycollect_makeitem(collector, name, obj):
37+
"""A pytest hook to collect asyncio coroutines."""
3338
if collector.funcnamefilter(name) and _is_coroutine(obj):
3439
item = pytest.Function(name, parent=collector)
40+
41+
# Due to how pytest test collection works, module-level pytestmarks
42+
# are applied after the collection step. Since this is the collection
43+
# step, we look ourselves.
44+
transfer_markers(obj, item.cls, item.module)
45+
item = pytest.Function(name, parent=collector) # To reload keywords.
46+
3547
if ('asyncio' in item.keywords or
36-
'asyncio_process_pool' in item.keywords):
48+
'asyncio_process_pool' in item.keywords):
3749
return list(collector._genfunctions(name, obj))
3850

3951

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
collect_ignore = []
77
if sys.version_info[:2] < (3, 5):
88
collect_ignore.append("test_simple_35.py")
9+
collect_ignore.append("markers/test_class_marker_35.py")
10+
collect_ignore.append("markers/test_module_marker_35.py")
911

1012

1113
@pytest.yield_fixture()

tests/markers/test_class_marker.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Test if pytestmark works when defined on a class."""
2+
import asyncio
3+
import pytest
4+
5+
6+
class TestPyTestMark:
7+
pytestmark = pytest.mark.asyncio(forbid_global_loop=True)
8+
9+
@asyncio.coroutine
10+
def test_no_global_loop(self, event_loop, sample_fixture):
11+
with pytest.raises(NotImplementedError):
12+
asyncio.get_event_loop()
13+
counter = 1
14+
15+
@asyncio.coroutine
16+
def inc():
17+
nonlocal counter
18+
counter += 1
19+
yield from asyncio.sleep(0, loop=event_loop)
20+
yield from asyncio.ensure_future(inc(), loop=event_loop)
21+
assert counter == 2
22+
23+
24+
@pytest.fixture
25+
def sample_fixture():
26+
return None

tests/markers/test_class_marker_35.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Test if pytestmark works when defined on a class."""
2+
import asyncio
3+
import pytest
4+
5+
6+
class TestPyTestMark:
7+
pytestmark = pytest.mark.asyncio(forbid_global_loop=True)
8+
9+
async def test_no_global_loop(self, event_loop, sample_fixture):
10+
with pytest.raises(NotImplementedError):
11+
asyncio.get_event_loop()
12+
counter = 1
13+
async def inc():
14+
nonlocal counter
15+
counter += 1
16+
await asyncio.sleep(0, loop=event_loop)
17+
await asyncio.ensure_future(inc(), loop=event_loop)
18+
assert counter == 2
19+
20+
21+
@pytest.fixture
22+
def sample_fixture():
23+
return None

tests/markers/test_module_marker.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Test if pytestmark works when defined in a module.
2+
3+
Pre-3.5 version.
4+
"""
5+
import asyncio
6+
7+
import pytest
8+
9+
pytestmark = pytest.mark.asyncio(forbid_global_loop=True)
10+
11+
12+
class TestPyTestMark:
13+
@asyncio.coroutine
14+
def test_no_global_loop_method(self, event_loop, sample_fixture):
15+
with pytest.raises(NotImplementedError):
16+
asyncio.get_event_loop()
17+
counter = 1
18+
19+
@asyncio.coroutine
20+
def inc():
21+
nonlocal counter
22+
counter += 1
23+
yield from asyncio.sleep(0, loop=event_loop)
24+
25+
yield from asyncio.async(inc(), loop=event_loop)
26+
assert counter == 2
27+
28+
@asyncio.coroutine
29+
def test_no_global_loop_coroutine(event_loop, sample_fixture):
30+
with pytest.raises(NotImplementedError):
31+
asyncio.get_event_loop()
32+
counter = 1
33+
34+
@asyncio.coroutine
35+
def inc():
36+
nonlocal counter
37+
counter += 1
38+
yield from asyncio.sleep(0, loop=event_loop)
39+
40+
yield from asyncio.async(inc(), loop=event_loop)
41+
assert counter == 2
42+
43+
44+
@pytest.fixture
45+
def sample_fixture():
46+
return None
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Test if pytestmark works when defined in a module."""
2+
import asyncio
3+
4+
import pytest
5+
6+
pytestmark = pytest.mark.asyncio(forbid_global_loop=True)
7+
8+
9+
class TestPyTestMark:
10+
async def test_no_global_loop_method(self, event_loop, sample_fixture):
11+
with pytest.raises(NotImplementedError):
12+
asyncio.get_event_loop()
13+
14+
counter = 1
15+
async def inc():
16+
nonlocal counter
17+
counter += 1
18+
await asyncio.sleep(0, loop=event_loop)
19+
await asyncio.ensure_future(inc(), loop=event_loop)
20+
assert counter == 2
21+
22+
async def test_no_global_loop_coroutine(event_loop, sample_fixture):
23+
with pytest.raises(NotImplementedError):
24+
asyncio.get_event_loop()
25+
counter = 1
26+
async def inc():
27+
nonlocal counter
28+
counter += 1
29+
await asyncio.sleep(0, loop=event_loop)
30+
await asyncio.ensure_future(inc(), loop=event_loop)
31+
assert counter == 2
32+
33+
34+
@pytest.fixture
35+
def sample_fixture():
36+
return None

0 commit comments

Comments
 (0)