Skip to content

Commit 8cd327c

Browse files
tests: switch from pytest.fixture to pytest_asyncio.fixture for async code
* Closes https://github.com/cylc/cylc-flow/issues/5995t * Asynchronous fixtures should use `pytest_asyncio.fixture` rather than `pytest.fixture` when attempting to override the default event loop scope. * In practice, this has to be done whenever the fixture scope is not "function" (which is the pytest_asyncio default).
1 parent 3a7481f commit 8cd327c

28 files changed

+124
-90
lines changed

tests/conftest.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from typing import List, Optional, Tuple
2222

2323
import pytest
24+
import pytest_asyncio
2425

2526
from cylc.flow import LOG, flags
2627
from cylc.flow.cfgspec.glbl_cfg import glbl_cfg
@@ -40,7 +41,7 @@ def before_each():
4041
GraphNodeParser.get_inst().clear()
4142

4243

43-
@pytest.fixture(scope='module')
44+
@pytest_asyncio.fixture(scope='module')
4445
def mod_monkeypatch():
4546
"""A module-scoped version of the monkeypatch fixture."""
4647
from _pytest.monkeypatch import MonkeyPatch
@@ -160,7 +161,7 @@ def _log_scan(log, items):
160161
return _log_scan
161162

162163

163-
@pytest.fixture(scope='session')
164+
@pytest_asyncio.fixture(scope='session')
164165
def port_range():
165166
return glbl_cfg().get(['scheduler', 'run hosts', 'ports'])
166167

tests/integration/conftest.py

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
)
3131

3232
import pytest
33+
import pytest_asyncio
3334

3435
from cylc.flow.config import WorkflowConfig
3536
from cylc.flow.id import Tokens
@@ -112,15 +113,15 @@ def _pytest_passed(request: pytest.FixtureRequest) -> bool:
112113
))
113114

114115

115-
@pytest.fixture(scope='session')
116+
@pytest_asyncio.fixture(scope='session')
116117
def run_dir():
117118
"""The cylc run directory for this host."""
118119
path = Path(get_cylc_run_dir())
119120
path.mkdir(exist_ok=True)
120121
yield path
121122

122123

123-
@pytest.fixture(scope='session')
124+
@pytest_asyncio.fixture(scope='session')
124125
def ses_test_dir(request, run_dir):
125126
"""The root run dir for test flows in this test session."""
126127
timestamp = get_current_time_string(use_basic_format=True)
@@ -131,7 +132,7 @@ def ses_test_dir(request, run_dir):
131132
_rm_if_empty(path)
132133

133134

134-
@pytest.fixture(scope='module')
135+
@pytest_asyncio.fixture(scope='module')
135136
def mod_test_dir(request, ses_test_dir):
136137
"""The root run dir for test flows in this test module."""
137138
path = Path(
@@ -163,7 +164,7 @@ def test_dir(request, mod_test_dir):
163164
_rm_if_empty(path)
164165

165166

166-
@pytest.fixture(scope='module')
167+
@pytest_asyncio.fixture(scope='module')
167168
def mod_flow(run_dir, mod_test_dir):
168169
"""A function for creating module-level flows."""
169170
yield partial(_make_flow, run_dir, mod_test_dir)
@@ -175,7 +176,7 @@ def flow(run_dir, test_dir):
175176
yield partial(_make_flow, run_dir, test_dir)
176177

177178

178-
@pytest.fixture(scope='module')
179+
@pytest_asyncio.fixture(scope='module')
179180
def mod_scheduler():
180181
"""Return a Scheduler object for a flow.
181182
@@ -197,7 +198,7 @@ def scheduler():
197198
yield _scheduler
198199

199200

200-
@pytest.fixture(scope='module')
201+
@pytest_asyncio.fixture(scope='module')
201202
def mod_start():
202203
"""Start a scheduler but don't set it running (module scope)."""
203204
return partial(_start_flow, None)
@@ -209,7 +210,7 @@ def start(caplog: pytest.LogCaptureFixture):
209210
return partial(_start_flow, caplog)
210211

211212

212-
@pytest.fixture(scope='module')
213+
@pytest_asyncio.fixture(scope='module')
213214
def mod_run():
214215
"""Start a scheduler and set it running (module scope)."""
215216
return partial(_run_flow, None)
@@ -235,7 +236,7 @@ def one_conf():
235236
}
236237

237238

238-
@pytest.fixture(scope='module')
239+
@pytest_asyncio.fixture(scope='module')
239240
def mod_one_conf():
240241
return {
241242
'scheduler': {
@@ -257,36 +258,37 @@ def one(one_conf, flow, scheduler):
257258
return schd
258259

259260

260-
@pytest.fixture(scope='module')
261+
@pytest_asyncio.fixture(scope='module')
261262
def mod_one(mod_one_conf, mod_flow, mod_scheduler):
262263
id_ = mod_flow(mod_one_conf)
263264
schd = mod_scheduler(id_)
264265
return schd
265266

266267

267-
@pytest.fixture(scope='module')
268-
def event_loop():
269-
"""This fixture defines the event loop used for each test.
270-
271-
The default scoping for this fixture is "function" which means that all
272-
async fixtures must have "function" scoping.
273-
274-
Defining `event_loop` as a module scoped fixture opens the door to
275-
module scoped fixtures but means all tests in a module will run in the same
276-
event loop. This is fine, it's actually an efficiency win but also
277-
something to be aware of.
278-
279-
See: https://github.com/pytest-dev/pytest-asyncio/issues/171
280-
281-
"""
282-
loop = asyncio.get_event_loop_policy().new_event_loop()
283-
yield loop
284-
# gracefully exit async generators
285-
loop.run_until_complete(loop.shutdown_asyncgens())
286-
# cancel any tasks still running in this event loop
287-
for task in asyncio.all_tasks(loop):
288-
task.cancel()
289-
loop.close()
268+
if pytest_asyncio.__version__.startswith('0.21'):
269+
# BACK COMPAT: event_loop
270+
# FROM: python 3
271+
# TO: python 3.7
272+
# URL: https://github.com/cylc/cylc-flow/pull/6726
273+
@pytest_asyncio.fixture(scope='module')
274+
def event_loop():
275+
"""This fixture defines the event loop used for each test.
276+
The default scoping for this fixture is "function" which means that all
277+
async fixtures must have "function" scoping.
278+
Defining `event_loop` as a module scoped fixture opens the door to
279+
module scoped fixtures but means all tests in a module will run in the same
280+
event loop. This is fine, it's actually an efficiency win but also
281+
something to be aware of.
282+
See: https://github.com/pytest-dev/pytest-asyncio/issues/171
283+
"""
284+
loop = asyncio.get_event_loop_policy().new_event_loop()
285+
yield loop
286+
# gracefully exit async generators
287+
loop.run_until_complete(loop.shutdown_asyncgens())
288+
# cancel any tasks still running in this event loop
289+
for task in asyncio.all_tasks(loop):
290+
task.cancel()
291+
loop.close()
290292

291293

292294
@pytest.fixture
@@ -382,7 +384,7 @@ def _validate(id_: Union[str, Path], **kwargs) -> WorkflowConfig:
382384
return _validate
383385

384386

385-
@pytest.fixture(scope='module')
387+
@pytest_asyncio.fixture(scope='module')
386388
def mod_validate(run_dir):
387389
"""Provides a function for validating workflow configurations.
388390
@@ -465,7 +467,7 @@ def run_job_cmd(
465467
return _disable_polling
466468

467469

468-
@pytest.fixture(scope='module')
470+
@pytest_asyncio.fixture(scope='module')
469471
def mod_workflow_source(mod_flow, tmp_path_factory):
470472
"""Create a workflow source directory.
471473
@@ -686,7 +688,7 @@ def complete():
686688
return _complete
687689

688690

689-
@pytest.fixture(scope='module')
691+
@pytest_asyncio.fixture(scope='module')
690692
def mod_complete():
691693
return _complete
692694

tests/integration/network/test_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
"""Test cylc.flow.client.WorkflowRuntimeClient."""
18+
1819
import json
1920
from unittest.mock import Mock
21+
2022
import pytest
23+
import pytest_asyncio
2124

2225
from cylc.flow.exceptions import ClientError
2326
from cylc.flow.network.client import WorkflowRuntimeClient
2427
from cylc.flow.network.server import PB_METHOD_MAP
2528

2629

27-
@pytest.fixture(scope='module')
30+
@pytest_asyncio.fixture(scope='module')
2831
async def harness(mod_flow, mod_scheduler, mod_run, mod_one_conf):
2932
id_ = mod_flow(mod_one_conf)
3033
schd = mod_scheduler(id_)

tests/integration/network/test_graphql.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
"""Test the top-level (root) GraphQL queries."""
1818

19-
import pytest
2019
from typing import TYPE_CHECKING
2120

21+
import pytest
22+
import pytest_asyncio
23+
2224
from cylc.flow.id import Tokens
2325
from cylc.flow.network.client import WorkflowRuntimeClient
2426

@@ -68,7 +70,7 @@ def job_db_row():
6870
]
6971

7072

71-
@pytest.fixture(scope='module')
73+
@pytest_asyncio.fixture(scope='module')
7274
async def harness(mod_flow, mod_scheduler, mod_run):
7375
flow_def = {
7476
'scheduler': {

tests/integration/network/test_resolvers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from unittest.mock import Mock
2020

2121
import pytest
22+
import pytest_asyncio
2223

2324
from cylc.flow.data_store_mgr import EDGES, TASK_PROXIES
2425
from cylc.flow.id import Tokens
@@ -50,7 +51,7 @@ def node_args():
5051
}
5152

5253

53-
@pytest.fixture(scope='module')
54+
@pytest_asyncio.fixture(scope='module')
5455
async def mock_flow(
5556
mod_flow: Callable[..., str],
5657
mod_scheduler: Callable[..., Scheduler],

tests/integration/network/test_scan.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from typing import List
2424

2525
import pytest
26+
import pytest_asyncio
2627

2728
from cylc.flow.network.scan import (
2829
filter_name,
@@ -81,7 +82,7 @@ def make_src(name):
8182
make_src(name)
8283

8384

84-
@pytest.fixture(scope='session')
85+
@pytest_asyncio.fixture(scope='session')
8586
def sample_run_dir():
8687
tmp_path = Path(TemporaryDirectory().name)
8788
tmp_path.mkdir()
@@ -111,7 +112,7 @@ def badly_messed_up_cylc_run_dir(
111112
return tmp_path
112113

113114

114-
@pytest.fixture(scope='session')
115+
@pytest_asyncio.fixture(scope='session')
115116
def run_dir_with_symlinks():
116117
tmp_path = Path(TemporaryDirectory().name)
117118
tmp_path.mkdir()
@@ -133,7 +134,7 @@ def run_dir_with_symlinks():
133134
rmtree(tmp_path)
134135

135136

136-
@pytest.fixture(scope='session')
137+
@pytest_asyncio.fixture(scope='session')
137138
def run_dir_with_nasty_symlinks():
138139
tmp_path = Path(TemporaryDirectory().name)
139140
tmp_path.mkdir()
@@ -148,7 +149,7 @@ def run_dir_with_nasty_symlinks():
148149
rmtree(tmp_path)
149150

150151

151-
@pytest.fixture(scope='session')
152+
@pytest_asyncio.fixture(scope='session')
152153
def nested_dir():
153154
tmp_path = Path(TemporaryDirectory().name)
154155
tmp_path.mkdir()

tests/integration/network/test_server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from typing import Callable
2020

2121
import pytest
22+
import pytest_asyncio
2223

2324
from cylc.flow import __version__ as CYLC_VERSION
2425
from cylc.flow.network.server import PB_METHOD_MAP
@@ -31,7 +32,7 @@
3132
from async_timeout import timeout
3233

3334

34-
@pytest.fixture(scope='module')
35+
@pytest_asyncio.fixture(scope='module')
3536
async def myflow(mod_flow, mod_scheduler, mod_run, mod_one_conf):
3637
id_ = mod_flow(mod_one_conf)
3738
schd = mod_scheduler(id_)

tests/integration/network/test_zmq.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
import pytest
18+
import pytest_asyncio
1819
import zmq
1920

2021
from cylc.flow.exceptions import CylcError
@@ -23,7 +24,7 @@
2324
from .key_setup import setup_keys
2425

2526

26-
@pytest.fixture(scope='module')
27+
@pytest_asyncio.fixture(scope='module')
2728
def myflow(mod_flow, mod_one_conf):
2829
return mod_flow(mod_one_conf)
2930

tests/integration/run_modes/test_simulation.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import logging
2020
from pathlib import Path
21+
2122
import pytest
2223
from pytest import param
24+
import pytest_asyncio
2325

2426
from cylc.flow import commands
2527
from cylc.flow.cycling.iso8601 import ISO8601Point
@@ -88,7 +90,7 @@ def _run_simjob(schd, point, task):
8890
return _run_simjob
8991

9092

91-
@pytest.fixture(scope='module')
93+
@pytest_asyncio.fixture(scope='module')
9294
async def sim_time_check_setup(
9395
mod_flow, mod_scheduler, mod_start, mod_one_conf,
9496
):

tests/integration/scripts/test_list.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"""Test the "cylc list" command."""
1818

1919
import pytest
20+
import pytest_asyncio
2021

2122
from cylc.flow.exceptions import InputError
2223
from cylc.flow.option_parsers import Options
@@ -29,7 +30,7 @@
2930
ListOptions = Options(get_option_parser())
3031

3132

32-
@pytest.fixture(scope='module')
33+
@pytest_asyncio.fixture(scope='module')
3334
async def cylc_list(mod_flow, mod_scheduler, mod_start):
3435
id_ = mod_flow(
3536
{

0 commit comments

Comments
 (0)