Skip to content

Add debug option #1173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/980.added.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``--asyncio-debug`` CLI option and ``asyncio_debug`` configuration option to enable asyncio debug mode for the default event loop.
30 changes: 29 additions & 1 deletion pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,24 @@ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None
metavar="MODE",
help=ASYNCIO_MODE_HELP,
)
group.addoption(
"--asyncio-debug",
dest="asyncio_debug",
action="store_true",
default=None,
help="enable asyncio debug mode for the default event loop",
)
parser.addini(
"asyncio_mode",
help="default value for --asyncio-mode",
default="strict",
)
parser.addini(
"asyncio_debug",
help="enable asyncio debug mode for the default event loop",
type="bool",
default="false",
)
parser.addini(
"asyncio_default_fixture_loop_scope",
type="string",
Expand Down Expand Up @@ -195,6 +208,17 @@ def _get_asyncio_mode(config: Config) -> Mode:
) from e


def _get_asyncio_debug(config: Config) -> bool:
val = config.getoption("asyncio_debug")
if val is None:
val = config.getini("asyncio_debug")

if isinstance(val, bool):
return val
else:
return val == "true"


_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET = """\
The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching \
Expand All @@ -221,10 +245,12 @@ def pytest_configure(config: Config) -> None:
def pytest_report_header(config: Config) -> list[str]:
"""Add asyncio config to pytest header."""
mode = _get_asyncio_mode(config)
debug = _get_asyncio_debug(config)
default_fixture_loop_scope = config.getini("asyncio_default_fixture_loop_scope")
default_test_loop_scope = _get_default_test_loop_scope(config)
header = [
f"mode={mode}",
f"debug={debug}",
f"asyncio_default_fixture_loop_scope={default_fixture_loop_scope}",
f"asyncio_default_test_loop_scope={default_test_loop_scope}",
]
Expand Down Expand Up @@ -751,10 +777,12 @@ def _create_scoped_runner_fixture(scope: _ScopeName) -> Callable:
)
def _scoped_runner(
event_loop_policy,
request: FixtureRequest,
) -> Iterator[Runner]:
new_loop_policy = event_loop_policy
debug_mode = _get_asyncio_debug(request.config)
with _temporary_event_loop_policy(new_loop_policy):
runner = Runner().__enter__()
runner = Runner(debug=debug_mode).__enter__()
try:
yield runner
except Exception as e:
Expand Down
216 changes: 216 additions & 0 deletions tests/test_asyncio_debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
from __future__ import annotations

from textwrap import dedent

import pytest
from pytest import Pytester


def test_asyncio_debug_disabled_by_default(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest

pytest_plugins = "pytest_asyncio"

@pytest.mark.asyncio
async def test_debug_mode_disabled():
loop = asyncio.get_running_loop()
assert not loop.get_debug()
"""
)
)
result = pytester.runpytest()
result.assert_outcomes(passed=1)


def test_asyncio_debug_enabled_via_cli_option(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest

pytest_plugins = "pytest_asyncio"

@pytest.mark.asyncio
async def test_debug_mode_enabled():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""
)
)
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=1)


@pytest.mark.parametrize("config_value", ("true", "1"))
def test_asyncio_debug_enabled_via_config_option(pytester: Pytester, config_value: str):
pytester.makeini(
dedent(
f"""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_debug = {config_value}
"""
)
)
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest

pytest_plugins = "pytest_asyncio"

@pytest.mark.asyncio
async def test_debug_mode_enabled():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""
)
)
result = pytester.runpytest()
result.assert_outcomes(passed=1)


@pytest.mark.parametrize("config_value", ("false", "0"))
def test_asyncio_debug_disabled_via_config_option(
pytester: Pytester,
config_value: str,
):
pytester.makeini(
dedent(
f"""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_debug = {config_value}
"""
)
)
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest

pytest_plugins = "pytest_asyncio"

@pytest.mark.asyncio
async def test_debug_mode_disabled():
loop = asyncio.get_running_loop()
assert not loop.get_debug()
"""
)
)
result = pytester.runpytest()
result.assert_outcomes(passed=1)


def test_asyncio_debug_cli_option_overrides_config(pytester: Pytester):
pytester.makeini(
"[pytest]\nasyncio_default_fixture_loop_scope = function\nasyncio_debug = false"
)
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest

pytest_plugins = "pytest_asyncio"

@pytest.mark.asyncio
async def test_debug_mode_enabled():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""
)
)
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=1)


@pytest.mark.parametrize("loop_scope", ("function", "module", "session"))
def test_asyncio_debug_with_different_loop_scopes(pytester: Pytester, loop_scope: str):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
f"""\
import asyncio
import pytest

pytest_plugins = "pytest_asyncio"

@pytest.mark.asyncio(loop_scope="{loop_scope}")
async def test_debug_mode_with_scope():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""
)
)
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=1)


def test_asyncio_debug_with_async_fixtures(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
import pytest_asyncio

pytest_plugins = "pytest_asyncio"

@pytest_asyncio.fixture
async def async_fixture():
loop = asyncio.get_running_loop()
assert loop.get_debug()
return "fixture_value"

@pytest.mark.asyncio
async def test_debug_mode_with_fixture(async_fixture):
loop = asyncio.get_running_loop()
assert loop.get_debug()
assert async_fixture == "fixture_value"
"""
)
)
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=1)


def test_asyncio_debug_multiple_test_functions(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest

pytest_plugins = "pytest_asyncio"

@pytest.mark.asyncio
async def test_debug_first():
loop = asyncio.get_running_loop()
assert loop.get_debug()

@pytest.mark.asyncio
async def test_debug_second():
loop = asyncio.get_running_loop()
assert loop.get_debug()

@pytest.mark.asyncio
async def test_debug_third():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""
)
)
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=3)
Loading