Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pytest-dev/pytest-asyncio
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.25.0
Choose a base ref
...
head repository: pytest-dev/pytest-asyncio
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Dec 16, 2024

  1. Build(deps): Bump certifi in /dependencies/docs

    Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.8.30 to 2024.12.14.
    - [Commits](certifi/python-certifi@2024.08.30...2024.12.14)
    
    ---
    updated-dependencies:
    - dependency-name: certifi
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Dec 16, 2024
    Copy the full SHA
    8035fa0 View commit details
  2. Build(deps): Bump attrs from 24.2.0 to 24.3.0 in /dependencies/default

    Bumps [attrs](https://github.com/sponsors/hynek) from 24.2.0 to 24.3.0.
    - [Commits](https://github.com/sponsors/hynek/commits)
    
    ---
    updated-dependencies:
    - dependency-name: attrs
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Dec 16, 2024
    Copy the full SHA
    c44cc68 View commit details

Commits on Dec 17, 2024

  1. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/astral-sh/ruff-pre-commit: v0.8.2 → v0.8.3](astral-sh/ruff-pre-commit@v0.8.2...v0.8.3)
    pre-commit-ci[bot] authored and seifertm committed Dec 17, 2024
    Copy the full SHA
    38ac413 View commit details

Commits on Dec 24, 2024

  1. Build(deps): Bump hypothesis in /dependencies/default

    Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.122.3 to 6.123.0.
    - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
    - [Commits](HypothesisWorks/hypothesis@hypothesis-python-6.122.3...hypothesis-python-6.123.0)
    
    ---
    updated-dependencies:
    - dependency-name: hypothesis
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Dec 24, 2024
    Copy the full SHA
    7ce56e0 View commit details
  2. Build(deps): Bump jinja2 from 3.1.4 to 3.1.5 in /dependencies/docs

    Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
    - [Release notes](https://github.com/pallets/jinja/releases)
    - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
    - [Commits](pallets/jinja@3.1.4...3.1.5)
    
    ---
    updated-dependencies:
    - dependency-name: jinja2
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Dec 24, 2024
    Copy the full SHA
    84eee34 View commit details
  3. Build(deps): Bump urllib3 from 2.2.3 to 2.3.0 in /dependencies/docs

    Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.3 to 2.3.0.
    - [Release notes](https://github.com/urllib3/urllib3/releases)
    - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
    - [Commits](urllib3/urllib3@2.2.3...2.3.0)
    
    ---
    updated-dependencies:
    - dependency-name: urllib3
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Dec 24, 2024
    Copy the full SHA
    065e7e3 View commit details
  4. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/astral-sh/ruff-pre-commit: v0.8.3 → v0.8.4](astral-sh/ruff-pre-commit@v0.8.3...v0.8.4)
    - [github.com/pre-commit/mirrors-mypy: v1.13.0 → v1.14.0](pre-commit/mirrors-mypy@v1.13.0...v1.14.0)
    pre-commit-ci[bot] authored and Pierre-Sassoulas committed Dec 24, 2024
    Copy the full SHA
    581bd29 View commit details

Commits on Dec 30, 2024

  1. Copy the full SHA
    c99fe57 View commit details
  2. Build(deps): Bump charset-normalizer in /dependencies/docs

    Bumps [charset-normalizer](https://github.com/jawah/charset_normalizer) from 3.4.0 to 3.4.1.
    - [Release notes](https://github.com/jawah/charset_normalizer/releases)
    - [Changelog](https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md)
    - [Commits](jawah/charset_normalizer@3.4.0...3.4.1)
    
    ---
    updated-dependencies:
    - dependency-name: charset-normalizer
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Dec 30, 2024
    Copy the full SHA
    f88ca48 View commit details
  3. Build(deps): Bump coverage from 7.6.9 to 7.6.10 in /dependencies/default

    Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.9 to 7.6.10.
    - [Release notes](https://github.com/nedbat/coveragepy/releases)
    - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
    - [Commits](nedbat/coveragepy@7.6.9...7.6.10)
    
    ---
    updated-dependencies:
    - dependency-name: coverage
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Dec 30, 2024
    Copy the full SHA
    1a6d3bb View commit details
  4. Build(deps): Bump hypothesis in /dependencies/default

    Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.123.0 to 6.123.2.
    - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
    - [Commits](HypothesisWorks/hypothesis@hypothesis-python-6.123.0...hypothesis-python-6.123.2)
    
    ---
    updated-dependencies:
    - dependency-name: hypothesis
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Dec 30, 2024
    Copy the full SHA
    bffed5d View commit details
  5. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/rhysd/actionlint: v1.7.4 → v1.7.5](rhysd/actionlint@v1.7.4...v1.7.5)
    pre-commit-ci[bot] authored and asvetlov committed Dec 30, 2024
    Copy the full SHA
    050a5f8 View commit details

Commits on Dec 31, 2024

  1. fix: Fix broken event loop when a function-scoped test is in between …

    …two wider-scoped tests.
    
    The event_loop fixture finalizers only close event loops that were not created by pytest-asyncio. This prevents the finalizers from accidentally closing a module-scoped loop, for example.
    seifertm committed Dec 31, 2024
    Copy the full SHA
    0642dcd View commit details
  2. Copy the full SHA
    0c931b7 View commit details
  3. Copy the full SHA
    dafef6c View commit details
  4. refactor: Replace the "__original_fixture_loop" magic attribute with …

    …the more generic "__pytest_asyncio" magic attribute.
    seifertm committed Dec 31, 2024
    Copy the full SHA
    04f9044 View commit details
  5. Copy the full SHA
    a4e82ab View commit details
  6. Copy the full SHA
    2fd10f8 View commit details
  7. fix: Correct warning message when redefining the event_loop fixture.

    The message new refers to the "loop_scope" keyword argument, rather than "scope".
    seifertm committed Dec 31, 2024
    Copy the full SHA
    41c645b View commit details
  8. Copy the full SHA
    c236550 View commit details

Commits on Jan 2, 2025

  1. Copy the full SHA
    623ab74 View commit details

Commits on Jan 6, 2025

  1. Build(deps): Bump pygments from 2.18.0 to 2.19.1 in /dependencies/docs

    Bumps [pygments](https://github.com/pygments/pygments) from 2.18.0 to 2.19.1.
    - [Release notes](https://github.com/pygments/pygments/releases)
    - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
    - [Commits](pygments/pygments@2.18.0...2.19.1)
    
    ---
    updated-dependencies:
    - dependency-name: pygments
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Jan 6, 2025
    Copy the full SHA
    941e8b5 View commit details
  2. Build(deps): Bump hypothesis in /dependencies/default

    Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.123.2 to 6.123.4.
    - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
    - [Commits](HypothesisWorks/hypothesis@hypothesis-python-6.123.2...hypothesis-python-6.123.4)
    
    ---
    updated-dependencies:
    - dependency-name: hypothesis
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Jan 6, 2025
    Copy the full SHA
    aae43d4 View commit details

Commits on Jan 7, 2025

  1. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/astral-sh/ruff-pre-commit: v0.8.4 → v0.8.6](astral-sh/ruff-pre-commit@v0.8.4...v0.8.6)
    - [github.com/pre-commit/mirrors-mypy: v1.14.0 → v1.14.1](pre-commit/mirrors-mypy@v1.14.0...v1.14.1)
    - [github.com/rhysd/actionlint: v1.7.5 → v1.7.6](rhysd/actionlint@v1.7.5...v1.7.6)
    pre-commit-ci[bot] authored and Pierre-Sassoulas committed Jan 7, 2025
    Copy the full SHA
    e8ffb10 View commit details

Commits on Jan 8, 2025

  1. fix: Shutdown generators before closing event loops.

    Add a comment
    provinzkraut authored and seifertm committed Jan 8, 2025
    Copy the full SHA
    c3ad634 View commit details
  2. Copy the full SHA
    2188cdb View commit details

Commits on Jan 15, 2025

  1. Build(deps): Bump hypothesis in /dependencies/default

    Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.123.4 to 6.123.16.
    - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
    - [Commits](HypothesisWorks/hypothesis@hypothesis-python-6.123.4...hypothesis-python-6.123.16)
    
    ---
    updated-dependencies:
    - dependency-name: hypothesis
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Jan 15, 2025
    Copy the full SHA
    b3283ea View commit details
  2. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/astral-sh/ruff-pre-commit: v0.8.6 → v0.9.1](astral-sh/ruff-pre-commit@v0.8.6...v0.9.1)
    - [github.com/sirosen/check-jsonschema: 0.30.0 → 0.31.0](python-jsonschema/check-jsonschema@0.30.0...0.31.0)
    pre-commit-ci[bot] authored and seifertm committed Jan 15, 2025
    Copy the full SHA
    84609be View commit details
  3. feat: Configuration option for setting default loop_scope for tests

    New configuration option, asyncio_default_test_loop_scope, provides
    default value for loop_scope argument of asyncio marker. This can be
    used to use the same event loop in auto mode without need to use
    modifyitems hook.
    
    Test functions can still override loop_scope by using asyncio marker.
    Novakov authored and seifertm committed Jan 15, 2025
    Copy the full SHA
    5a0c567 View commit details

Commits on Jan 21, 2025

  1. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/astral-sh/ruff-pre-commit: v0.9.1 → v0.9.2](astral-sh/ruff-pre-commit@v0.9.1...v0.9.2)
    - [github.com/rhysd/actionlint: v1.7.6 → v1.7.7](rhysd/actionlint@v1.7.6...v1.7.7)
    pre-commit-ci[bot] authored and Pierre-Sassoulas committed Jan 21, 2025
    Copy the full SHA
    97d1f8d View commit details
  2. Build(deps): Bump hypothesis in /dependencies/default

    Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.123.16 to 6.124.1.
    - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
    - [Commits](HypothesisWorks/hypothesis@hypothesis-python-6.123.16...hypothesis-python-6.124.1)
    
    ---
    updated-dependencies:
    - dependency-name: hypothesis
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Jan 21, 2025
    Copy the full SHA
    dc3e247 View commit details
  3. Copy the full SHA
    c0120b5 View commit details
  4. Fix __package_loop_stack

    cdce8p authored and seifertm committed Jan 21, 2025
    Copy the full SHA
    b9c65d2 View commit details
  5. Simplify and improve typing even further

    cdce8p authored and seifertm committed Jan 21, 2025
    Copy the full SHA
    6ddb6f1 View commit details
  6. Add changelog entries

    cdce8p authored and seifertm committed Jan 21, 2025
    Copy the full SHA
    3205555 View commit details

Commits on Jan 24, 2025

  1. Build(deps): Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4

    Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.3 to 1.12.4.
    - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
    - [Commits](pypa/gh-action-pypi-publish@v1.12.3...v1.12.4)
    
    ---
    updated-dependencies:
    - dependency-name: pypa/gh-action-pypi-publish
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Jan 24, 2025
    Copy the full SHA
    fe32d2d View commit details

Commits on Jan 28, 2025

  1. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/astral-sh/ruff-pre-commit: v0.9.2 → v0.9.3](astral-sh/ruff-pre-commit@v0.9.2...v0.9.3)
    pre-commit-ci[bot] authored and Pierre-Sassoulas committed Jan 28, 2025
    Copy the full SHA
    92b006f View commit details
  2. fix: Avoid errors in cleanup of async generators when event loop is a…

    …lready closed
    
    check `is_closed()` before calling cleanup methods
    and degrade exceptions to warnings during cleanup to avoid problems
    minrk authored and seifertm committed Jan 28, 2025
    Copy the full SHA
    e8de4dc View commit details
  3. docs: Fixed typo in changelog.

    minrk authored and seifertm committed Jan 28, 2025
    Copy the full SHA
    6da8c87 View commit details

Commits on Feb 3, 2025

  1. Build(deps): Bump hypothesis in /dependencies/default

    Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.124.1 to 6.125.1.
    - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
    - [Commits](HypothesisWorks/hypothesis@hypothesis-python-6.124.1...hypothesis-python-6.125.1)
    
    ---
    updated-dependencies:
    - dependency-name: hypothesis
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and Pierre-Sassoulas committed Feb 3, 2025
    Copy the full SHA
    f3a9596 View commit details
  2. Build(deps): Bump attrs from 24.3.0 to 25.1.0 in /dependencies/default

    Bumps [attrs](https://github.com/sponsors/hynek) from 24.3.0 to 25.1.0.
    - [Commits](https://github.com/sponsors/hynek/commits)
    
    ---
    updated-dependencies:
    - dependency-name: attrs
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Feb 3, 2025
    Copy the full SHA
    e2c7bef View commit details
  3. Build(deps): Bump babel from 2.16.0 to 2.17.0 in /dependencies/docs

    Bumps [babel](https://github.com/python-babel/babel) from 2.16.0 to 2.17.0.
    - [Release notes](https://github.com/python-babel/babel/releases)
    - [Changelog](https://github.com/python-babel/babel/blob/master/CHANGES.rst)
    - [Commits](python-babel/babel@v2.16.0...v2.17.0)
    
    ---
    updated-dependencies:
    - dependency-name: babel
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Feb 3, 2025
    Copy the full SHA
    f77abae View commit details

Commits on Feb 4, 2025

  1. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/astral-sh/ruff-pre-commit: v0.9.3 → v0.9.4](astral-sh/ruff-pre-commit@v0.9.3...v0.9.4)
    - [github.com/sirosen/check-jsonschema: 0.31.0 → 0.31.1](python-jsonschema/check-jsonschema@0.31.0...0.31.1)
    pre-commit-ci[bot] authored and Pierre-Sassoulas committed Feb 4, 2025
    Copy the full SHA
    e8a497f View commit details

Commits on Feb 7, 2025

  1. Fix typo in uvloop.rst

    WillDaSilva authored Feb 7, 2025
    Copy the full SHA
    2589151 View commit details

Commits on Feb 8, 2025

  1. Build(deps): Bump certifi in /dependencies/docs

    Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.12.14 to 2025.1.31.
    - [Commits](certifi/python-certifi@2024.12.14...2025.01.31)
    
    ---
    updated-dependencies:
    - dependency-name: certifi
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Feb 8, 2025
    Copy the full SHA
    7d69b5b View commit details
  2. docs: how to set asyncio mode

    When reading the docs, it wasn't clear to me how to set asyncio's mode
    and I had to rely on looking at the source code (or scouring `pytest
    --help`). I thought it would be nice to expand the docs to be more
    explicit in this regard.
    
    Signed-off-by: JP-Ellis <josh@jpellis.me>
    JP-Ellis authored and seifertm committed Feb 8, 2025
    Copy the full SHA
    68b8d72 View commit details

Commits on Feb 10, 2025

  1. Build(deps): Bump hypothesis in /dependencies/default

    Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.125.1 to 6.125.2.
    - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
    - [Commits](HypothesisWorks/hypothesis@hypothesis-python-6.125.1...hypothesis-python-6.125.2)
    
    ---
    updated-dependencies:
    - dependency-name: hypothesis
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored Feb 10, 2025
    Copy the full SHA
    318aed7 View commit details
  2. Build(deps): Bump coverage in /dependencies/default

    Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.10 to 7.6.11.
    - [Release notes](https://github.com/nedbat/coveragepy/releases)
    - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
    - [Commits](nedbat/coveragepy@7.6.10...7.6.11)
    
    ---
    updated-dependencies:
    - dependency-name: coverage
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored Feb 10, 2025
    Copy the full SHA
    026ec60 View commit details
  3. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/astral-sh/ruff-pre-commit: v0.9.4 → v0.9.6](astral-sh/ruff-pre-commit@v0.9.4...v0.9.6)
    - [github.com/pre-commit/mirrors-mypy: v1.14.1 → v1.15.0](pre-commit/mirrors-mypy@v1.14.1...v1.15.0)
    pre-commit-ci[bot] authored Feb 10, 2025
    Copy the full SHA
    def231e View commit details

Commits on Feb 24, 2025

  1. Build(deps): Bump coverage in /dependencies/default

    Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.11 to 7.6.12.
    - [Release notes](https://github.com/nedbat/coveragepy/releases)
    - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
    - [Commits](nedbat/coveragepy@7.6.11...7.6.12)
    
    ---
    updated-dependencies:
    - dependency-name: coverage
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and seifertm committed Feb 24, 2025
    Copy the full SHA
    83d53da View commit details
Showing with 1,821 additions and 1,963 deletions.
  1. +1 −7 .github/dependabot.yml
  2. +142 −57 .github/workflows/main.yml
  3. +0 −4 .gitignore
  4. +23 −20 .pre-commit-config.yaml
  5. 0 tests/loop_fixture_scope/__init__.py → changelog.d/.gitkeep
  6. +66 −0 constraints.txt
  7. +0 −11 dependencies/default/constraints.txt
  8. +0 −3 dependencies/default/requirements.txt
  9. +9 −9 dependencies/docs/constraints.txt
  10. +0 −2 dependencies/docs/requirements.txt
  11. +17 −1 docs/concepts.rst
  12. +16 −0 docs/concepts_concurrent_execution_example.py
  13. +24 −0 docs/how-to-guides/change_default_test_loop.rst
  14. +2 −1 docs/how-to-guides/index.rst
  15. +6 −1 docs/how-to-guides/multiple_loops_example.py
  16. +11 −0 docs/how-to-guides/parametrize_with_asyncio.rst
  17. +10 −0 docs/how-to-guides/parametrize_with_asyncio_example.py
  18. +0 −10 docs/how-to-guides/run_session_tests_in_same_loop.rst
  19. +0 −10 docs/how-to-guides/session_scoped_loop_example.py
  20. +0 −63 docs/how-to-guides/test_session_scoped_loop_example.py
  21. +1 −1 docs/how-to-guides/uvloop.rst
  22. +118 −2 docs/reference/changelog.rst
  23. +29 −0 docs/reference/configuration.rst
  24. +0 −5 docs/reference/fixtures/event_loop_example.py
  25. +7 −1 docs/reference/fixtures/event_loop_policy_example.py
  26. +7 −2 docs/reference/fixtures/event_loop_policy_parametrized_example.py
  27. +0 −18 docs/reference/fixtures/index.rst
  28. +7 −3 docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py
  29. +3 −5 docs/reference/markers/index.rst
  30. +58 −15 pyproject.toml
  31. +4 −1 pytest_asyncio/__init__.py
  32. +305 −617 pytest_asyncio/plugin.py
  33. +0 −18 setup.cfg
  34. +54 −31 tests/async_fixtures/test_async_fixtures_contextvars.py
  35. +0 −30 tests/async_fixtures/test_async_fixtures_scope.py
  36. +0 −63 tests/async_fixtures/test_async_fixtures_with_finalizer.py
  37. +0 −53 tests/async_fixtures/test_async_gen_fixtures.py
  38. +0 −48 tests/async_fixtures/test_parametrized_loop.py
  39. +0 −31 tests/conftest.py
  40. +0 −37 tests/hypothesis/test_base.py
  41. +0 −17 tests/loop_fixture_scope/conftest.py
  42. +0 −19 tests/loop_fixture_scope/test_loop_fixture_scope.py
  43. +0 −23 tests/markers/test_class_scope.py
  44. +6 −29 tests/markers/test_function_scope.py
  45. +36 −0 tests/markers/test_mixed_scope.py
  46. +0 −75 tests/markers/test_module_scope.py
  47. +0 −42 tests/markers/test_package_scope.py
  48. +0 −22 tests/markers/test_session_scope.py
  49. +20 −7 tests/modes/test_strict_mode.py
  50. +216 −0 tests/test_asyncio_debug.py
  51. +77 −0 tests/test_asyncio_mark.py
  52. +0 −16 tests/test_dependent_fixtures.py
  53. +88 −0 tests/test_event_loop_fixture.py
  54. +0 −148 tests/test_event_loop_fixture_finalizer.py
  55. +0 −113 tests/test_event_loop_fixture_override_deprecation.py
  56. +0 −168 tests/test_explicit_event_loop_fixture_request.py
  57. +17 −0 tests/test_fixture_loop_scopes.py
  58. +0 −72 tests/test_multiloop.py
  59. +5 −0 tests/test_package.py
  60. +371 −0 tests/test_set_event_loop.py
  61. +0 −7 tests/test_simple.py
  62. +0 −10 tests/test_subprocess.py
  63. +30 −0 tests/test_task_cleanup.py
  64. +35 −15 tox.ini
8 changes: 1 addition & 7 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -2,13 +2,7 @@
version: 2
updates:
- package-ecosystem: pip
directory: /dependencies/default
schedule:
interval: weekly
open-pull-requests-limit: 10
target-branch: main
- package-ecosystem: pip
directory: /dependencies/docs
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
199 changes: 142 additions & 57 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -10,40 +10,32 @@ on:
merge_group:
workflow_dispatch:

permissions: {}

env:
PYTHON_LATEST: 3.13

jobs:
lint:
name: Run linters
build:
name: Build package
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
prerelease: ${{ steps.version.outputs.prerelease }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-python@v5
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_LATEST }}
- name: Install GitHub matcher for ActionLint checker
run: |
echo "::add-matcher::.github/actionlint-matcher.json"
- name: Install pre-commit
run: python -m pip install pre-commit
- name: Run pre-commit checks
run: pre-commit run --all-files --show-diff-on-failure
- name: Install check-wheel-content, and twine
run: python -m pip install build check-wheel-contents twine
- name: Build package
run: python -m build
- name: Install tox
run: python -m pip install tox
- name: Build package and check distributions
run: tox run -e build
- name: List result
run: ls -l dist
- name: Check wheel contents
run: check-wheel-contents dist/*.whl
- name: Check long_description
run: python -m twine check dist/*
- name: Install pytest-asyncio
run: pip install .
- name: Get version info
@@ -55,23 +47,48 @@ jobs:
name: dist
path: dist

lint:
name: Run linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_LATEST }}
- name: Install GitHub matcher for ActionLint checker
run: |
echo "::add-matcher::.github/actionlint-matcher.json"
- name: Install pre-commit
run: python -m pip install pre-commit
- name: Run pre-commit checks
run: pre-commit run --all-files --show-diff-on-failure

test:
name: ${{ matrix.os }} - Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}-latest

continue-on-error: ${{ !matrix.required }}
strategy:
matrix:
os: [ubuntu, windows]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
required: [true]
include:
- os: ubuntu
python-version: 3.14-dev
required: false
- os: windows
python-version: 3.14-dev
required: false


steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
if: "!endsWith(matrix.python-version, '-dev')"
- uses: actions/checkout@v5
with:
python-version: ${{ matrix.python-version }}
- uses: deadsnakes/action@v3.2.0
if: endsWith(matrix.python-version, '-dev')
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -91,26 +108,34 @@ jobs:
path: coverage/coverage.*
if-no-files-found: error

lint-github-actions:
name: Lint GitHub Actions
permissions:
security-events: write
uses: zizmorcore/workflow/.github/workflows/reusable-zizmor.yml@3bb5e95068d0f44b6d2f3f7e91379bed1d2f96a8

check:
name: Check
if: always()
needs: [lint, test]
needs: [build, lint, test]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_LATEST }}
- name: Install Coverage.py
run: |
set -xe
python -m pip install --upgrade coverage[toml]
- name: Download coverage data for all test runs
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
pattern: coverage-*
path: coverage
@@ -120,47 +145,107 @@ jobs:
coverage combine
coverage xml
- name: Upload coverage report
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
with:
files: coverage.xml
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

deploy:
name: Deploy
environment: release
# Run only on pushing a tag
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
needs: [lint, check]
create-github-release:
name: Create GitHub release
needs: [build, lint, check]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
- name: Install Python
uses: actions/setup-python@v6
- name: Install towncrier
run: pip install towncrier==24.8.0
- name: Install pandoc
run: |
sudo apt-get install -y pandoc
- name: Checkout
uses: actions/checkout@v4
- name: Install pytest-asyncio
run: pip install .
- name: Compile Release Notes Draft
if: ${{ !contains(github.ref, 'refs/tags/') }}
run: towncrier build --draft --version "${version}" > release-notes.rst
env:
version: ${{ needs.build.outputs.version }}
- name: Extract release notes from Git tag
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
run: |
set -e
git fetch --tags --force # see https://github.com/actions/checkout/issues/290
git for-each-ref "${GITHUB_REF}" --format='%(contents)' > release-notes.rst
# Strip signature from signed tags
sed -i -e "/-----BEGIN PGP SIGNATURE-----/,/-----END PGP SIGNATURE-----\n/d" \
-e "/-----BEGIN SSH SIGNATURE-----/,/-----END SSH SIGNATURE-----\n/d" release-notes.rst
- name: Convert Release Notes to Markdown
run: |
pandoc --wrap=preserve -o release-notes.md release-notes.rst
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release-notes.md
path: release-notes.md
- name: Download distributions
uses: actions/download-artifact@v5
with:
name: dist
path: dist
- name: Create GitHub Release
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
with:
name: pytest-asyncio ${{ needs.build.outputs.version }}
artifacts: dist/*
bodyFile: release-notes.md
prerelease: ${{ needs.build.outputs.prerelease }}
token: ${{ secrets.GITHUB_TOKEN }}
allowUpdates: true
draft: true
skipIfReleaseExists: true

publish-test-pypi:
name: Publish packages to test.pypi.org
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [build, lint, check, create-github-release]
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: dist
path: dist
- name: Upload to test.pypi.org
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
repository-url: https://test.pypi.org/legacy/

publish-pypi:
name: Publish packages to pypi.org
environment: release
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
needs: [build, lint, check, create-github-release]
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@v5
with:
name: dist
path: dist
- name: Collected dists
run: |
tree dist
- name: Convert README.rst to Markdown
run: |
pandoc -s -o README.md README.rst
- name: PyPI upload
uses: pypa/gh-action-pypi-publish@v1.12.3
with:
attestations: true
packages-dir: dist
password: ${{ secrets.PYPI_API_TOKEN }}
- name: GitHub Release
uses: ncipollo/release-action@v1
with:
name: pytest-asyncio ${{ needs.lint.outputs.version }}
artifacts: dist/*
bodyFile: README.md
prerelease: ${{ needs.lint.outputs.prerelease }}
token: ${{ secrets.GITHUB_TOKEN }}
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -62,7 +62,3 @@ target/

# pyenv
.python-version


# generated by setuptools_scm
pytest_asyncio/_version.py
43 changes: 23 additions & 20 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
exclude: rst$
- id: check-case-conflict
- id: check-json
- id: check-xml
- id: check-yaml
- id: debug-statements
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.2
rev: v0.13.3
hooks:
- id: ruff
args: [--fix]
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade
- repo: https://github.com/asottile/yesqa
rev: v1.5.0
hooks:
- id: yesqa
- repo: https://github.com/Zac-HD/shed
rev: 2024.10.1
rev: 2025.6.1
hooks:
- id: shed
args:
@@ -29,20 +40,8 @@ repos:
hooks:
- id: yamlfmt
args: [--mapping, '2', --sequence, '2', --offset, '0']
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: fix-encoding-pragma
args: [--remove]
- id: check-case-conflict
- id: check-json
- id: check-xml
- id: check-yaml
- id: debug-statements
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
rev: v1.18.2
hooks:
- id: mypy
exclude: ^(docs|tests)/.*
@@ -53,7 +52,7 @@ repos:
hooks:
- id: python-use-type-annotations
- repo: https://github.com/rhysd/actionlint
rev: v1.7.4
rev: v1.7.7
hooks:
- id: actionlint-docker
args:
@@ -65,15 +64,19 @@ repos:
- 'SC1004:'
stages: [manual]
- repo: https://github.com/sirosen/check-jsonschema
rev: 0.30.0
rev: 0.34.0
hooks:
- id: check-github-actions
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.5.0
rev: v2.7.0
hooks:
- id: pyproject-fmt
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
additional_dependencies: [tox>=4.9]
additional_dependencies: [tox>=4.28]
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.14.2
hooks:
- id: zizmor
ci:
skip:
- actionlint-docker
File renamed without changes.
66 changes: 66 additions & 0 deletions constraints.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
alabaster==0.7.16
annotated-types-0.7.0
attrs==25.4.0
babel==2.17.0
backports.asyncio.runner==1.2.0
backports.tarfile==1.2.0
certifi==2025.10.5
charset-normalizer==3.4.3
check-wheel-contents==0.6.1
cffi==2.0.0
click==8.1.8
coverage==7.10.7
cryptography==46.0.2
docutils==0.21.2
exceptiongroup==1.3.0
hypothesis==6.140.3
iniconfig==2.1.0
id==1.5.0
idna==3.10
imagesize==1.4.1
importlib_metadata==8.7.0
iniconfig==2.1.0
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.3.0
jeepney==0.9.0
Jinja2==3.1.6
keyring==25.6.0
markdown-it-py==3.0.0
MarkupSafe==3.0.3
mdurl==0.1.2
more-itertools==10.8.0
nh3==0.3.0
packaging==25.0
pluggy==1.6.0
Pygments==2.19.2
pycparser==2.23
pydantic==2.11.10
pydantic-core==2.33.2
pytest==8.4.2
readme-renderer==44.0
requests==2.32.5
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==14.1.0
SecretStorage==3.3.3
setuptools==80.9.0
setuptools-scm==9.2.0
snowballstemmer==3.0.1
sortedcontainers==2.4.0
Sphinx==8.0.2
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.2.1
twine==6.2.0
typing_extensions==4.15.0
typing-inspection==0.4.2
urllib3==2.5.0
wheel-filename==1.4.2
zipp==3.23.0
11 changes: 0 additions & 11 deletions dependencies/default/constraints.txt

This file was deleted.

3 changes: 0 additions & 3 deletions dependencies/default/requirements.txt

This file was deleted.

18 changes: 9 additions & 9 deletions dependencies/docs/constraints.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
alabaster==0.7.16
Babel==2.16.0
certifi==2024.8.30
charset-normalizer==3.4.0
Babel==2.17.0
certifi==2025.8.3
charset-normalizer==3.4.3
docutils==0.21.2
idna==3.10
imagesize==1.4.1
Jinja2==3.1.4
Jinja2==3.1.6
MarkupSafe==3.0.2
packaging==24.2
Pygments==2.18.0
requests==2.32.3
snowballstemmer==2.2.0
packaging==25.0
Pygments==2.19.2
requests==2.32.5
snowballstemmer==3.0.1
Sphinx==8.0.2
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
@@ -20,4 +20,4 @@ sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
urllib3==2.2.3
urllib3==2.5.0
2 changes: 0 additions & 2 deletions dependencies/docs/requirements.txt

This file was deleted.

18 changes: 17 additions & 1 deletion docs/concepts.rst
Original file line number Diff line number Diff line change
@@ -47,8 +47,12 @@ Assigning neighboring tests to different event loop scopes is discouraged as it
Test discovery modes
====================

Pytest-asyncio provides two modes for test discovery, *strict* and *auto*.
Pytest-asyncio provides two modes for test discovery, *strict* and *auto*. This can be set through Pytest's ``--asyncio-mode`` command line flag, or through the configuration file:

.. code-block:: toml
[tool.pytest.ini_options]
asyncio_mode = "auto" # or "strict"
Strict mode
-----------
@@ -67,3 +71,15 @@ In *auto* mode pytest-asyncio automatically adds the *asyncio* marker to all asy
This mode is intended for projects that use *asyncio* as their only asynchronous programming library. Auto mode makes for the simplest test and fixture configuration and is the recommended default.

If you intend to support multiple asynchronous programming libraries, e.g. *asyncio* and *trio*, strict mode will be the preferred option.

.. _concepts/concurrent_execution:

Test execution and concurrency
==============================

pytest-asyncio runs async tests sequentially, just like how pytest runs synchronous tests. Each asynchronous test runs within its assigned event loop. For example, consider the following two tests:

.. include:: concepts_concurrent_execution_example.py
:code: python

This sequential execution is intentional and important for maintaining test isolation. Running tests concurrently could introduce race conditions and side effects where one test could interfere with another, making test results unreliable and difficult to debug.
16 changes: 16 additions & 0 deletions docs/concepts_concurrent_execution_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import asyncio

import pytest


@pytest.mark.asyncio
async def test_first():
await asyncio.sleep(2) # Takes 2 seconds


@pytest.mark.asyncio
async def test_second():
await asyncio.sleep(2) # Takes 2 seconds


# Total execution time: ~4 seconds, not ~2 seconds
24 changes: 24 additions & 0 deletions docs/how-to-guides/change_default_test_loop.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
=======================================================
How to change the default event loop scope of all tests
=======================================================
The :ref:`configuration/asyncio_default_test_loop_scope` configuration option sets the default event loop scope for asynchronous tests. The following code snippets configure all tests to run in a session-scoped loop by default:

.. code-block:: ini
:caption: pytest.ini
[pytest]
asyncio_default_test_loop_scope = session
.. code-block:: toml
:caption: pyproject.toml
[tool.pytest.ini_options]
asyncio_default_test_loop_scope = "session"
.. code-block:: ini
:caption: setup.cfg
[tool:pytest]
asyncio_default_test_loop_scope = session
Please refer to :ref:`configuration/asyncio_default_test_loop_scope` for other valid scopes.
3 changes: 2 additions & 1 deletion docs/how-to-guides/index.rst
Original file line number Diff line number Diff line change
@@ -9,11 +9,12 @@ How-To Guides
migrate_from_0_23
change_fixture_loop
change_default_fixture_loop
change_default_test_loop
run_class_tests_in_same_loop
run_module_tests_in_same_loop
run_package_tests_in_same_loop
run_session_tests_in_same_loop
multiple_loops
parametrize_with_asyncio
uvloop
test_item_is_async

7 changes: 6 additions & 1 deletion docs/how-to-guides/multiple_loops_example.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import asyncio
from asyncio import DefaultEventLoopPolicy
import warnings

with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
from asyncio import DefaultEventLoopPolicy

import pytest

@@ -20,5 +24,6 @@ def event_loop_policy(request):


@pytest.mark.asyncio
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
11 changes: 11 additions & 0 deletions docs/how-to-guides/parametrize_with_asyncio.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
=====================================
How to parametrize asynchronous tests
=====================================

The ``pytest.mark.parametrize`` marker works with asynchronous tests the same as with synchronous tests. You can apply both ``pytest.mark.asyncio`` and ``pytest.mark.parametrize`` to asynchronous test functions:

.. include:: parametrize_with_asyncio_example.py
:code: python

.. note::
Whilst asynchronous tests can be parametrized, each individual test case still runs sequentially, not concurrently. For more information about how pytest-asyncio executes tests, see :ref:`concepts/concurrent_execution`.
10 changes: 10 additions & 0 deletions docs/how-to-guides/parametrize_with_asyncio_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import asyncio

import pytest


@pytest.mark.asyncio
@pytest.mark.parametrize("value", [1, 2, 3])
async def test_parametrized_async_function(value):
await asyncio.sleep(1)
assert value > 0
10 changes: 0 additions & 10 deletions docs/how-to-guides/run_session_tests_in_same_loop.rst

This file was deleted.

10 changes: 0 additions & 10 deletions docs/how-to-guides/session_scoped_loop_example.py

This file was deleted.

63 changes: 0 additions & 63 deletions docs/how-to-guides/test_session_scoped_loop_example.py

This file was deleted.

2 changes: 1 addition & 1 deletion docs/how-to-guides/uvloop.rst
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
How to test with uvloop
=======================

Redefinig the *event_loop_policy* fixture will parametrize all async tests. The following example causes all async tests to run multiple times, once for each event loop in the fixture parameters:
Redefining the *event_loop_policy* fixture will parametrize all async tests. The following example causes all async tests to run multiple times, once for each event loop in the fixture parameters:
Replace the default event loop policy in your *conftest.py:*

.. code-block:: python
120 changes: 118 additions & 2 deletions docs/reference/changelog.rst
Original file line number Diff line number Diff line change
@@ -2,15 +2,131 @@
Changelog
=========

All notable changes to this project will be documented in this file.

The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`__, and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`__.

This project uses `towncrier <https://towncrier.readthedocs.io/>`__ for changlog management and the changes for the upcoming release can be found in https://github.com/pytest-dev/pytest-asyncio/tree/main/changelog.d/.

.. towncrier release notes start
`1.2.0 <https://github.com/pytest-dev/pytest-asyncio/tree/1.2.0>`_ - 2025-09-12
===============================================================================

Added
-----

- ``--asyncio-debug`` CLI option and ``asyncio_debug`` configuration option to enable asyncio debug mode for the default event loop. (`#980 <https://github.com/pytest-dev/pytest-asyncio/issues/980>`_)
- A ``pytest.UsageError`` for invalid configuration values of ``asyncio_default_fixture_loop_scope`` and ``asyncio_default_test_loop_scope``. (`#1189 <https://github.com/pytest-dev/pytest-asyncio/issues/1189>`_)
- Compatibility with the `Pyright` type checker (`#731 <https://github.com/pytest-dev/pytest-asyncio/issues/731>`_)


Fixed
-----

- ``RuntimeError: There is no current event loop in thread 'MainThread'`` when any test unsets the event loop (such as when using ``asyncio.run`` and ``asyncio.Runner``). (`#1177 <https://github.com/pytest-dev/pytest-asyncio/issues/1177>`_)
- Deprecation warning when decorating an asynchronous fixture with ``@pytest.fixture`` in `strict` mode. The warning message now refers to the correct package. (`#1198 <https://github.com/pytest-dev/pytest-asyncio/issues/1198>`_)


Notes for Downstream Packagers
------------------------------

- Bump the minimum required version of tox to v4.28. This change is only relevant if you use the ``tox.ini`` file provided by pytest-asyncio to run tests.
- Extend dependency on typing-extensions>=4.12 from Python<3.10 to Python<3.13.


`1.1.0 <https://github.com/pytest-dev/pytest-asyncio/tree/1.1.0>`_ - 2025-07-16
===============================================================================

Added
-----

- Propagation of ContextVars from async fixtures to other fixtures and tests on Python 3.10 and older (`#127 <https://github.com/pytest-dev/pytest-asyncio/issues/127>`_)
- Cancellation of tasks when the `loop_scope` ends (`#200 <https://github.com/pytest-dev/pytest-asyncio/issues/200>`_)
- Warning when the current event loop is closed by a test


Fixed
-----

- Error about missing loop when calling functions requiring a loop in the `finally` clause of a task (`#878 <https://github.com/pytest-dev/pytest-asyncio/issues/878>`_)
- An error that could cause duplicate warnings to be issued


Notes for Downstream Packagers
------------------------------

- Added runtime dependency on `backports.asyncio.runner <https://pypi.org/project/backports.asyncio.runner/>`__ for use with Python 3.10 and older


`1.0.0 <https://github.com/pytest-dev/pytest-asyncio/tree/1.0.0>`_ - 2025-05-26
===============================================================================

Removed
-------

- The deprecated *event_loop* fixture. (`#1106 <https://github.com/pytest-dev/pytest-asyncio/issues/1106>`_)


Added
-----

- Prelimiary support for Python 3.14 (`#1025 <https://github.com/pytest-dev/pytest-asyncio/issues/1025>`_)


Changed
-------

- Scoped event loops (e.g. module-scoped loops) are created once rather than per scope (e.g. per module). This reduces the number of fixtures and speeds up collection time, especially for large test suites. (`#1107 <https://github.com/pytest-dev/pytest-asyncio/issues/1107>`_)
- The *loop_scope* argument to ``pytest.mark.asyncio`` no longer forces that a pytest Collector exists at the level of the specified scope. For example, a test function marked with ``pytest.mark.asyncio(loop_scope="class")`` no longer requires a class surrounding the test. This is consistent with the behavior of the *scope* argument to ``pytest_asyncio.fixture``. (`#1112 <https://github.com/pytest-dev/pytest-asyncio/issues/1112>`_)


Fixed
-----

- An error caused when using pytest's `--setup-plan` option. (`#630 <https://github.com/pytest-dev/pytest-asyncio/issues/630>`_)
- Unsuppressed import errors with pytest option ``--doctest-ignore-import-errors`` (`#797 <https://github.com/pytest-dev/pytest-asyncio/issues/797>`_)
- A "fixture not found" error in connection with package-scoped loops (`#1052 <https://github.com/pytest-dev/pytest-asyncio/issues/1052>`_)


Notes for Downstream Packagers
------------------------------

- Removed a test that had an ordering dependency on other tests. (`#1114 <https://github.com/pytest-dev/pytest-asyncio/issues/1114>`_)


0.26.0 (2025-03-25)
===================
- Adds configuration option that sets default event loop scope for all tests `#793 <https://github.com/pytest-dev/pytest-asyncio/issues/793>`_
- Improved type annotations for ``pytest_asyncio.fixture`` `#1045 <https://github.com/pytest-dev/pytest-asyncio/pull/1045>`_
- Added ``typing-extensions`` as additional dependency for Python ``<3.10`` `#1045 <https://github.com/pytest-dev/pytest-asyncio/pull/1045>`_


0.25.3 (2025-01-28)
===================
- Avoid errors in cleanup of async generators when event loop is already closed `#1040 <https://github.com/pytest-dev/pytest-asyncio/issues/1040>`_


0.25.2 (2025-01-08)
===================
- Call ``loop.shutdown_asyncgens()`` before closing the event loop to ensure async generators are closed in the same manner as ``asyncio.run`` does `#1034 <https://github.com/pytest-dev/pytest-asyncio/pull/1034>`_


0.25.1 (2025-01-02)
===================
- Fixes an issue that caused a broken event loop when a function-scoped test was executed in between two tests with wider loop scope `#950 <https://github.com/pytest-dev/pytest-asyncio/issues/950>`_
- Improves test collection speed in auto mode `#1020 <https://github.com/pytest-dev/pytest-asyncio/pull/1020>`_
- Corrects the warning that is emitted upon redefining the event_loop fixture


0.25.0 (2024-12-13)
===================
- Deprecated: Added warning when asyncio test requests async ``@pytest.fixture`` in strict mode. This will become an error in a future version of flake8-asyncio. `#979 <https://github.com/pytest-dev/pytest-asyncio/pull/979>`_
- Deprecated: Added warning when asyncio test requests async ``@pytest.fixture`` in strict mode. This will become an error in a future version of pytest-asyncio. `#979 <https://github.com/pytest-dev/pytest-asyncio/pull/979>`_
- Updates the error message about `pytest.mark.asyncio`'s `scope` keyword argument to say `loop_scope` instead. `#1004 <https://github.com/pytest-dev/pytest-asyncio/pull/1004>`_
- Verbose log displays correct parameter name: asyncio_default_fixture_loop_scope `#990 <https://github.com/pytest-dev/pytest-asyncio/pull/990>`_
- Propagates `contextvars` set in async fixtures to other fixtures and tests on Python 3.11 and above. `#1008 <https://github.com/pytest-dev/pytest-asyncio/pull/1008>`_



0.24.0 (2024-08-22)
===================
- BREAKING: Updated minimum supported pytest version to v8.2.0
29 changes: 29 additions & 0 deletions docs/reference/configuration.rst
Original file line number Diff line number Diff line change
@@ -8,6 +8,35 @@ asyncio_default_fixture_loop_scope
==================================
Determines the default event loop scope of asynchronous fixtures. When this configuration option is unset, it defaults to the fixture scope. In future versions of pytest-asyncio, the value will default to ``function`` when unset. Possible values are: ``function``, ``class``, ``module``, ``package``, ``session``

.. _configuration/asyncio_default_test_loop_scope:

asyncio_default_test_loop_scope
===============================
Determines the default event loop scope of asynchronous tests. When this configuration option is unset, it default to function scope. Possible values are: ``function``, ``class``, ``module``, ``package``, ``session``

.. _configuration/asyncio_debug:

asyncio_debug
=============
Enables `asyncio debug mode <https://docs.python.org/3/library/asyncio-dev.html#debug-mode>`_ for the default event loop used by asynchronous tests and fixtures.

The debug mode can be set by the ``asyncio_debug`` configuration option in the `configuration file
<https://docs.pytest.org/en/latest/reference/customize.html>`_:

.. code-block:: ini
# pytest.ini
[pytest]
asyncio_debug = true
The value can also be set via the ``--asyncio-debug`` command-line option:

.. code-block:: bash
$ pytest tests --asyncio-debug
By default, asyncio debug mode is disabled.

asyncio_mode
============
The pytest-asyncio mode can be set by the ``asyncio_mode`` configuration option in the `configuration file
5 changes: 0 additions & 5 deletions docs/reference/fixtures/event_loop_example.py

This file was deleted.

8 changes: 7 additions & 1 deletion docs/reference/fixtures/event_loop_policy_example.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import asyncio
import warnings

with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
from asyncio import DefaultEventLoopPolicy

import pytest


class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
class CustomEventLoopPolicy(DefaultEventLoopPolicy):
pass


@@ -13,5 +18,6 @@ def event_loop_policy(request):


@pytest.mark.asyncio(loop_scope="module")
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import asyncio
from asyncio import DefaultEventLoopPolicy
import warnings

with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
from asyncio import DefaultEventLoopPolicy

import pytest


class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
class CustomEventLoopPolicy(DefaultEventLoopPolicy):
pass


@@ -19,5 +23,6 @@ def event_loop_policy(request):


@pytest.mark.asyncio
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), DefaultEventLoopPolicy)
18 changes: 0 additions & 18 deletions docs/reference/fixtures/index.rst
Original file line number Diff line number Diff line change
@@ -2,24 +2,6 @@
Fixtures
========

event_loop
==========
Creates a new asyncio event loop based on the current event loop policy. The new loop
is available as the return value of this fixture for synchronous functions, or via `asyncio.get_running_loop <https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop>`__ for asynchronous functions.
The event loop is closed when the fixture scope ends.
The fixture scope defaults to ``function`` scope.

.. include:: event_loop_example.py
:code: python

Note that, when using the ``event_loop`` fixture, you need to interact with the event loop using methods like ``event_loop.run_until_complete``. If you want to *await* code inside your test function, you need to write a coroutine and use it as a test function. The `asyncio <#pytest-mark-asyncio>`__ marker
is used to mark coroutines that should be treated as test functions.

If you need to change the type of the event loop, prefer setting a custom event loop policy over redefining the ``event_loop`` fixture.

If the ``pytest.mark.asyncio`` decorator is applied to a test function, the ``event_loop``
fixture will be requested automatically by the test function.

event_loop_policy
=================
Returns the event loop policy used to create asyncio event loops.
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import asyncio
import warnings

with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
from asyncio import DefaultEventLoopPolicy

import pytest


@pytest.fixture(
params=[
asyncio.DefaultEventLoopPolicy(),
asyncio.DefaultEventLoopPolicy(),
DefaultEventLoopPolicy(),
DefaultEventLoopPolicy(),
]
)
def event_loop_policy(request):
8 changes: 3 additions & 5 deletions docs/reference/markers/index.rst
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@
Markers
=======

.. _reference/markers/asyncio:

``pytest.mark.asyncio``
=======================
A coroutine or async generator with this marker is treated as a test function by pytest.
@@ -19,21 +21,17 @@ The ``pytest.mark.asyncio`` marker can be omitted entirely in |auto mode|_ where

By default, each test runs in it's own asyncio event loop.
Multiple tests can share the same event loop by providing a *loop_scope* keyword argument to the *asyncio* mark.
The supported scopes are *class,* and *module,* and *package*.
The supported scopes are *function,* *class,* and *module,* *package,* and *session*.
The following code example provides a shared event loop for all tests in `TestClassScopedLoop`:

.. include:: class_scoped_loop_strict_mode_example.py
:code: python

If you request class scope for a test that is not part of a class, it will result in a *UsageError*.
Similar to class-scoped event loops, a module-scoped loop is provided when setting mark's scope to *module:*

.. include:: module_scoped_loop_strict_mode_example.py
:code: python

Package-scoped loops only work with `regular Python packages. <https://docs.python.org/3/glossary.html#term-regular-package>`__
That means they require an *__init__.py* to be present.
Package-scoped loops do not work in `namespace packages. <https://docs.python.org/3/glossary.html#term-namespace-package>`__
Subpackages do not share the loop with their parent package.

Tests marked with *session* scope share the same event loop, even if the tests exist in different packages.
73 changes: 58 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
[build-system]
build-backend = "setuptools.build_meta"

requires = [
"setuptools>=51",
"setuptools>=77",
"setuptools-scm[toml]>=6.2",
"wheel>=0.36",
]

[project]
name = "pytest-asyncio"
description = "Pytest support for asyncio"
readme.content-type = "text/x-rst"
readme.file = "README.rst"
license.text = "Apache 2.0"
license = "Apache-2.0"
license-files = [
"LICENSE",
]
maintainers = [
{ name = "Michael Seifert", email = "m.seifert@digitalernachschub.de" },
]
authors = [
{ name = "Tin Tvrtković <tinchester@gmail.com>", email = "tinchester@gmail.com" },
{ name = "Tin Tvrtković", email = "tinchester@gmail.com" },
]
requires-python = ">=3.9"
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Framework :: AsyncIO",
"Framework :: Pytest",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development :: Testing",
"Typing :: Typed",
]
@@ -37,7 +41,9 @@ dynamic = [
]

dependencies = [
"backports-asyncio-runner>=1.1,<2; python_version<'3.11'",
"pytest>=8.2,<9",
"typing-extensions>=4.12; python_version<'3.13'",
]
optional-dependencies.docs = [
"sphinx>=5.3",
@@ -59,12 +65,9 @@ packages = [
"pytest_asyncio",
]
include-package-data = true
license-files = [
"LICENSE",
]

[tool.setuptools_scm]
write_to = "pytest_asyncio/_version.py"
local_scheme = "no-local-version"

[tool.ruff]
line-length = 88
@@ -108,6 +111,9 @@ lint.ignore = [
"D415", # First line should end with a period, question mark, or exclamation point
]

[tool.pyproject-fmt]
max_supported_python = "3.14"

[tool.pytest.ini_options]
python_files = [
"test_*.py",
@@ -123,7 +129,6 @@ asyncio_default_fixture_loop_scope = "function"
junit_family = "xunit2"
filterwarnings = [
"error",
"ignore:The event_loop fixture provided by pytest-asyncio has been redefined.*:DeprecationWarning",
]

[tool.coverage.run]
@@ -132,10 +137,48 @@ source = [
]
branch = true
data_file = "coverage/coverage"
omit = [
"*/_version.py",
]
parallel = true

[tool.coverage.report]
show_missing = true

[tool.towncrier]
directory = "changelog.d"
filename = "docs/reference/changelog.rst"
title_format = "`{version} <https://github.com/pytest-dev/pytest-asyncio/tree/{version}>`_ - {project_date}"
issue_format = "`#{issue} <https://github.com/pytest-dev/pytest-asyncio/issues/{issue}>`_"

[[tool.towncrier.type]]
directory = "security"
name = "Security"
showcontent = true

[[tool.towncrier.type]]
directory = "removed"
name = "Removed"
showcontent = true

[[tool.towncrier.type]]
directory = "deprecated"
name = "Deprecated"
showcontent = true

[[tool.towncrier.type]]
directory = "added"
name = "Added"
showcontent = true

[[tool.towncrier.type]]
directory = "changed"
name = "Changed"
showcontent = true

[[tool.towncrier.type]]
directory = "fixed"
name = "Fixed"
showcontent = true

[[tool.towncrier.type]]
directory = "downstream"
name = "Notes for Downstream Packagers"
showcontent = true
5 changes: 4 additions & 1 deletion pytest_asyncio/__init__.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,10 @@

from __future__ import annotations

from ._version import version as __version__ # noqa: F401
from importlib.metadata import version

from .plugin import fixture, is_async_test

__version__ = version(__name__)

__all__ = ("fixture", "is_async_test")
922 changes: 305 additions & 617 deletions pytest_asyncio/plugin.py

Large diffs are not rendered by default.

18 changes: 0 additions & 18 deletions setup.cfg

This file was deleted.

85 changes: 54 additions & 31 deletions tests/async_fixtures/test_async_fixtures_contextvars.py
Original file line number Diff line number Diff line change
@@ -5,8 +5,8 @@

from __future__ import annotations

import sys
from textwrap import dedent
from typing import Literal

import pytest
from pytest import Pytester
@@ -56,11 +56,6 @@ async def test(check_var_fixture):
result.assert_outcomes(passed=1)


@pytest.mark.xfail(
sys.version_info < (3, 11),
reason="requires asyncio Task context support",
strict=True,
)
def test_var_from_async_generator_propagates_to_sync(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
@@ -86,11 +81,6 @@ async def test(check_var_fixture):
result.assert_outcomes(passed=1)


@pytest.mark.xfail(
sys.version_info < (3, 11),
reason="requires asyncio Task context support",
strict=True,
)
def test_var_from_async_fixture_propagates_to_sync(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
@@ -115,11 +105,6 @@ def test(check_var_fixture):
result.assert_outcomes(passed=1)


@pytest.mark.xfail(
sys.version_info < (3, 11),
reason="requires asyncio Task context support",
strict=True,
)
def test_var_from_generator_reset_before_previous_fixture_cleanup(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
@@ -149,11 +134,6 @@ async def test(var_fixture):
result.assert_outcomes(passed=1)


@pytest.mark.xfail(
sys.version_info < (3, 11),
reason="requires asyncio Task context support",
strict=True,
)
def test_var_from_fixture_reset_before_previous_fixture_cleanup(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
@@ -183,11 +163,6 @@ async def test(var_fixture):
result.assert_outcomes(passed=1)


@pytest.mark.xfail(
sys.version_info < (3, 11),
reason="requires asyncio Task context support",
strict=True,
)
def test_var_previous_value_restored_after_fixture(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
@@ -216,11 +191,6 @@ async def test(var_fixture_2):
result.assert_outcomes(passed=1)


@pytest.mark.xfail(
sys.version_info < (3, 11),
reason="requires asyncio Task context support",
strict=True,
)
def test_var_set_to_existing_value_ok(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
@@ -245,3 +215,56 @@ async def test(same_var_fixture):
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)


def test_no_isolation_against_context_changes_in_sync_tests(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""
import pytest
import pytest_asyncio
from contextvars import ContextVar
_context_var = ContextVar("my_var")
def test_sync():
_context_var.set("new_value")
@pytest.mark.asyncio
async def test_async():
assert _context_var.get() == "new_value"
"""
)
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)


@pytest.mark.parametrize("loop_scope", ("function", "module"))
def test_isolation_against_context_changes_in_async_tests(
pytester: Pytester, loop_scope: Literal["function", "module"]
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
f"""
import pytest
import pytest_asyncio
from contextvars import ContextVar
_context_var = ContextVar("my_var")
@pytest.mark.asyncio(loop_scope="{loop_scope}")
async def test_async_first():
_context_var.set("new_value")
@pytest.mark.asyncio(loop_scope="{loop_scope}")
async def test_async_second():
with pytest.raises(LookupError):
_context_var.get()
"""
)
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
30 changes: 0 additions & 30 deletions tests/async_fixtures/test_async_fixtures_scope.py

This file was deleted.

63 changes: 0 additions & 63 deletions tests/async_fixtures/test_async_fixtures_with_finalizer.py

This file was deleted.

53 changes: 0 additions & 53 deletions tests/async_fixtures/test_async_gen_fixtures.py

This file was deleted.

48 changes: 0 additions & 48 deletions tests/async_fixtures/test_parametrized_loop.py

This file was deleted.

31 changes: 0 additions & 31 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,3 @@
from __future__ import annotations

import asyncio

import pytest

pytest_plugins = "pytester"


@pytest.fixture
def dependent_fixture(event_loop):
"""A fixture dependent on the event_loop fixture, doing some cleanup."""
counter = 0

async def just_a_sleep():
"""Just sleep a little while."""
nonlocal event_loop
await asyncio.sleep(0.1)
nonlocal counter
counter += 1

event_loop.run_until_complete(just_a_sleep())
yield
event_loop.run_until_complete(just_a_sleep())

assert counter == 2


@pytest.fixture(scope="session", name="factory_involving_factories")
def factory_involving_factories_fixture(unused_tcp_port_factory):
def factory():
return unused_tcp_port_factory()

return factory
37 changes: 0 additions & 37 deletions tests/hypothesis/test_base.py
Original file line number Diff line number Diff line change
@@ -45,43 +45,6 @@ async def test_mark_and_parametrize(x, y):
assert y in (1, 2)


def test_can_use_explicit_event_loop_fixture(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = module")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
from hypothesis import given
import hypothesis.strategies as st
pytest_plugins = 'pytest_asyncio'
@pytest.fixture(scope="module")
def event_loop():
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@given(st.integers())
@pytest.mark.asyncio
async def test_explicit_fixture_request(event_loop, n):
semaphore = asyncio.Semaphore(value=0)
event_loop.call_soon(semaphore.release)
await semaphore.acquire()
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
result.assert_outcomes(passed=1, warnings=2)
result.stdout.fnmatch_lines(
[
'*is asynchronous and explicitly requests the "event_loop" fixture*',
"*event_loop fixture provided by pytest-asyncio has been redefined*",
]
)


def test_async_auto_marked(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
17 changes: 0 additions & 17 deletions tests/loop_fixture_scope/conftest.py

This file was deleted.

19 changes: 0 additions & 19 deletions tests/loop_fixture_scope/test_loop_fixture_scope.py

This file was deleted.

23 changes: 0 additions & 23 deletions tests/markers/test_class_scope.py
Original file line number Diff line number Diff line change
@@ -82,29 +82,6 @@ async def test_this_runs_in_same_loop(self):
result.assert_outcomes(passed=2)


def test_asyncio_mark_raises_when_class_scoped_is_request_without_class(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
@pytest.mark.asyncio(loop_scope="class")
async def test_has_no_surrounding_class():
pass
"""
)
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
"*is marked to be run in an event loop with scope*",
)


def test_asyncio_mark_is_inherited_to_subclasses(pytester: pytest.Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
35 changes: 6 additions & 29 deletions tests/markers/test_function_scope.py
Original file line number Diff line number Diff line change
@@ -87,32 +87,9 @@ async def test_warns():
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result.assert_outcomes(passed=1, warnings=2)
result.stdout.fnmatch_lines("*DeprecationWarning*")


def test_function_scope_supports_explicit_event_loop_fixture_request(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import pytest
pytestmark = pytest.mark.asyncio
async def test_remember_loop(event_loop):
pass
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
result = pytester.runpytest("--asyncio-mode=strict", "--assert=plain")
result.assert_outcomes(passed=1, warnings=1)
result.stdout.fnmatch_lines(
'*is asynchronous and explicitly requests the "event_loop" fixture*'
)
result.stdout.fnmatch_lines("*DeprecationWarning*")


def test_asyncio_mark_respects_the_loop_policy(
@@ -180,7 +157,7 @@ async def test_parametrized_loop():
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)


@@ -211,7 +188,7 @@ async def test_runs_is_same_loop_as_fixture(my_fixture):
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)


@@ -265,7 +242,7 @@ async def test_anything():
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result = pytester.runpytest("--asyncio-mode=strict", "--assert=plain")
result.assert_outcomes(warnings=0, passed=1)


@@ -297,5 +274,5 @@ async def test_markers_not_duplicated(request):
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=auto")
result = pytester.runpytest("--asyncio-mode=auto", "--assert=plain")
result.assert_outcomes(warnings=0, passed=1)
36 changes: 36 additions & 0 deletions tests/markers/test_mixed_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import annotations

from textwrap import dedent

from pytest import Pytester


def test_function_scoped_loop_restores_previous_loop_scope(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
module_loop: asyncio.AbstractEventLoop
@pytest.mark.asyncio(loop_scope="module")
async def test_remember_loop():
global module_loop
module_loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="function")
async def test_with_function_scoped_loop():
pass
@pytest.mark.asyncio(loop_scope="module")
async def test_runs_in_same_loop():
global module_loop
assert asyncio.get_running_loop() is module_loop
"""
)
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=3)
75 changes: 0 additions & 75 deletions tests/markers/test_module_scope.py
Original file line number Diff line number Diff line change
@@ -5,59 +5,6 @@
from pytest import Pytester


def test_asyncio_mark_works_on_module_level(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio
class TestPyTestMark:
async def test_is_asyncio(self, event_loop, sample_fixture):
assert asyncio.get_event_loop()
counter = 1
async def inc():
nonlocal counter
counter += 1
await asyncio.sleep(0)
await asyncio.ensure_future(inc())
assert counter == 2
async def test_is_asyncio(event_loop, sample_fixture):
assert asyncio.get_event_loop()
counter = 1
async def inc():
nonlocal counter
counter += 1
await asyncio.sleep(0)
await asyncio.ensure_future(inc())
assert counter == 2
@pytest.fixture
def sample_fixture():
return None
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
result.assert_outcomes(passed=2, warnings=2)
result.stdout.fnmatch_lines(
'*is asynchronous and explicitly requests the "event_loop" fixture*'
)


def test_asyncio_mark_provides_module_scoped_loop_strict_mode(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
@@ -89,28 +36,6 @@ async def test_this_runs_in_same_loop(self):
result.assert_outcomes(passed=3)


def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio(loop_scope="module")
async def test_remember_loop(event_loop):
pass
"""
)
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *")


def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
42 changes: 0 additions & 42 deletions tests/markers/test_package_scope.py
Original file line number Diff line number Diff line change
@@ -69,28 +69,6 @@ async def test_subpackage_runs_in_different_loop():
result.assert_outcomes(passed=4)


def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_raises=dedent(
"""\
import asyncio
import pytest
@pytest.mark.asyncio(loop_scope="package")
async def test_remember_loop(event_loop):
pass
"""
),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *")


def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
@@ -361,23 +339,3 @@ async def test_does_not_fail(sets_event_loop_to_none, n):
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)


def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_module=dedent(
"""\
import pytest
@pytest.mark.asyncio(loop_scope="package")
async def test_anything():
pass
"""
),
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result.assert_outcomes(warnings=0, passed=1)
22 changes: 0 additions & 22 deletions tests/markers/test_session_scope.py
Original file line number Diff line number Diff line change
@@ -70,28 +70,6 @@ async def test_subpackage_runs_in_same_loop():
result.assert_outcomes(passed=4)


def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_raises=dedent(
"""\
import asyncio
import pytest
@pytest.mark.asyncio(loop_scope="session")
async def test_remember_loop(event_loop):
pass
"""
),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *")


def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
27 changes: 20 additions & 7 deletions tests/modes/test_strict_mode.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

from textwrap import dedent

from pytest import Pytester
from pytest import Pytester, version_tuple as pytest_version


def test_strict_mode_cmdline(pytester: Pytester):
@@ -95,7 +95,10 @@ async def test_anything():
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
result.assert_outcomes(skipped=1, warnings=1)
if pytest_version >= (8, 4, 0):
result.assert_outcomes(failed=1, skipped=0, warnings=0)
else:
result.assert_outcomes(skipped=1, warnings=1)
result.stdout.fnmatch_lines(["*async def functions are not natively supported*"])


@@ -117,7 +120,11 @@ async def test_anything(any_fixture):
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
result.assert_outcomes(skipped=1, warnings=2)

if pytest_version >= (8, 4, 0):
result.assert_outcomes(failed=1, skipped=0, warnings=2)
else:
result.assert_outcomes(skipped=1, warnings=2)
result.stdout.fnmatch_lines(
[
"*async def functions are not natively supported*",
@@ -149,7 +156,10 @@ async def test_anything(any_fixture):
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
result.assert_outcomes(passed=1, failed=0, skipped=0, warnings=1)
if pytest_version >= (8, 4, 0):
result.assert_outcomes(passed=1, failed=0, skipped=0, warnings=2)
else:
result.assert_outcomes(passed=1, failed=0, skipped=0, warnings=1)
result.stdout.fnmatch_lines(
[
"*warnings summary*",
@@ -163,7 +173,7 @@ async def test_anything(any_fixture):
"@pytest.fixture 'any_fixture' in strict mode. "
"You might want to use @pytest_asyncio.fixture or switch to "
"auto mode. "
"This will become an error in future versions of flake8-asyncio."
"This will become an error in future versions of pytest-asyncio."
),
],
)
@@ -193,7 +203,10 @@ async def test_anything(any_fixture):
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
result.assert_outcomes(passed=1, warnings=1)
if pytest_version >= (8, 4, 0):
result.assert_outcomes(passed=1, warnings=2)
else:
result.assert_outcomes(passed=1, warnings=1)
result.stdout.fnmatch_lines(
[
"*warnings summary*",
@@ -207,7 +220,7 @@ async def test_anything(any_fixture):
"@pytest.fixture 'any_fixture' in strict mode. "
"You might want to use @pytest_asyncio.fixture or switch to "
"auto mode. "
"This will become an error in future versions of flake8-asyncio."
"This will become an error in future versions of pytest-asyncio."
),
],
)
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)
77 changes: 77 additions & 0 deletions tests/test_asyncio_mark.py
Original file line number Diff line number Diff line change
@@ -146,3 +146,80 @@ async def test_a():
result.stdout.fnmatch_lines(
["*Tests based on asynchronous generators are not supported*"]
)


def test_asyncio_marker_fallbacks_to_configured_default_loop_scope_if_not_set(
pytester: Pytester,
):
pytester.makeini(
dedent(
"""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_default_test_loop_scope = session
"""
)
)

pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest_asyncio
import pytest
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def session_loop_fixture():
global loop
loop = asyncio.get_running_loop()
async def test_a(session_loop_fixture):
global loop
assert asyncio.get_running_loop() is loop
"""
)
)

result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)


def test_asyncio_marker_uses_marker_loop_scope_even_if_config_is_set(
pytester: Pytester,
):
pytester.makeini(
dedent(
"""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_default_test_loop_scope = module
"""
)
)

pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest_asyncio
import pytest
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def session_loop_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="session")
async def test_a(session_loop_fixture):
global loop
assert asyncio.get_running_loop() is loop
"""
)
)

result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
16 changes: 0 additions & 16 deletions tests/test_dependent_fixtures.py

This file was deleted.

88 changes: 88 additions & 0 deletions tests/test_event_loop_fixture.py
Original file line number Diff line number Diff line change
@@ -53,3 +53,91 @@ async def test_custom_policy_is_not_overwritten():
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result.assert_outcomes(passed=2)


def test_event_loop_fixture_handles_unclosed_async_gen(
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_something():
async def generator_fn():
yield
yield
gen = generator_fn()
await gen.__anext__()
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1, warnings=0)


def test_closing_event_loop_in_sync_fixture_teardown_raises_warning(
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 _event_loop():
return asyncio.get_running_loop()
@pytest.fixture
def close_event_loop(_event_loop):
yield
# fixture has its own cleanup code
_event_loop.close()
@pytest.mark.asyncio
async def test_something(close_event_loop):
await asyncio.sleep(0.01)
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result.assert_outcomes(passed=1, warnings=1)
result.stdout.fnmatch_lines(
["*An exception occurred during teardown of an asyncio.Runner*"]
)


def test_event_loop_fixture_asyncgen_error(
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_something():
# mock shutdown_asyncgen failure
loop = asyncio.get_running_loop()
async def fail():
raise RuntimeError("mock error cleaning up...")
loop.shutdown_asyncgens = fail
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1, warnings=1)
148 changes: 0 additions & 148 deletions tests/test_event_loop_fixture_finalizer.py

This file was deleted.

113 changes: 0 additions & 113 deletions tests/test_event_loop_fixture_override_deprecation.py

This file was deleted.

168 changes: 0 additions & 168 deletions tests/test_explicit_event_loop_fixture_request.py

This file was deleted.

17 changes: 17 additions & 0 deletions tests/test_fixture_loop_scopes.py
Original file line number Diff line number Diff line change
@@ -136,3 +136,20 @@ async def test_runs_in_fixture_loop(fixture_loop):
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result.assert_outcomes(passed=1)


def test_invalid_default_fixture_loop_scope_raises_error(pytester: Pytester):
pytester.makeini(
"""\
[pytest]
asyncio_default_fixture_loop_scope = invalid_scope
"""
)
result = pytester.runpytest()
result.stderr.fnmatch_lines(
[
"ERROR: 'invalid_scope' is not a valid "
"asyncio_default_fixture_loop_scope. Valid scopes are: "
"function, class, module, package, session."
]
)
72 changes: 0 additions & 72 deletions tests/test_multiloop.py

This file was deleted.

5 changes: 5 additions & 0 deletions tests/test_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import pytest_asyncio


def test_package_exposes_version():
assert pytest_asyncio.__version__
371 changes: 371 additions & 0 deletions tests/test_set_event_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
from __future__ import annotations

import sys
from textwrap import dedent

import pytest
from pytest import Pytester


@pytest.mark.parametrize(
"test_loop_scope",
("function", "module", "package", "session"),
)
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_set_event_loop_none(
pytester: Pytester,
test_loop_scope: str,
loop_breaking_action: str,
):
pytester.makeini(
dedent(
f"""\
[pytest]
asyncio_default_test_loop_scope = {test_loop_scope}
asyncio_default_fixture_loop_scope = function
"""
)
)
pytester.makepyfile(
dedent(
f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_before():
pass
def test_set_event_loop_none():
{loop_breaking_action}
@pytest.mark.asyncio
async def test_after():
pass
"""
)
)
result = pytester.runpytest_subprocess()
result.assert_outcomes(passed=3)


@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_set_event_loop_none_class(pytester: Pytester, loop_breaking_action: str):
pytester.makeini(
dedent(
"""\
[pytest]
asyncio_default_test_loop_scope = class
asyncio_default_fixture_loop_scope = function
"""
)
)
pytester.makepyfile(
dedent(
f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
class TestClass:
@pytest.mark.asyncio
async def test_before(self):
pass
def test_set_event_loop_none(self):
{loop_breaking_action}
@pytest.mark.asyncio
async def test_after(self):
pass
"""
)
)
result = pytester.runpytest_subprocess()
result.assert_outcomes(passed=3)


@pytest.mark.parametrize("test_loop_scope", ("module", "package", "session"))
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_original_shared_loop_is_reinstated_not_fresh_loop(
pytester: Pytester,
test_loop_scope: str,
loop_breaking_action: str,
):
pytester.makeini(
dedent(
f"""\
[pytest]
asyncio_default_test_loop_scope = {test_loop_scope}
asyncio_default_fixture_loop_scope = function
"""
)
)
pytester.makepyfile(
dedent(
f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
original_shared_loop: asyncio.AbstractEventLoop = None
@pytest.mark.asyncio
async def test_store_original_shared_loop():
global original_shared_loop
original_shared_loop = asyncio.get_running_loop()
original_shared_loop._custom_marker = "original_loop_marker"
def test_unset_event_loop():
{loop_breaking_action}
@pytest.mark.asyncio
async def test_verify_original_loop_reinstated():
global original_shared_loop
current_loop = asyncio.get_running_loop()
assert current_loop is original_shared_loop
assert hasattr(current_loop, '_custom_marker')
assert current_loop._custom_marker == "original_loop_marker"
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result.assert_outcomes(passed=3)


@pytest.mark.parametrize("test_loop_scope", ("module", "package", "session"))
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_shared_loop_with_fixture_preservation(
pytester: Pytester,
test_loop_scope: str,
loop_breaking_action: str,
):
pytester.makeini(
dedent(
f"""\
[pytest]
asyncio_default_test_loop_scope = {test_loop_scope}
asyncio_default_fixture_loop_scope = {test_loop_scope}
"""
)
)
pytester.makepyfile(
dedent(
f"""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = "pytest_asyncio"
fixture_loop: asyncio.AbstractEventLoop = None
long_running_task = None
@pytest_asyncio.fixture
async def webserver():
global fixture_loop, long_running_task
fixture_loop = asyncio.get_running_loop()
async def background_task():
while True:
await asyncio.sleep(1)
long_running_task = asyncio.create_task(background_task())
yield
long_running_task.cancel()
@pytest.mark.asyncio
async def test_before(webserver):
global fixture_loop, long_running_task
assert asyncio.get_running_loop() is fixture_loop
assert not long_running_task.done()
def test_set_event_loop_none():
{loop_breaking_action}
@pytest.mark.asyncio
async def test_after(webserver):
global fixture_loop, long_running_task
current_loop = asyncio.get_running_loop()
assert current_loop is fixture_loop
assert not long_running_task.done()
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result.assert_outcomes(passed=3)


@pytest.mark.parametrize(
"first_scope,second_scope",
[
("module", "session"),
("session", "module"),
("package", "session"),
("session", "package"),
("package", "module"),
("module", "package"),
],
)
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_shared_loop_with_multiple_fixtures_preservation(
pytester: Pytester,
first_scope: str,
second_scope: str,
loop_breaking_action: str,
):
pytester.makeini(
dedent(
"""\
[pytest]
asyncio_default_test_loop_scope = session
asyncio_default_fixture_loop_scope = session
"""
)
)
pytester.makepyfile(
dedent(
f"""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = "pytest_asyncio"
first_fixture_loop: asyncio.AbstractEventLoop = None
second_fixture_loop: asyncio.AbstractEventLoop = None
first_long_running_task = None
second_long_running_task = None
@pytest_asyncio.fixture(scope="{first_scope}", loop_scope="{first_scope}")
async def first_webserver():
global first_fixture_loop, first_long_running_task
first_fixture_loop = asyncio.get_running_loop()
async def background_task():
while True:
await asyncio.sleep(0.1)
first_long_running_task = asyncio.create_task(background_task())
yield
first_long_running_task.cancel()
@pytest_asyncio.fixture(scope="{second_scope}", loop_scope="{second_scope}")
async def second_webserver():
global second_fixture_loop, second_long_running_task
second_fixture_loop = asyncio.get_running_loop()
async def background_task():
while True:
await asyncio.sleep(0.1)
second_long_running_task = asyncio.create_task(background_task())
yield
second_long_running_task.cancel()
@pytest.mark.asyncio(loop_scope="{first_scope}")
async def test_before_first(first_webserver):
global first_fixture_loop, first_long_running_task
assert asyncio.get_running_loop() is first_fixture_loop
assert not first_long_running_task.done()
@pytest.mark.asyncio(loop_scope="{second_scope}")
async def test_before_second(second_webserver):
global second_fixture_loop, second_long_running_task
assert asyncio.get_running_loop() is second_fixture_loop
assert not second_long_running_task.done()
def test_set_event_loop_none():
{loop_breaking_action}
@pytest.mark.asyncio(loop_scope="{first_scope}")
async def test_after_first(first_webserver):
global first_fixture_loop, first_long_running_task
current_loop = asyncio.get_running_loop()
assert current_loop is first_fixture_loop
assert not first_long_running_task.done()
@pytest.mark.asyncio(loop_scope="{second_scope}")
async def test_after_second(second_webserver):
global second_fixture_loop, second_long_running_task
current_loop = asyncio.get_running_loop()
assert current_loop is second_fixture_loop
assert not second_long_running_task.done()
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result.assert_outcomes(passed=5)
7 changes: 0 additions & 7 deletions tests/test_simple.py
Original file line number Diff line number Diff line change
@@ -14,13 +14,6 @@ async def async_coro():
return "ok"


def test_event_loop_fixture(event_loop):
"""Test the injection of the event_loop fixture."""
assert event_loop
ret = event_loop.run_until_complete(async_coro())
assert ret == "ok"


@pytest.mark.asyncio
async def test_asyncio_marker():
"""Test the asyncio pytest marker."""
10 changes: 0 additions & 10 deletions tests/test_subprocess.py
Original file line number Diff line number Diff line change
@@ -7,16 +7,6 @@

import pytest

if sys.platform == "win32":
# The default asyncio event loop implementation on Windows does not
# support subprocesses. Subprocesses are available for Windows if a
# ProactorEventLoop is used.
@pytest.fixture()
def event_loop():
loop = asyncio.ProactorEventLoop()
yield loop
loop.close()


@pytest.mark.asyncio
async def test_subprocess():
Loading