diff options
author | robot-piglet <[email protected]> | 2024-10-06 13:42:43 +0300 |
---|---|---|
committer | robot-piglet <[email protected]> | 2024-10-06 13:52:30 +0300 |
commit | 52aed29f744afda4549ef5d64acd0fa8c2092789 (patch) | |
tree | e40c9abd25653990d13b68936aee518454df424e /contrib/python/pytest-asyncio | |
parent | 813943fcad905eee1235d764be4268dddd07ce64 (diff) |
Intermediate changes
commit_hash:cc4365f5a0e443b92d87079a9c91e77fea2ddcaf
Diffstat (limited to 'contrib/python/pytest-asyncio')
-rw-r--r-- | contrib/python/pytest-asyncio/.dist-info/METADATA | 91 | ||||
-rw-r--r-- | contrib/python/pytest-asyncio/.dist-info/entry_points.txt | 2 | ||||
-rw-r--r-- | contrib/python/pytest-asyncio/.dist-info/top_level.txt | 1 | ||||
-rw-r--r-- | contrib/python/pytest-asyncio/LICENSE | 201 | ||||
-rw-r--r-- | contrib/python/pytest-asyncio/README.rst | 52 | ||||
-rw-r--r-- | contrib/python/pytest-asyncio/pytest_asyncio/__init__.py | 5 | ||||
-rw-r--r-- | contrib/python/pytest-asyncio/pytest_asyncio/_version.py | 4 | ||||
-rw-r--r-- | contrib/python/pytest-asyncio/pytest_asyncio/plugin.py | 624 | ||||
-rw-r--r-- | contrib/python/pytest-asyncio/pytest_asyncio/py.typed | 0 | ||||
-rw-r--r-- | contrib/python/pytest-asyncio/ya.make | 30 |
10 files changed, 1010 insertions, 0 deletions
diff --git a/contrib/python/pytest-asyncio/.dist-info/METADATA b/contrib/python/pytest-asyncio/.dist-info/METADATA new file mode 100644 index 00000000000..c73b027ba51 --- /dev/null +++ b/contrib/python/pytest-asyncio/.dist-info/METADATA @@ -0,0 +1,91 @@ +Metadata-Version: 2.1 +Name: pytest-asyncio +Version: 0.21.1 +Summary: Pytest support for asyncio +Home-page: https://github.com/pytest-dev/pytest-asyncio +Author: Tin Tvrtković <[email protected]> +Author-email: [email protected] +License: Apache 2.0 +Project-URL: Documentation, https://pytest-asyncio.readthedocs.io +Project-URL: Changelog, https://pytest-asyncio.readthedocs.io/en/latest/reference/changelog.html +Project-URL: Source Code, https://github.com/pytest-dev/pytest-asyncio +Project-URL: Bug Tracker, https://github.com/pytest-dev/pytest-asyncio/issues +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Topic :: Software Development :: Testing +Classifier: Framework :: AsyncIO +Classifier: Framework :: Pytest +Classifier: Typing :: Typed +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: pytest (>=7.0.0) +Requires-Dist: typing-extensions (>=3.7.2) ; python_version < "3.8" +Provides-Extra: docs +Requires-Dist: sphinx (>=5.3) ; extra == 'docs' +Requires-Dist: sphinx-rtd-theme (>=1.0) ; extra == 'docs' +Provides-Extra: testing +Requires-Dist: coverage (>=6.2) ; extra == 'testing' +Requires-Dist: hypothesis (>=5.7.1) ; extra == 'testing' +Requires-Dist: flaky (>=3.5.0) ; extra == 'testing' +Requires-Dist: mypy (>=0.931) ; extra == 'testing' +Requires-Dist: pytest-trio (>=0.7.0) ; extra == 'testing' + +pytest-asyncio +============== + +.. image:: https://img.shields.io/pypi/v/pytest-asyncio.svg + :target: https://pypi.python.org/pypi/pytest-asyncio +.. image:: https://github.com/pytest-dev/pytest-asyncio/workflows/CI/badge.svg + :target: https://github.com/pytest-dev/pytest-asyncio/actions?workflow=CI +.. image:: https://codecov.io/gh/pytest-dev/pytest-asyncio/branch/main/graph/badge.svg + :target: https://codecov.io/gh/pytest-dev/pytest-asyncio +.. image:: https://img.shields.io/pypi/pyversions/pytest-asyncio.svg + :target: https://github.com/pytest-dev/pytest-asyncio + :alt: Supported Python versions +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + +`pytest-asyncio <https://pytest-asyncio.readthedocs.io/en/latest/>`_ is a `pytest <https://docs.pytest.org/en/latest/contents.html>`_ plugin. It facilitates testing of code that uses the `asyncio <https://docs.python.org/3/library/asyncio.html>`_ library. + +Specifically, pytest-asyncio provides support for coroutines as test functions. This allows users to *await* code inside their tests. For example, the following code is executed as a test item by pytest: + +.. code-block:: python + + @pytest.mark.asyncio + async def test_some_asyncio_code(): + res = await library.do_something() + assert b"expected result" == res + +More details can be found in the `documentation <https://pytest-asyncio.readthedocs.io/en/latest/>`_. + +Note that test classes subclassing the standard `unittest <https://docs.python.org/3/library/unittest.html>`__ library are not supported. Users +are advised to use `unittest.IsolatedAsyncioTestCase <https://docs.python.org/3/library/unittest.html#unittest.IsolatedAsyncioTestCase>`__ +or an async framework such as `asynctest <https://asynctest.readthedocs.io/en/latest>`__. + + +pytest-asyncio is available under the `Apache License 2.0 <https://github.com/pytest-dev/pytest-asyncio/blob/main/LICENSE>`_. + + +Installation +------------ + +To install pytest-asyncio, simply: + +.. code-block:: bash + + $ pip install pytest-asyncio + +This is enough for pytest to pick up pytest-asyncio. + + +Contributing +------------ +Contributions are very welcome. Tests can be run with ``tox``, please ensure +the coverage at least stays the same before you submit a pull request. diff --git a/contrib/python/pytest-asyncio/.dist-info/entry_points.txt b/contrib/python/pytest-asyncio/.dist-info/entry_points.txt new file mode 100644 index 00000000000..88db714dad5 --- /dev/null +++ b/contrib/python/pytest-asyncio/.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +asyncio = pytest_asyncio.plugin diff --git a/contrib/python/pytest-asyncio/.dist-info/top_level.txt b/contrib/python/pytest-asyncio/.dist-info/top_level.txt new file mode 100644 index 00000000000..08d05d1ecf4 --- /dev/null +++ b/contrib/python/pytest-asyncio/.dist-info/top_level.txt @@ -0,0 +1 @@ +pytest_asyncio diff --git a/contrib/python/pytest-asyncio/LICENSE b/contrib/python/pytest-asyncio/LICENSE new file mode 100644 index 00000000000..5c304d1a4a7 --- /dev/null +++ b/contrib/python/pytest-asyncio/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contrib/python/pytest-asyncio/README.rst b/contrib/python/pytest-asyncio/README.rst new file mode 100644 index 00000000000..0682b744302 --- /dev/null +++ b/contrib/python/pytest-asyncio/README.rst @@ -0,0 +1,52 @@ +pytest-asyncio +============== + +.. image:: https://img.shields.io/pypi/v/pytest-asyncio.svg + :target: https://pypi.python.org/pypi/pytest-asyncio +.. image:: https://github.com/pytest-dev/pytest-asyncio/workflows/CI/badge.svg + :target: https://github.com/pytest-dev/pytest-asyncio/actions?workflow=CI +.. image:: https://codecov.io/gh/pytest-dev/pytest-asyncio/branch/main/graph/badge.svg + :target: https://codecov.io/gh/pytest-dev/pytest-asyncio +.. image:: https://img.shields.io/pypi/pyversions/pytest-asyncio.svg + :target: https://github.com/pytest-dev/pytest-asyncio + :alt: Supported Python versions +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + +`pytest-asyncio <https://pytest-asyncio.readthedocs.io/en/latest/>`_ is a `pytest <https://docs.pytest.org/en/latest/contents.html>`_ plugin. It facilitates testing of code that uses the `asyncio <https://docs.python.org/3/library/asyncio.html>`_ library. + +Specifically, pytest-asyncio provides support for coroutines as test functions. This allows users to *await* code inside their tests. For example, the following code is executed as a test item by pytest: + +.. code-block:: python + + @pytest.mark.asyncio + async def test_some_asyncio_code(): + res = await library.do_something() + assert b"expected result" == res + +More details can be found in the `documentation <https://pytest-asyncio.readthedocs.io/en/latest/>`_. + +Note that test classes subclassing the standard `unittest <https://docs.python.org/3/library/unittest.html>`__ library are not supported. Users +are advised to use `unittest.IsolatedAsyncioTestCase <https://docs.python.org/3/library/unittest.html#unittest.IsolatedAsyncioTestCase>`__ +or an async framework such as `asynctest <https://asynctest.readthedocs.io/en/latest>`__. + + +pytest-asyncio is available under the `Apache License 2.0 <https://github.com/pytest-dev/pytest-asyncio/blob/main/LICENSE>`_. + + +Installation +------------ + +To install pytest-asyncio, simply: + +.. code-block:: bash + + $ pip install pytest-asyncio + +This is enough for pytest to pick up pytest-asyncio. + + +Contributing +------------ +Contributions are very welcome. Tests can be run with ``tox``, please ensure +the coverage at least stays the same before you submit a pull request. diff --git a/contrib/python/pytest-asyncio/pytest_asyncio/__init__.py b/contrib/python/pytest-asyncio/pytest_asyncio/__init__.py new file mode 100644 index 00000000000..1bc2811d93f --- /dev/null +++ b/contrib/python/pytest-asyncio/pytest_asyncio/__init__.py @@ -0,0 +1,5 @@ +"""The main point for importing pytest-asyncio items.""" +from ._version import version as __version__ # noqa +from .plugin import fixture + +__all__ = ("fixture",) diff --git a/contrib/python/pytest-asyncio/pytest_asyncio/_version.py b/contrib/python/pytest-asyncio/pytest_asyncio/_version.py new file mode 100644 index 00000000000..11f23015fb4 --- /dev/null +++ b/contrib/python/pytest-asyncio/pytest_asyncio/_version.py @@ -0,0 +1,4 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +__version__ = version = '0.21.1' +__version_tuple__ = version_tuple = (0, 21, 1) diff --git a/contrib/python/pytest-asyncio/pytest_asyncio/plugin.py b/contrib/python/pytest-asyncio/pytest_asyncio/plugin.py new file mode 100644 index 00000000000..db93b851de8 --- /dev/null +++ b/contrib/python/pytest-asyncio/pytest_asyncio/plugin.py @@ -0,0 +1,624 @@ +"""pytest-asyncio implementation.""" +import asyncio +import contextlib +import enum +import functools +import inspect +import socket +import sys +import warnings +from textwrap import dedent +from typing import ( + Any, + AsyncIterator, + Awaitable, + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Set, + TypeVar, + Union, + cast, + overload, +) + +import pytest +from pytest import ( + Config, + FixtureRequest, + Function, + Item, + Parser, + PytestPluginManager, + Session, +) + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +_R = TypeVar("_R") + +_ScopeName = Literal["session", "package", "module", "class", "function"] +_T = TypeVar("_T") + +SimpleFixtureFunction = TypeVar( + "SimpleFixtureFunction", bound=Callable[..., Awaitable[_R]] +) +FactoryFixtureFunction = TypeVar( + "FactoryFixtureFunction", bound=Callable[..., AsyncIterator[_R]] +) +FixtureFunction = Union[SimpleFixtureFunction, FactoryFixtureFunction] +FixtureFunctionMarker = Callable[[FixtureFunction], FixtureFunction] + +# https://github.com/pytest-dev/pytest/pull/9510 +FixtureDef = Any +SubRequest = Any + + +class Mode(str, enum.Enum): + AUTO = "auto" + STRICT = "strict" + + +ASYNCIO_MODE_HELP = """\ +'auto' - for automatically handling all async functions by the plugin +'strict' - for autoprocessing disabling (useful if different async frameworks \ +should be tested together, e.g. \ +both pytest-asyncio and pytest-trio are used in the same project) +""" + + +def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: + group = parser.getgroup("asyncio") + group.addoption( + "--asyncio-mode", + dest="asyncio_mode", + default=None, + metavar="MODE", + help=ASYNCIO_MODE_HELP, + ) + parser.addini( + "asyncio_mode", + help="default value for --asyncio-mode", + default="auto", + ) + + +@overload +def fixture( + fixture_function: FixtureFunction, + *, + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., + params: Optional[Iterable[object]] = ..., + autouse: bool = ..., + ids: Union[ + Iterable[Union[str, float, int, bool, None]], + Callable[[Any], Optional[object]], + None, + ] = ..., + name: Optional[str] = ..., +) -> FixtureFunction: + ... + + +@overload +def fixture( + fixture_function: None = ..., + *, + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., + params: Optional[Iterable[object]] = ..., + autouse: bool = ..., + ids: Union[ + Iterable[Union[str, float, int, bool, None]], + Callable[[Any], Optional[object]], + None, + ] = ..., + name: Optional[str] = None, +) -> FixtureFunctionMarker: + ... + + +def fixture( + fixture_function: Optional[FixtureFunction] = None, **kwargs: Any +) -> Union[FixtureFunction, FixtureFunctionMarker]: + if fixture_function is not None: + _make_asyncio_fixture_function(fixture_function) + return pytest.fixture(fixture_function, **kwargs) + + else: + + @functools.wraps(fixture) + def inner(fixture_function: FixtureFunction) -> FixtureFunction: + return fixture(fixture_function, **kwargs) + + return inner + + +def _is_asyncio_fixture_function(obj: Any) -> bool: + obj = getattr(obj, "__func__", obj) # instance method maybe? + return getattr(obj, "_force_asyncio_fixture", False) + + +def _make_asyncio_fixture_function(obj: Any) -> None: + if hasattr(obj, "__func__"): + # instance method, check the function object + obj = obj.__func__ + obj._force_asyncio_fixture = True + + +def _is_coroutine(obj: Any) -> bool: + """Check to see if an object is really an asyncio coroutine.""" + return asyncio.iscoroutinefunction(obj) + + +def _is_coroutine_or_asyncgen(obj: Any) -> bool: + return _is_coroutine(obj) or inspect.isasyncgenfunction(obj) + + +def _get_asyncio_mode(config: Config) -> Mode: + val = config.getoption("asyncio_mode") + if val is None: + val = config.getini("asyncio_mode") + try: + return Mode(val) + except ValueError: + modes = ", ".join(m.value for m in Mode) + raise pytest.UsageError( + f"{val!r} is not a valid asyncio_mode. Valid modes: {modes}." + ) + + +def pytest_configure(config: Config) -> None: + """Inject documentation.""" + config.addinivalue_line( + "markers", + "asyncio: " + "mark the test as a coroutine, it will be " + "run using an asyncio event loop", + ) + + [email protected](tryfirst=True) +def pytest_report_header(config: Config) -> List[str]: + """Add asyncio config to pytest header.""" + mode = _get_asyncio_mode(config) + return [f"asyncio: mode={mode}"] + + +def _preprocess_async_fixtures( + config: Config, + processed_fixturedefs: Set[FixtureDef], +) -> None: + asyncio_mode = _get_asyncio_mode(config) + fixturemanager = config.pluginmanager.get_plugin("funcmanage") + for fixtures in fixturemanager._arg2fixturedefs.values(): + for fixturedef in fixtures: + func = fixturedef.func + if fixturedef in processed_fixturedefs or not _is_coroutine_or_asyncgen( + func + ): + continue + if not _is_asyncio_fixture_function(func) and asyncio_mode == Mode.STRICT: + # Ignore async fixtures without explicit asyncio mark in strict mode + # This applies to pytest_trio fixtures, for example + continue + _make_asyncio_fixture_function(func) + _inject_fixture_argnames(fixturedef) + _synchronize_async_fixture(fixturedef) + assert _is_asyncio_fixture_function(fixturedef.func) + processed_fixturedefs.add(fixturedef) + + +def _inject_fixture_argnames(fixturedef: FixtureDef) -> None: + """ + Ensures that `request` and `event_loop` are arguments of the specified fixture. + """ + to_add = [] + for name in ("request", "event_loop"): + if name not in fixturedef.argnames: + to_add.append(name) + if to_add: + fixturedef.argnames += tuple(to_add) + + +def _synchronize_async_fixture(fixturedef: FixtureDef) -> None: + """ + Wraps the fixture function of an async fixture in a synchronous function. + """ + if inspect.isasyncgenfunction(fixturedef.func): + _wrap_asyncgen_fixture(fixturedef) + elif inspect.iscoroutinefunction(fixturedef.func): + _wrap_async_fixture(fixturedef) + + +def _add_kwargs( + func: Callable[..., Any], + kwargs: Dict[str, Any], + event_loop: asyncio.AbstractEventLoop, + request: SubRequest, +) -> Dict[str, Any]: + sig = inspect.signature(func) + ret = kwargs.copy() + if "request" in sig.parameters: + ret["request"] = request + if "event_loop" in sig.parameters: + ret["event_loop"] = event_loop + return ret + + +def _perhaps_rebind_fixture_func( + func: _T, instance: Optional[Any], unittest: bool +) -> _T: + if instance is not None: + # The fixture needs to be bound to the actual request.instance + # so it is bound to the same object as the test method. + unbound, cls = func, None + try: + unbound, cls = func.__func__, type(func.__self__) # type: ignore + except AttributeError: + pass + # If unittest is true, the fixture is bound unconditionally. + # otherwise, only if the fixture was bound before to an instance of + # the same type. + if unittest or (cls is not None and isinstance(instance, cls)): + func = unbound.__get__(instance) # type: ignore + return func + + +def _wrap_asyncgen_fixture(fixturedef: FixtureDef) -> None: + fixture = fixturedef.func + + @functools.wraps(fixture) + def _asyncgen_fixture_wrapper( + event_loop: asyncio.AbstractEventLoop, request: SubRequest, **kwargs: Any + ): + func = _perhaps_rebind_fixture_func( + fixture, request.instance, fixturedef.unittest + ) + gen_obj = func(**_add_kwargs(func, kwargs, event_loop, request)) + + async def setup(): + res = await gen_obj.__anext__() + return res + + def finalizer() -> None: + """Yield again, to finalize.""" + + async def async_finalizer() -> None: + try: + await gen_obj.__anext__() + except StopAsyncIteration: + pass + else: + msg = "Async generator fixture didn't stop." + msg += "Yield only once." + raise ValueError(msg) + + event_loop.run_until_complete(async_finalizer()) + + result = event_loop.run_until_complete(setup()) + request.addfinalizer(finalizer) + return result + + fixturedef.func = _asyncgen_fixture_wrapper + + +def _wrap_async_fixture(fixturedef: FixtureDef) -> None: + fixture = fixturedef.func + + @functools.wraps(fixture) + def _async_fixture_wrapper( + event_loop: asyncio.AbstractEventLoop, request: SubRequest, **kwargs: Any + ): + func = _perhaps_rebind_fixture_func( + fixture, request.instance, fixturedef.unittest + ) + + async def setup(): + res = await func(**_add_kwargs(func, kwargs, event_loop, request)) + return res + + return event_loop.run_until_complete(setup()) + + fixturedef.func = _async_fixture_wrapper + + +_HOLDER: Set[FixtureDef] = set() + + [email protected](tryfirst=True) +def pytest_pycollect_makeitem( + collector: Union[pytest.Module, pytest.Class], name: str, obj: object +) -> Union[ + pytest.Item, pytest.Collector, List[Union[pytest.Item, pytest.Collector]], None +]: + """A pytest hook to collect asyncio coroutines.""" + if not collector.funcnamefilter(name): + return None + _preprocess_async_fixtures(collector.config, _HOLDER) + return None + + +def pytest_collection_modifyitems( + session: Session, config: Config, items: List[Item] +) -> None: + """ + Marks collected async test items as `asyncio` tests. + + The mark is only applied in `AUTO` mode. It is applied to: + + - coroutines + - staticmethods wrapping coroutines + - Hypothesis tests wrapping coroutines + + """ + if _get_asyncio_mode(config) != Mode.AUTO: + return + function_items = (item for item in items if isinstance(item, Function)) + for function_item in function_items: + function = function_item.obj + if isinstance(function, staticmethod): + # staticmethods need to be unwrapped. + function = function.__func__ + if ( + _is_coroutine(function) + or _is_hypothesis_test(function) + and _hypothesis_test_wraps_coroutine(function) + ): + function_item.add_marker("asyncio") + + +def _hypothesis_test_wraps_coroutine(function: Any) -> bool: + return _is_coroutine(function.hypothesis.inner_test) + + [email protected](hookwrapper=True) +def pytest_fixture_setup( + fixturedef: FixtureDef, request: SubRequest +) -> Optional[object]: + """Adjust the event loop policy when an event loop is produced.""" + if fixturedef.argname == "event_loop": + # The use of a fixture finalizer is preferred over the + # pytest_fixture_post_finalizer hook. The fixture finalizer is invoked once + # for each fixture, whereas the hook may be invoked multiple times for + # any specific fixture. + # see https://github.com/pytest-dev/pytest/issues/5848 + _add_finalizers( + fixturedef, + _close_event_loop, + _provide_clean_event_loop, + ) + outcome = yield + loop = outcome.get_result() + policy = asyncio.get_event_loop_policy() + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + old_loop = policy.get_event_loop() + if old_loop is not loop: + old_loop.close() + except RuntimeError: + # Either the current event loop has been set to None + # or the loop policy doesn't specify to create new loops + # or we're not in the main thread + pass + policy.set_event_loop(loop) + return + + yield + + +def _add_finalizers(fixturedef: FixtureDef, *finalizers: Callable[[], object]) -> None: + """ + Regsiters the specified fixture finalizers in the fixture. + + Finalizers need to specified in the exact order in which they should be invoked. + + :param fixturedef: Fixture definition which finalizers should be added to + :param finalizers: Finalizers to be added + """ + for finalizer in reversed(finalizers): + fixturedef.addfinalizer(finalizer) + + +_UNCLOSED_EVENT_LOOP_WARNING = dedent( + """\ + pytest-asyncio detected an unclosed event loop when tearing down the event_loop + fixture: %r + pytest-asyncio will close the event loop for you, but future versions of the + library will no longer do so. In order to ensure compatibility with future + versions, please make sure that: + 1. Any custom "event_loop" fixture properly closes the loop after yielding it + 2. The scopes of your custom "event_loop" fixtures do not overlap + 3. Your code does not modify the event loop in async fixtures or tests + """ +) + + +def _close_event_loop() -> None: + policy = asyncio.get_event_loop_policy() + try: + loop = policy.get_event_loop() + except RuntimeError: + loop = None + if loop is not None: + if not loop.is_closed(): + warnings.warn( + _UNCLOSED_EVENT_LOOP_WARNING % loop, + DeprecationWarning, + ) + loop.close() + + +def _provide_clean_event_loop() -> None: + # At this point, the event loop for the current thread is closed. + # When a user calls asyncio.get_event_loop(), they will get a closed loop. + # In order to avoid this side effect from pytest-asyncio, we need to replace + # the current loop with a fresh one. + # Note that we cannot set the loop to None, because get_event_loop only creates + # a new loop, when set_event_loop has not been called. + policy = asyncio.get_event_loop_policy() + new_loop = policy.new_event_loop() + policy.set_event_loop(new_loop) + + [email protected](tryfirst=True, hookwrapper=True) +def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> Optional[object]: + """ + Pytest hook called before a test case is run. + + Wraps marked tests in a synchronous function + where the wrapped test coroutine is executed in an event loop. + """ + marker = pyfuncitem.get_closest_marker("asyncio") + if marker is not None: + funcargs: Dict[str, object] = pyfuncitem.funcargs # type: ignore[name-defined] + loop = cast(asyncio.AbstractEventLoop, funcargs["event_loop"]) + if _is_hypothesis_test(pyfuncitem.obj): + pyfuncitem.obj.hypothesis.inner_test = wrap_in_sync( + pyfuncitem, + pyfuncitem.obj.hypothesis.inner_test, + _loop=loop, + ) + else: + pyfuncitem.obj = wrap_in_sync( + pyfuncitem, + pyfuncitem.obj, + _loop=loop, + ) + yield + + +def _is_hypothesis_test(function: Any) -> bool: + return getattr(function, "is_hypothesis_test", False) + + +def wrap_in_sync( + pyfuncitem: pytest.Function, + func: Callable[..., Awaitable[Any]], + _loop: asyncio.AbstractEventLoop, +): + """Return a sync wrapper around an async function executing it in the + current event loop.""" + + # if the function is already wrapped, we rewrap using the original one + # not using __wrapped__ because the original function may already be + # a wrapped one + raw_func = getattr(func, "_raw_test_func", None) + if raw_func is not None: + func = raw_func + + @functools.wraps(func) + def inner(*args, **kwargs): + coro = func(*args, **kwargs) + if not inspect.isawaitable(coro): + pyfuncitem.warn( + pytest.PytestWarning( + f"The test {pyfuncitem} is marked with '@pytest.mark.asyncio' " + "but it is not an async function. " + "Please remove asyncio marker. " + "If the test is not marked explicitly, " + "check for global markers applied via 'pytestmark'." + ) + ) + return + task = asyncio.ensure_future(coro, loop=_loop) + try: + return _loop.run_until_complete(task) + except BaseException: + # run_until_complete doesn't get the result from exceptions + # that are not subclasses of `Exception`. Consume all + # exceptions to prevent asyncio's warning from logging. + if task.done() and not task.cancelled(): + task.exception() + raise + + inner._raw_test_func = func # type: ignore[attr-defined] + return inner + + +def pytest_runtest_setup(item: pytest.Item) -> None: + marker = item.get_closest_marker("asyncio") + if marker is None: + return + fixturenames = item.fixturenames # type: ignore[attr-defined] + # inject an event loop fixture for all async tests + if "event_loop" in fixturenames: + fixturenames.remove("event_loop") + fixturenames.insert(0, "event_loop") + obj = getattr(item, "obj", None) + if not getattr(obj, "hypothesis", False) and getattr( + obj, "is_hypothesis_test", False + ): + pytest.fail( + "test function `%r` is using Hypothesis, but pytest-asyncio " + "only works with Hypothesis 3.64.0 or later." % item + ) + + +def event_loop(request: FixtureRequest) -> Iterator[asyncio.AbstractEventLoop]: + """Create an instance of the default event loop for each test case.""" + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +def _unused_port(socket_type: int) -> int: + """Find an unused localhost port from 1024-65535 and return it.""" + with contextlib.closing(socket.socket(type=socket_type)) as sock: + sock.bind(("127.0.0.1", 0)) + return sock.getsockname()[1] + + +def unused_tcp_port() -> int: + return _unused_port(socket.SOCK_STREAM) + + +def unused_udp_port() -> int: + return _unused_port(socket.SOCK_DGRAM) + + [email protected](scope="session") +def unused_tcp_port_factory() -> Callable[[], int]: + """A factory function, producing different unused TCP ports.""" + produced = set() + + def factory(): + """Return an unused port.""" + port = _unused_port(socket.SOCK_STREAM) + + while port in produced: + port = _unused_port(socket.SOCK_STREAM) + + produced.add(port) + + return port + + return factory + + [email protected](scope="session") +def unused_udp_port_factory() -> Callable[[], int]: + """A factory function, producing different unused UDP ports.""" + produced = set() + + def factory(): + """Return an unused port.""" + port = _unused_port(socket.SOCK_DGRAM) + + while port in produced: + port = _unused_port(socket.SOCK_DGRAM) + + produced.add(port) + + return port + + return factory diff --git a/contrib/python/pytest-asyncio/pytest_asyncio/py.typed b/contrib/python/pytest-asyncio/pytest_asyncio/py.typed new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/contrib/python/pytest-asyncio/pytest_asyncio/py.typed diff --git a/contrib/python/pytest-asyncio/ya.make b/contrib/python/pytest-asyncio/ya.make new file mode 100644 index 00000000000..e3918ea4b02 --- /dev/null +++ b/contrib/python/pytest-asyncio/ya.make @@ -0,0 +1,30 @@ +# Generated by devtools/yamaker (pypi). + +PY3_LIBRARY() + +VERSION(0.21.1) + +LICENSE(Apache-2.0) + +PEERDIR( + contrib/python/pytest +) + +NO_LINT() + +PY_SRCS( + TOP_LEVEL + pytest_asyncio/__init__.py + pytest_asyncio/_version.py + pytest_asyncio/plugin.py +) + +RESOURCE_FILES( + PREFIX contrib/python/pytest-asyncio/ + .dist-info/METADATA + .dist-info/entry_points.txt + .dist-info/top_level.txt + pytest_asyncio/py.typed +) + +END() |