|
4 | 4 | import enum |
5 | 5 | import functools |
6 | 6 | import inspect |
7 | | -import os |
8 | 7 | import socket |
9 | 8 | import sys |
10 | 9 | import warnings |
11 | 10 | from asyncio import AbstractEventLoopPolicy |
12 | | -from pathlib import Path |
13 | | -from tempfile import NamedTemporaryFile |
14 | 11 | from textwrap import dedent |
15 | 12 | from typing import ( |
16 | 13 | Any, |
|
31 | 28 | ) |
32 | 29 |
|
33 | 30 | import pytest |
34 | | -from _pytest.pathlib import visit |
35 | 31 | from pytest import ( |
36 | 32 | Class, |
37 | 33 | Collector, |
@@ -627,100 +623,28 @@ def _patched_collect(): |
627 | 623 | collector.__original_collect = collector.collect |
628 | 624 | collector.collect = _patched_collect |
629 | 625 | elif type(collector) is Package: |
630 | | - if not collector.funcnamefilter(collector.name): |
631 | | - return |
632 | 626 |
|
633 | 627 | def _patched_collect(): |
634 | | - # pytest.Package collects all files and sub-packages. Pytest 8 changes |
635 | | - # this logic to only collect a single directory. Sub-packages are then |
636 | | - # collected by a separate Package collector. Therefore, this logic can be |
637 | | - # dropped, once we move to pytest 8. |
638 | | - collector_dir = Path(collector.path.parent) |
639 | | - for direntry in visit(str(collector_dir), recurse=collector._recurse): |
640 | | - if not direntry.name == "__init__.py": |
641 | | - # No need to register a package-scoped fixture, if we aren't |
642 | | - # collecting a (sub-)package |
643 | | - continue |
644 | | - pkgdir = Path(direntry.path).parent |
645 | | - pkg_nodeid = str(pkgdir.relative_to(collector_dir)) |
646 | | - if pkg_nodeid == ".": |
647 | | - pkg_nodeid = "" |
648 | | - # Pytest's fixture matching algorithm compares a fixture's baseid with |
649 | | - # an Item's nodeid to determine whether a fixture is available for a |
650 | | - # specific Item. Package.nodeid ends with __init__.py, so the |
651 | | - # fixture's baseid will also end with __init__.py and prevents |
652 | | - # the fixture from being matched to test items in the package. |
653 | | - # Furthermore, Package also collects any sub-packages, which means |
654 | | - # the ID of the scoped event loop for the package must change for |
655 | | - # each sub-package. |
656 | | - # As the fixture matching is purely based on string comparison, we |
657 | | - # can assemble a path based on the root package path |
658 | | - # (i.e. Package.path.parent) and the sub-package path |
659 | | - # (i.e. Path(direntry.path).parent)). This makes the fixture visible |
660 | | - # to all items in the package. |
661 | | - # see also https://github.com/pytest-dev/pytest/issues/11662#issuecomment-1879310072 # noqa |
662 | | - # Possibly related to https://github.com/pytest-dev/pytest/issues/4085 |
663 | | - fixture_id = f"{pkg_nodeid}/__init__.py::<event_loop>".lstrip("/") |
664 | | - # When collector is a Package, collector.obj is the package's |
665 | | - # __init__.py. Accessing the __init__.py to attach the fixture function |
666 | | - # may trigger additional module imports or change the order of imports, |
667 | | - # which leads to a number of problems. |
668 | | - # see https://github.com/pytest-dev/pytest-asyncio/issues/729 |
669 | | - # Moreover, Package.obj has been removed in pytest 8. |
670 | | - # Therefore, pytest-asyncio creates a temporary Python module inside the |
671 | | - # collected package. The sole purpose of that module is to house a |
672 | | - # fixture function for the pacakge-scoped event loop fixture. Once the |
673 | | - # fixture has been evaluated by pytest, the temporary module |
674 | | - # can be removed. |
675 | | - with NamedTemporaryFile( |
676 | | - dir=pkgdir, |
677 | | - prefix="pytest_asyncio_virtual_module_", |
678 | | - suffix=".py", |
679 | | - delete=False, # Required for Windows compatibility |
680 | | - ) as virtual_module_file: |
681 | | - virtual_module = Module.from_parent( |
682 | | - collector, path=Path(virtual_module_file.name) |
683 | | - ) |
684 | | - virtual_module_file.write( |
685 | | - dedent( |
686 | | - f"""\ |
687 | | - # This is a temporary file created by pytest-asyncio |
688 | | - # If you see this file, a pytest run has crashed and |
689 | | - # wasn't able to clean up the file in time. |
690 | | - # You can safely remove this file. |
691 | | - import asyncio |
692 | | - import pytest |
693 | | - from pytest_asyncio.plugin \ |
694 | | - import _temporary_event_loop_policy |
695 | | - @pytest.fixture( |
696 | | - scope="{collector_scope}", |
697 | | - name="{fixture_id}", |
698 | | - ) |
699 | | - def scoped_event_loop( |
700 | | - *args, |
701 | | - event_loop_policy, |
702 | | - ): |
703 | | - new_loop_policy = event_loop_policy |
704 | | - with _temporary_event_loop_policy(new_loop_policy): |
705 | | - loop = asyncio.new_event_loop() |
706 | | - loop.__pytest_asyncio = True |
707 | | - asyncio.set_event_loop(loop) |
708 | | - yield loop |
709 | | - loop.close() |
710 | | - """ |
711 | | - ).encode() |
712 | | - ) |
713 | | - virtual_module_file.flush() |
714 | | - fixturemanager = collector.config.pluginmanager.get_plugin( |
715 | | - "funcmanage" |
| 628 | + # When collector is a Package, collector.obj is the package's |
| 629 | + # __init__.py. Accessing the __init__.py to attach the fixture function |
| 630 | + # may trigger additional module imports or change the order of imports, |
| 631 | + # which leads to a number of problems. |
| 632 | + # see https://github.com/pytest-dev/pytest-asyncio/issues/729 |
| 633 | + # Moreover, Package.obj has been removed in pytest 8. |
| 634 | + # Therefore, pytest-asyncio attaches the packages-scoped event loop |
| 635 | + # fixture to the first collected module in that package. |
| 636 | + package_scoped_loop_added = False |
| 637 | + for subcollector in collector.__original_collect(): |
| 638 | + if ( |
| 639 | + not package_scoped_loop_added |
| 640 | + and isinstance(subcollector, Module) |
| 641 | + and getattr(subcollector, "obj", None) |
| 642 | + ): |
| 643 | + subcollector.obj.__pytest_asyncio_package_scoped_event_loop = ( |
| 644 | + scoped_event_loop |
716 | 645 | ) |
717 | | - # Collect the fixtures in the virtual module with the node ID of |
718 | | - # the current sub-package to ensure correct fixture matching. |
719 | | - # see also https://github.com/pytest-dev/pytest/issues/11662#issuecomment-1879310072 # noqa |
720 | | - fixturemanager.parsefactories(virtual_module.obj, nodeid=pkg_nodeid) |
721 | | - yield virtual_module |
722 | | - os.unlink(virtual_module_file.name) |
723 | | - yield from collector.__original_collect() |
| 646 | + package_scoped_loop_added = True |
| 647 | + yield subcollector |
724 | 648 |
|
725 | 649 | collector.__original_collect = collector.collect |
726 | 650 | collector.collect = _patched_collect |
|
0 commit comments