././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1710815530.4068553 pytest-twisted-1.14.1/0000755000000000000000000000000014576174452013364 5ustar00rootroot././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815523.0 pytest-twisted-1.14.1/LICENSE0000644000000000000000000000306114576174443014371 0ustar00rootrootBSD 3-Clause License Copyright (c) 2012, Ralf Schmitt Copyright (c) 2018, Victor Titor Copyright (c) 2019-2020, Kyle Altendorf All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815523.0 pytest-twisted-1.14.1/MANIFEST.in0000644000000000000000000000027614576174443015127 0ustar00rootrootinclude MANIFEST.in include LICENSE include README.rst include pytest_twisted.py include setup.cfg include setup.py include testing/conftest.py include testing/test_basic.py include tox.ini ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1710815530.4068553 pytest-twisted-1.14.1/PKG-INFO0000644000000000000000000002273314576174452014470 0ustar00rootrootMetadata-Version: 2.1 Name: pytest-twisted Version: 1.14.1 Summary: A twisted plugin for pytest. Home-page: https://github.com/pytest-dev/pytest-twisted Author: Ralf Schmitt, Kyle Altendorf, Victor Titor Author-email: sda@fstab.net Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Testing Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.* Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: greenlet Requires-Dist: pytest>=2.3 Requires-Dist: decorator Provides-Extra: dev Requires-Dist: pre-commit; extra == "dev" Requires-Dist: black; extra == "dev" Provides-Extra: pyside2 Requires-Dist: qt5reactor[pyside2]>=0.6.3; extra == "pyside2" Provides-Extra: pyqt5 Requires-Dist: qt5reactor[pyqt5]>=0.6.2; extra == "pyqt5" .. -*- mode: rst; coding: utf-8 -*- ============================================================================== pytest-twisted - test twisted code with pytest ============================================================================== |PyPI| |Pythons| |Travis| |AppVeyor| |Actions| |Black| :Authors: Ralf Schmitt, Kyle Altendorf, Victor Titor :Version: 1.14.1 :Date: 2024-03-18 :Download: https://pypi.python.org/pypi/pytest-twisted#downloads :Code: https://github.com/pytest-dev/pytest-twisted pytest-twisted is a plugin for pytest, which allows to test code, which uses the twisted framework. test functions can return Deferred objects and pytest will wait for their completion with this plugin. NOTICE: Python 3.8 with asyncio support ======================================= In Python 3.8, asyncio changed the default loop implementation to use their proactor. The proactor does not implement some methods used by Twisted's asyncio support. The result is a ``NotImplementedError`` exception such as below. .. code-block:: pytb File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\asyncioreactor.py", line 320, in install reactor = AsyncioSelectorReactor(eventloop) File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\asyncioreactor.py", line 69, in __init__ super().__init__() File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\base.py", line 571, in __init__ self.installWaker() File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\posixbase.py", line 286, in installWaker self.addReader(self.waker) File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\asyncioreactor.py", line 151, in addReader self._asyncioEventloop.add_reader(fd, callWithLogger, reader, File "C:\Python38-x64\Lib\asyncio\events.py", line 501, in add_reader raise NotImplementedError NotImplementedError The previous default, the selector loop, still works but you have to explicitly set it and do so early. The following ``conftest.py`` is provided for reference. .. code-block:: python3 import sys import pytest import pytest_twisted @pytest.hookimpl(tryfirst=True) def pytest_configure(config): # https://twistedmatrix.com/trac/ticket/9766 # https://github.com/pytest-dev/pytest-twisted/issues/80 if ( config.getoption("reactor", "default") == "asyncio" and sys.platform == 'win32' and sys.version_info >= (3, 8) ): import asyncio selector_policy = asyncio.WindowsSelectorEventLoopPolicy() asyncio.set_event_loop_policy(selector_policy) Python 2 support plans ====================== At some point it may become impractical to retain Python 2 support. Given the small size and very low amount of development it seems likely that this will not be a near term issue. While I personally have no need for Python 2 support I try to err on the side of being helpful so support will not be explicitly removed just to not have to think about it. If major issues are reported and neither myself nor the community have time to resolve them then options will be considered. Installation ============ Install the plugin as below. .. code-block:: sh pip install pytest-twisted Using the plugin ================ The plugin is available after installation and can be disabled using ``-p no:twisted``. By default ``twisted.internet.default`` is used to install the reactor. This creates the same reactor that ``import twisted.internet.reactor`` would. Alternative reactors can be specified using the ``--reactor`` option. This presently supports ``qt5reactor`` for use with ``pyqt5`` and ``pytest-qt`` as well as ``asyncio``. This `guide`_ describes how to add support for a new reactor. The reactor is automatically created prior to the first test but can be explicitly installed earlier by calling ``pytest_twisted.init_default_reactor()`` or the corresponding function for the desired alternate reactor. inlineCallbacks =============== Using ``twisted.internet.defer.inlineCallbacks`` as a decorator for test functions, which use fixtures, does not work. Please use ``pytest_twisted.inlineCallbacks`` instead. .. code-block:: python @pytest_twisted.inlineCallbacks def test_some_stuff(tmpdir): res = yield threads.deferToThread(os.listdir, tmpdir.strpath) assert res == [] ensureDeferred ============== Using ``twisted.internet.defer.ensureDeferred`` as a decorator for test functions, which use fixtures, does not work. Please use ``pytest_twisted.ensureDeferred`` instead. .. code-block:: python @pytest_twisted.ensureDeferred async def test_some_stuff(tmpdir): res = await threads.deferToThread(os.listdir, tmpdir.strpath) assert res == [] Waiting for deferreds in fixtures ================================= ``pytest_twisted.blockon`` allows fixtures to wait for deferreds. .. code-block:: python @pytest.fixture def val(): d = defer.Deferred() reactor.callLater(1.0, d.callback, 10) return pytest_twisted.blockon(d) async/await fixtures ==================== ``async``/``await`` fixtures can be used along with ``yield`` for normal pytest fixture semantics of setup, value, and teardown. At present only function and module scope are supported. .. code-block:: python # No yield (coroutine function) # -> use pytest_twisted.async_fixture() @pytest_twisted.async_fixture() async def foo(): d = defer.Deferred() reactor.callLater(0.01, d.callback, 42) value = await d return value # With yield (asynchronous generator) # -> use pytest_twisted.async_yield_fixture() @pytest_twisted.async_yield_fixture() async def foo_with_teardown(): d1, d2 = defer.Deferred(), defer.Deferred() reactor.callLater(0.01, d1.callback, 42) reactor.callLater(0.02, d2.callback, 37) value = await d1 yield value await d2 Hypothesis ========== pytest-twisted can be used with Hypothesis. .. code-block:: python @hypothesis.given(x=hypothesis.strategies.integers()) @pytest_twisted.ensureDeferred async def test_async(x): assert isinstance(x, int) The twisted greenlet ==================== Some libraries (e.g. corotwine) need to know the greenlet, which is running the twisted reactor. It's available from the ``twisted_greenlet`` fixture. The following code can be used to make corotwine work with pytest-twisted. .. code-block:: python @pytest.fixture(scope="session", autouse=True) def set_MAIN(request, twisted_greenlet): from corotwine import protocol protocol.MAIN = twisted_greenlet That's (almost) all. Deprecations ============ ---- v1.9 ---- ``pytest.blockon`` Use ``pytest_twisted.blockon`` ``pytest.inlineCallbacks`` Use ``pytest_twisted.inlineCallbacks`` .. |PyPI| image:: https://img.shields.io/pypi/v/pytest-twisted.svg :alt: PyPI version :target: https://pypi.python.org/pypi/pytest-twisted .. |Pythons| image:: https://img.shields.io/pypi/pyversions/pytest-twisted.svg :alt: Supported Python versions :target: https://pypi.python.org/pypi/pytest-twisted .. |Travis| image:: https://travis-ci.org/pytest-dev/pytest-twisted.svg?branch=master :alt: Travis build status :target: https://travis-ci.org/pytest-dev/pytest-twisted .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/eb1vp9hysp463c66/branch/master?svg=true :alt: AppVeyor build status :target: https://ci.appveyor.com/project/pytestbot/pytest-twisted .. |Actions| image:: https://img.shields.io/github/workflow/status/pytest-dev/pytest-twisted/CI/master?logo=GitHub-Actions :alt: GitHub Actions build status :target: https://github.com/pytest-dev/pytest-twisted/actions?query=branch%3Amaster .. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg :alt: Black code style :target: https://github.com/ambv/black .. _guide: CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815523.0 pytest-twisted-1.14.1/README.rst0000644000000000000000000002002614576174443015053 0ustar00rootroot.. -*- mode: rst; coding: utf-8 -*- ============================================================================== pytest-twisted - test twisted code with pytest ============================================================================== |PyPI| |Pythons| |Travis| |AppVeyor| |Actions| |Black| :Authors: Ralf Schmitt, Kyle Altendorf, Victor Titor :Version: 1.14.1 :Date: 2024-03-18 :Download: https://pypi.python.org/pypi/pytest-twisted#downloads :Code: https://github.com/pytest-dev/pytest-twisted pytest-twisted is a plugin for pytest, which allows to test code, which uses the twisted framework. test functions can return Deferred objects and pytest will wait for their completion with this plugin. NOTICE: Python 3.8 with asyncio support ======================================= In Python 3.8, asyncio changed the default loop implementation to use their proactor. The proactor does not implement some methods used by Twisted's asyncio support. The result is a ``NotImplementedError`` exception such as below. .. code-block:: pytb File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\asyncioreactor.py", line 320, in install reactor = AsyncioSelectorReactor(eventloop) File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\asyncioreactor.py", line 69, in __init__ super().__init__() File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\base.py", line 571, in __init__ self.installWaker() File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\posixbase.py", line 286, in installWaker self.addReader(self.waker) File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\asyncioreactor.py", line 151, in addReader self._asyncioEventloop.add_reader(fd, callWithLogger, reader, File "C:\Python38-x64\Lib\asyncio\events.py", line 501, in add_reader raise NotImplementedError NotImplementedError The previous default, the selector loop, still works but you have to explicitly set it and do so early. The following ``conftest.py`` is provided for reference. .. code-block:: python3 import sys import pytest import pytest_twisted @pytest.hookimpl(tryfirst=True) def pytest_configure(config): # https://twistedmatrix.com/trac/ticket/9766 # https://github.com/pytest-dev/pytest-twisted/issues/80 if ( config.getoption("reactor", "default") == "asyncio" and sys.platform == 'win32' and sys.version_info >= (3, 8) ): import asyncio selector_policy = asyncio.WindowsSelectorEventLoopPolicy() asyncio.set_event_loop_policy(selector_policy) Python 2 support plans ====================== At some point it may become impractical to retain Python 2 support. Given the small size and very low amount of development it seems likely that this will not be a near term issue. While I personally have no need for Python 2 support I try to err on the side of being helpful so support will not be explicitly removed just to not have to think about it. If major issues are reported and neither myself nor the community have time to resolve them then options will be considered. Installation ============ Install the plugin as below. .. code-block:: sh pip install pytest-twisted Using the plugin ================ The plugin is available after installation and can be disabled using ``-p no:twisted``. By default ``twisted.internet.default`` is used to install the reactor. This creates the same reactor that ``import twisted.internet.reactor`` would. Alternative reactors can be specified using the ``--reactor`` option. This presently supports ``qt5reactor`` for use with ``pyqt5`` and ``pytest-qt`` as well as ``asyncio``. This `guide`_ describes how to add support for a new reactor. The reactor is automatically created prior to the first test but can be explicitly installed earlier by calling ``pytest_twisted.init_default_reactor()`` or the corresponding function for the desired alternate reactor. inlineCallbacks =============== Using ``twisted.internet.defer.inlineCallbacks`` as a decorator for test functions, which use fixtures, does not work. Please use ``pytest_twisted.inlineCallbacks`` instead. .. code-block:: python @pytest_twisted.inlineCallbacks def test_some_stuff(tmpdir): res = yield threads.deferToThread(os.listdir, tmpdir.strpath) assert res == [] ensureDeferred ============== Using ``twisted.internet.defer.ensureDeferred`` as a decorator for test functions, which use fixtures, does not work. Please use ``pytest_twisted.ensureDeferred`` instead. .. code-block:: python @pytest_twisted.ensureDeferred async def test_some_stuff(tmpdir): res = await threads.deferToThread(os.listdir, tmpdir.strpath) assert res == [] Waiting for deferreds in fixtures ================================= ``pytest_twisted.blockon`` allows fixtures to wait for deferreds. .. code-block:: python @pytest.fixture def val(): d = defer.Deferred() reactor.callLater(1.0, d.callback, 10) return pytest_twisted.blockon(d) async/await fixtures ==================== ``async``/``await`` fixtures can be used along with ``yield`` for normal pytest fixture semantics of setup, value, and teardown. At present only function and module scope are supported. .. code-block:: python # No yield (coroutine function) # -> use pytest_twisted.async_fixture() @pytest_twisted.async_fixture() async def foo(): d = defer.Deferred() reactor.callLater(0.01, d.callback, 42) value = await d return value # With yield (asynchronous generator) # -> use pytest_twisted.async_yield_fixture() @pytest_twisted.async_yield_fixture() async def foo_with_teardown(): d1, d2 = defer.Deferred(), defer.Deferred() reactor.callLater(0.01, d1.callback, 42) reactor.callLater(0.02, d2.callback, 37) value = await d1 yield value await d2 Hypothesis ========== pytest-twisted can be used with Hypothesis. .. code-block:: python @hypothesis.given(x=hypothesis.strategies.integers()) @pytest_twisted.ensureDeferred async def test_async(x): assert isinstance(x, int) The twisted greenlet ==================== Some libraries (e.g. corotwine) need to know the greenlet, which is running the twisted reactor. It's available from the ``twisted_greenlet`` fixture. The following code can be used to make corotwine work with pytest-twisted. .. code-block:: python @pytest.fixture(scope="session", autouse=True) def set_MAIN(request, twisted_greenlet): from corotwine import protocol protocol.MAIN = twisted_greenlet That's (almost) all. Deprecations ============ ---- v1.9 ---- ``pytest.blockon`` Use ``pytest_twisted.blockon`` ``pytest.inlineCallbacks`` Use ``pytest_twisted.inlineCallbacks`` .. |PyPI| image:: https://img.shields.io/pypi/v/pytest-twisted.svg :alt: PyPI version :target: https://pypi.python.org/pypi/pytest-twisted .. |Pythons| image:: https://img.shields.io/pypi/pyversions/pytest-twisted.svg :alt: Supported Python versions :target: https://pypi.python.org/pypi/pytest-twisted .. |Travis| image:: https://travis-ci.org/pytest-dev/pytest-twisted.svg?branch=master :alt: Travis build status :target: https://travis-ci.org/pytest-dev/pytest-twisted .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/eb1vp9hysp463c66/branch/master?svg=true :alt: AppVeyor build status :target: https://ci.appveyor.com/project/pytestbot/pytest-twisted .. |Actions| image:: https://img.shields.io/github/workflow/status/pytest-dev/pytest-twisted/CI/master?logo=GitHub-Actions :alt: GitHub Actions build status :target: https://github.com/pytest-dev/pytest-twisted/actions?query=branch%3Amaster .. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg :alt: Black code style :target: https://github.com/ambv/black .. _guide: CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1710815530.4068553 pytest-twisted-1.14.1/pytest_twisted.egg-info/0000755000000000000000000000000014576174452020151 5ustar00rootroot././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815530.0 pytest-twisted-1.14.1/pytest_twisted.egg-info/PKG-INFO0000644000000000000000000002273314576174452021255 0ustar00rootrootMetadata-Version: 2.1 Name: pytest-twisted Version: 1.14.1 Summary: A twisted plugin for pytest. Home-page: https://github.com/pytest-dev/pytest-twisted Author: Ralf Schmitt, Kyle Altendorf, Victor Titor Author-email: sda@fstab.net Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Testing Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.* Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: greenlet Requires-Dist: pytest>=2.3 Requires-Dist: decorator Provides-Extra: dev Requires-Dist: pre-commit; extra == "dev" Requires-Dist: black; extra == "dev" Provides-Extra: pyside2 Requires-Dist: qt5reactor[pyside2]>=0.6.3; extra == "pyside2" Provides-Extra: pyqt5 Requires-Dist: qt5reactor[pyqt5]>=0.6.2; extra == "pyqt5" .. -*- mode: rst; coding: utf-8 -*- ============================================================================== pytest-twisted - test twisted code with pytest ============================================================================== |PyPI| |Pythons| |Travis| |AppVeyor| |Actions| |Black| :Authors: Ralf Schmitt, Kyle Altendorf, Victor Titor :Version: 1.14.1 :Date: 2024-03-18 :Download: https://pypi.python.org/pypi/pytest-twisted#downloads :Code: https://github.com/pytest-dev/pytest-twisted pytest-twisted is a plugin for pytest, which allows to test code, which uses the twisted framework. test functions can return Deferred objects and pytest will wait for their completion with this plugin. NOTICE: Python 3.8 with asyncio support ======================================= In Python 3.8, asyncio changed the default loop implementation to use their proactor. The proactor does not implement some methods used by Twisted's asyncio support. The result is a ``NotImplementedError`` exception such as below. .. code-block:: pytb File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\asyncioreactor.py", line 320, in install reactor = AsyncioSelectorReactor(eventloop) File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\asyncioreactor.py", line 69, in __init__ super().__init__() File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\base.py", line 571, in __init__ self.installWaker() File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\posixbase.py", line 286, in installWaker self.addReader(self.waker) File "c:\projects\pytest-twisted\.tox\py38-asyncioreactor\lib\site-packages\twisted\internet\asyncioreactor.py", line 151, in addReader self._asyncioEventloop.add_reader(fd, callWithLogger, reader, File "C:\Python38-x64\Lib\asyncio\events.py", line 501, in add_reader raise NotImplementedError NotImplementedError The previous default, the selector loop, still works but you have to explicitly set it and do so early. The following ``conftest.py`` is provided for reference. .. code-block:: python3 import sys import pytest import pytest_twisted @pytest.hookimpl(tryfirst=True) def pytest_configure(config): # https://twistedmatrix.com/trac/ticket/9766 # https://github.com/pytest-dev/pytest-twisted/issues/80 if ( config.getoption("reactor", "default") == "asyncio" and sys.platform == 'win32' and sys.version_info >= (3, 8) ): import asyncio selector_policy = asyncio.WindowsSelectorEventLoopPolicy() asyncio.set_event_loop_policy(selector_policy) Python 2 support plans ====================== At some point it may become impractical to retain Python 2 support. Given the small size and very low amount of development it seems likely that this will not be a near term issue. While I personally have no need for Python 2 support I try to err on the side of being helpful so support will not be explicitly removed just to not have to think about it. If major issues are reported and neither myself nor the community have time to resolve them then options will be considered. Installation ============ Install the plugin as below. .. code-block:: sh pip install pytest-twisted Using the plugin ================ The plugin is available after installation and can be disabled using ``-p no:twisted``. By default ``twisted.internet.default`` is used to install the reactor. This creates the same reactor that ``import twisted.internet.reactor`` would. Alternative reactors can be specified using the ``--reactor`` option. This presently supports ``qt5reactor`` for use with ``pyqt5`` and ``pytest-qt`` as well as ``asyncio``. This `guide`_ describes how to add support for a new reactor. The reactor is automatically created prior to the first test but can be explicitly installed earlier by calling ``pytest_twisted.init_default_reactor()`` or the corresponding function for the desired alternate reactor. inlineCallbacks =============== Using ``twisted.internet.defer.inlineCallbacks`` as a decorator for test functions, which use fixtures, does not work. Please use ``pytest_twisted.inlineCallbacks`` instead. .. code-block:: python @pytest_twisted.inlineCallbacks def test_some_stuff(tmpdir): res = yield threads.deferToThread(os.listdir, tmpdir.strpath) assert res == [] ensureDeferred ============== Using ``twisted.internet.defer.ensureDeferred`` as a decorator for test functions, which use fixtures, does not work. Please use ``pytest_twisted.ensureDeferred`` instead. .. code-block:: python @pytest_twisted.ensureDeferred async def test_some_stuff(tmpdir): res = await threads.deferToThread(os.listdir, tmpdir.strpath) assert res == [] Waiting for deferreds in fixtures ================================= ``pytest_twisted.blockon`` allows fixtures to wait for deferreds. .. code-block:: python @pytest.fixture def val(): d = defer.Deferred() reactor.callLater(1.0, d.callback, 10) return pytest_twisted.blockon(d) async/await fixtures ==================== ``async``/``await`` fixtures can be used along with ``yield`` for normal pytest fixture semantics of setup, value, and teardown. At present only function and module scope are supported. .. code-block:: python # No yield (coroutine function) # -> use pytest_twisted.async_fixture() @pytest_twisted.async_fixture() async def foo(): d = defer.Deferred() reactor.callLater(0.01, d.callback, 42) value = await d return value # With yield (asynchronous generator) # -> use pytest_twisted.async_yield_fixture() @pytest_twisted.async_yield_fixture() async def foo_with_teardown(): d1, d2 = defer.Deferred(), defer.Deferred() reactor.callLater(0.01, d1.callback, 42) reactor.callLater(0.02, d2.callback, 37) value = await d1 yield value await d2 Hypothesis ========== pytest-twisted can be used with Hypothesis. .. code-block:: python @hypothesis.given(x=hypothesis.strategies.integers()) @pytest_twisted.ensureDeferred async def test_async(x): assert isinstance(x, int) The twisted greenlet ==================== Some libraries (e.g. corotwine) need to know the greenlet, which is running the twisted reactor. It's available from the ``twisted_greenlet`` fixture. The following code can be used to make corotwine work with pytest-twisted. .. code-block:: python @pytest.fixture(scope="session", autouse=True) def set_MAIN(request, twisted_greenlet): from corotwine import protocol protocol.MAIN = twisted_greenlet That's (almost) all. Deprecations ============ ---- v1.9 ---- ``pytest.blockon`` Use ``pytest_twisted.blockon`` ``pytest.inlineCallbacks`` Use ``pytest_twisted.inlineCallbacks`` .. |PyPI| image:: https://img.shields.io/pypi/v/pytest-twisted.svg :alt: PyPI version :target: https://pypi.python.org/pypi/pytest-twisted .. |Pythons| image:: https://img.shields.io/pypi/pyversions/pytest-twisted.svg :alt: Supported Python versions :target: https://pypi.python.org/pypi/pytest-twisted .. |Travis| image:: https://travis-ci.org/pytest-dev/pytest-twisted.svg?branch=master :alt: Travis build status :target: https://travis-ci.org/pytest-dev/pytest-twisted .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/eb1vp9hysp463c66/branch/master?svg=true :alt: AppVeyor build status :target: https://ci.appveyor.com/project/pytestbot/pytest-twisted .. |Actions| image:: https://img.shields.io/github/workflow/status/pytest-dev/pytest-twisted/CI/master?logo=GitHub-Actions :alt: GitHub Actions build status :target: https://github.com/pytest-dev/pytest-twisted/actions?query=branch%3Amaster .. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg :alt: Black code style :target: https://github.com/ambv/black .. _guide: CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815530.0 pytest-twisted-1.14.1/pytest_twisted.egg-info/SOURCES.txt0000644000000000000000000000053314576174452022036 0ustar00rootrootLICENSE MANIFEST.in README.rst pytest_twisted.py setup.cfg setup.py tox.ini pytest_twisted.egg-info/PKG-INFO pytest_twisted.egg-info/SOURCES.txt pytest_twisted.egg-info/dependency_links.txt pytest_twisted.egg-info/entry_points.txt pytest_twisted.egg-info/requires.txt pytest_twisted.egg-info/top_level.txt testing/conftest.py testing/test_basic.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815530.0 pytest-twisted-1.14.1/pytest_twisted.egg-info/dependency_links.txt0000644000000000000000000000000114576174452024217 0ustar00rootroot ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815530.0 pytest-twisted-1.14.1/pytest_twisted.egg-info/entry_points.txt0000644000000000000000000000004414576174452023445 0ustar00rootroot[pytest11] twisted = pytest_twisted ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815530.0 pytest-twisted-1.14.1/pytest_twisted.egg-info/requires.txt0000644000000000000000000000017714576174452022556 0ustar00rootrootgreenlet pytest>=2.3 decorator [dev] pre-commit black [pyqt5] qt5reactor[pyqt5]>=0.6.2 [pyside2] qt5reactor[pyside2]>=0.6.3 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815530.0 pytest-twisted-1.14.1/pytest_twisted.egg-info/top_level.txt0000644000000000000000000000001714576174452022701 0ustar00rootrootpytest_twisted ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815523.0 pytest-twisted-1.14.1/pytest_twisted.py0000644000000000000000000003544714576174443017046 0ustar00rootrootimport functools import inspect import itertools import signal import sys import threading import warnings import decorator import greenlet import pytest from twisted.internet import defer, error from twisted.internet.threads import blockingCallFromThread from twisted.python import failure class WrongReactorAlreadyInstalledError(Exception): pass class UnrecognizedCoroutineMarkError(Exception): @classmethod def from_mark(cls, mark): return cls( 'Coroutine wrapper mark not recognized: {}'.format(repr(mark)), ) class AsyncGeneratorFixtureDidNotStopError(Exception): @classmethod def from_generator(cls, generator): return cls( 'async fixture did not stop: {}'.format(generator), ) class AsyncFixtureUnsupportedScopeError(Exception): @classmethod def from_scope(cls, scope): return cls( 'Unsupported scope {0!r} used for async fixture'.format(scope) ) class _config: external_reactor = False class _instances: gr_twisted = None reactor = None def _deprecate(deprecated, recommended): def decorator(f): @functools.wraps(f) def wrapper(*args, **kwargs): warnings.warn( '{deprecated} has been deprecated, use {recommended}'.format( deprecated=deprecated, recommended=recommended, ), DeprecationWarning, stacklevel=2, ) return f(*args, **kwargs) return wrapper return decorator def blockon(d): if _config.external_reactor: return block_from_thread(d) return blockon_default(d) def blockon_default(d): current = greenlet.getcurrent() assert ( current is not _instances.gr_twisted ), "blockon cannot be called from the twisted greenlet" result = [] def cb(r): result.append(r) if greenlet.getcurrent() is not current: current.switch(result) d.addCallbacks(cb, cb) if not result: _result = _instances.gr_twisted.switch() assert _result is result, "illegal switch in blockon" if isinstance(result[0], failure.Failure): result[0].raiseException() return result[0] def block_from_thread(d): return blockingCallFromThread(_instances.reactor, lambda x: x, d) def decorator_apply(dec, func): """ Decorate a function by preserving the signature even if dec is not a signature-preserving decorator. https://github.com/micheles/decorator/blob/55a68b5ef1951614c5c37a6d201b1f3b804dbce6/docs/documentation.md#dealing-with-third-party-decorators """ return decorator.FunctionMaker.create( func, 'return decfunc(%(signature)s)', dict(decfunc=dec(func)), __wrapped__=func) class DecoratorArgumentsError(Exception): pass def repr_args_kwargs(*args, **kwargs): arguments = ', '.join(itertools.chain( (repr(x) for x in args), ('{}={}'.format(k, repr(v)) for k, v in kwargs.items()) )) return '({})'.format(arguments) def _positional_not_allowed_exception(*args, **kwargs): arguments = repr_args_kwargs(*args, **kwargs) return DecoratorArgumentsError( 'Positional decorator arguments not allowed: {}'.format(arguments), ) def _optional_arguments(): def decorator_decorator(d): # TODO: this should get the signature of d minus the f or something def decorator_wrapper(*args, **decorator_arguments): """this is decorator_wrapper""" if len(args) > 1: raise _positional_not_allowed_exception() if len(args) == 1: maybe_f = args[0] if len(decorator_arguments) > 0 or not callable(maybe_f): raise _positional_not_allowed_exception() f = maybe_f return d(f) # TODO: this should get the signature of d minus the kwargs def decorator_closure_on_arguments(f): return d(f, **decorator_arguments) return decorator_closure_on_arguments return decorator_wrapper return decorator_decorator @_optional_arguments() def inlineCallbacks(f): """ Mark as inline callbacks test for pytest-twisted processing and apply @inlineCallbacks. Unlike @ensureDeferred, @inlineCallbacks can be applied here because it does not call nor schedule the test function. Further, @inlineCallbacks must be applied here otherwise pytest identifies the test as a 'yield test' for which they dropped support in 4.0 and now they skip. """ decorated = decorator_apply(defer.inlineCallbacks, f) _set_mark(o=decorated, mark='inline_callbacks_test') return decorated @_optional_arguments() def ensureDeferred(f): """ Mark as async test for pytest-twisted processing. Unlike @inlineCallbacks, @ensureDeferred must not be applied here since it would call and schedule the test function. """ _set_mark(o=f, mark='async_test') return f def init_twisted_greenlet(): if _instances.reactor is None or _instances.gr_twisted: return if not _instances.reactor.running: if not isinstance(threading.current_thread(), threading._MainThread): warnings.warn( ( 'Will not attempt to block Twisted signal configuration' ' since we are not running in the main thread. See' ' https://github.com/pytest-dev/pytest-twisted/issues/153.' ), RuntimeWarning, ) elif signal.getsignal(signal.SIGINT) == signal.default_int_handler: signal.signal( signal.SIGINT, functools.partial(signal.default_int_handler), ) _instances.gr_twisted = greenlet.greenlet(_instances.reactor.run) # give me better tracebacks: failure.Failure.cleanFailure = lambda self: None else: _config.external_reactor = True def stop_twisted_greenlet(): if _instances.gr_twisted: try: _instances.reactor.stop() except error.ReactorNotRunning: # Sometimes the reactor is stopped before we get here. For # example, this can happen in response to a SIGINT in some cases. pass _instances.gr_twisted.switch() def _get_mark(o, default=None): """Get the pytest-twisted test or fixture mark.""" return getattr(o, _mark_attribute_name, default) def _set_mark(o, mark): """Set the pytest-twisted test or fixture mark.""" setattr(o, _mark_attribute_name, mark) def _marked_async_fixture(mark): @functools.wraps(pytest.fixture) @_optional_arguments() def fixture(f, *args, **kwargs): try: scope = args[0] except IndexError: scope = kwargs.get('scope', 'function') if scope not in ['function', 'module']: # TODO: handle... # - class # - package # - session # - dynamic # # https://docs.pytest.org/en/latest/reference.html#pytest-fixture-api # then remove this and update docs, or maybe keep it around # in case new options come in without support? # # https://github.com/pytest-dev/pytest-twisted/issues/56 raise AsyncFixtureUnsupportedScopeError.from_scope(scope=scope) _set_mark(f, mark) result = pytest.fixture(*args, **kwargs)(f) return result return fixture _mark_attribute_name = '_pytest_twisted_mark' async_fixture = _marked_async_fixture('async_fixture') async_yield_fixture = _marked_async_fixture('async_yield_fixture') def pytest_fixture_setup(fixturedef, request): """Interface pytest to async for async and async yield fixtures.""" # TODO: what about _adding_ inlineCallbacks fixture support? maybe_mark = _get_mark(fixturedef.func) if maybe_mark is None: return None mark = maybe_mark _run_inline_callbacks( _async_pytest_fixture_setup, fixturedef, request, mark, ) return not None def _create_async_yield_fixture_finalizer(coroutine): def finalizer(): _run_inline_callbacks( _tear_it_down, defer.ensureDeferred(coroutine.__anext__()), ) return finalizer @defer.inlineCallbacks def _async_pytest_fixture_setup(fixturedef, request, mark): """Setup an async or async yield fixture.""" fixture_function = fixturedef.func kwargs = { name: request.getfixturevalue(name) for name in fixturedef.argnames } if mark == 'async_fixture': arg_value = yield defer.ensureDeferred( fixture_function(**kwargs) ) elif mark == 'async_yield_fixture': coroutine = fixture_function(**kwargs) request.addfinalizer( _create_async_yield_fixture_finalizer(coroutine=coroutine), ) arg_value = yield defer.ensureDeferred(coroutine.__anext__()) else: raise UnrecognizedCoroutineMarkError.from_mark(mark=mark) fixturedef.cached_result = (arg_value, request.param_index, None) defer.returnValue(arg_value) @defer.inlineCallbacks def _tear_it_down(deferred): """Tear down a specific async yield fixture.""" try: yield deferred except StopAsyncIteration: return # TODO: six.raise_from() raise AsyncGeneratorFixtureDidNotStopError.from_generator( generator=deferred, ) def _run_inline_callbacks(f, *args): """Interface into Twisted greenlet to run and wait for a deferred.""" if _instances.gr_twisted is not None: if _instances.gr_twisted.dead: raise RuntimeError("twisted reactor has stopped") def in_reactor(d, f, *args): return defer.maybeDeferred(f, *args).chainDeferred(d) d = defer.Deferred() _instances.reactor.callLater(0.0, in_reactor, d, f, *args) blockon_default(d) else: if not _instances.reactor.running: raise RuntimeError("twisted reactor is not running") blockingCallFromThread(_instances.reactor, f, *args) def pytest_pyfunc_call(pyfuncitem): """Interface to async test call handler.""" # TODO: only handle 'our' tests? what is the point of handling others? # well, because our interface allowed people to return deferreds # from arbitrary tests so we kinda have to keep this up for now maybe_hypothesis = getattr(pyfuncitem.obj, "hypothesis", None) if maybe_hypothesis is None: _run_inline_callbacks( _async_pytest_pyfunc_call, pyfuncitem, pyfuncitem.obj, {} ) result = not None else: hypothesis = maybe_hypothesis f = hypothesis.inner_test def inner_test(**kwargs): return _run_inline_callbacks( _async_pytest_pyfunc_call, pyfuncitem, f, kwargs, ) pyfuncitem.obj.hypothesis.inner_test = inner_test result = None return result @defer.inlineCallbacks def _async_pytest_pyfunc_call(pyfuncitem, f, kwargs): """Run test function.""" fixture_kwargs = { name: value for name, value in pyfuncitem.funcargs.items() if name in pyfuncitem._fixtureinfo.argnames } kwargs.update(fixture_kwargs) maybe_mark = _get_mark(f) if maybe_mark == 'async_test': result = yield defer.ensureDeferred(f(**kwargs)) elif maybe_mark == 'inline_callbacks_test': result = yield f(**kwargs) else: # TODO: maybe deprecate this result = yield f(**kwargs) defer.returnValue(result) @pytest.fixture(scope="session", autouse=True) def twisted_greenlet(): """Provide the twisted greenlet in fixture form.""" return _instances.gr_twisted def init_default_reactor(): """Install the default Twisted reactor.""" import twisted.internet.default module = inspect.getmodule(twisted.internet.default.install) module_name = module.__name__.split(".")[-1] reactor_type_name, = (x for x in dir(module) if x.lower() == module_name) reactor_type = getattr(module, reactor_type_name) _install_reactor( reactor_installer=twisted.internet.default.install, reactor_type=reactor_type, ) def init_qt5_reactor(): """Install the qt5reactor... reactor.""" import qt5reactor _install_reactor( reactor_installer=qt5reactor.install, reactor_type=qt5reactor.QtReactor ) def init_asyncio_reactor(): """Install the Twisted reactor for asyncio.""" from twisted.internet import asyncioreactor _install_reactor( reactor_installer=asyncioreactor.install, reactor_type=asyncioreactor.AsyncioSelectorReactor, ) reactor_installers = { "default": init_default_reactor, "qt5reactor": init_qt5_reactor, "asyncio": init_asyncio_reactor, } def _install_reactor(reactor_installer, reactor_type): """Install the specified reactor and create the greenlet.""" try: reactor_installer() except error.ReactorAlreadyInstalledError: import twisted.internet.reactor if not isinstance(twisted.internet.reactor, reactor_type): raise WrongReactorAlreadyInstalledError( "expected {} but found {}".format( reactor_type, type(twisted.internet.reactor) ) ) import twisted.internet.reactor _instances.reactor = twisted.internet.reactor init_twisted_greenlet() def pytest_addoption(parser): """Add options into the pytest CLI.""" group = parser.getgroup("twisted") group.addoption( "--reactor", default="default", choices=tuple(reactor_installers.keys()), ) def pytest_configure(config): """Identify and install chosen reactor.""" pytest.inlineCallbacks = _deprecate( deprecated='pytest.inlineCallbacks', recommended='pytest_twisted.inlineCallbacks', )(inlineCallbacks) pytest.blockon = _deprecate( deprecated='pytest.blockon', recommended='pytest_twisted.blockon', )(blockon) reactor_installers[config.getoption("reactor")]() def pytest_unconfigure(config): """Stop the reactor greenlet.""" stop_twisted_greenlet() def _use_asyncio_selector_if_required(config): """Set asyncio selector event loop policy if needed.""" # https://twistedmatrix.com/trac/ticket/9766 # https://github.com/pytest-dev/pytest-twisted/issues/80 is_asyncio = config.getoption("reactor", "default") == "asyncio" if is_asyncio and sys.platform == 'win32' and sys.version_info >= (3, 8): import asyncio selector_policy = asyncio.WindowsSelectorEventLoopPolicy() asyncio.set_event_loop_policy(selector_policy) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1710815530.4068553 pytest-twisted-1.14.1/setup.cfg0000644000000000000000000000013214576174452015201 0ustar00rootroot[sdist] formats = zip [bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815523.0 pytest-twisted-1.14.1/setup.py0000755000000000000000000000324614576174443015106 0ustar00rootrootfrom setuptools import setup with open("README.rst") as f: long_description = f.read() setup( name="pytest-twisted", version="1.14.1", description="A twisted plugin for pytest.", long_description=long_description, long_description_content_type="text/x-rst", author="Ralf Schmitt, Kyle Altendorf, Victor Titor", author_email="sda@fstab.net", url="https://github.com/pytest-dev/pytest-twisted", py_modules=["pytest_twisted"], python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*', install_requires=["greenlet", "pytest>=2.3", "decorator"], extras_require={ "dev": ["pre-commit", "black"], "pyside2": [ # >= 0.6.3 for PySide2 extra version constraints "qt5reactor[pyside2]>=0.6.3", ], "pyqt5": ["qt5reactor[pyqt5]>=0.6.2"], }, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Software Development :: Testing", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ], entry_points={"pytest11": ["twisted = pytest_twisted"]}, ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1710815530.4068553 pytest-twisted-1.14.1/testing/0000755000000000000000000000000014576174452015041 5ustar00rootroot././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815523.0 pytest-twisted-1.14.1/testing/conftest.py0000644000000000000000000000030614576174443017237 0ustar00rootrootimport pytest import pytest_twisted pytest_plugins = "pytester" @pytest.hookimpl(tryfirst=True) def pytest_configure(config): pytest_twisted._use_asyncio_selector_if_required(config=config) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815523.0 pytest-twisted-1.14.1/testing/test_basic.py0000755000000000000000000010626014576174443017543 0ustar00rootrootimport os import re import sys import textwrap import pytest # https://docs.python.org/3/whatsnew/3.5.html#pep-492-coroutines-with-async-and-await-syntax ASYNC_AWAIT = sys.version_info >= (3, 5) # https://docs.python.org/3/whatsnew/3.6.html#pep-525-asynchronous-generators ASYNC_GENERATORS = sys.version_info >= (3, 6) timeout = 15 pytest_version = tuple( int(segment) for segment in pytest.__version__.split(".")[:3] ) # https://github.com/pytest-dev/pytest/issues/6505 def force_plural(name): if name in {"error", "warning"}: return name + "s" return name def assert_outcomes(run_result, outcomes): formatted_output = format_run_result_output_for_assert(run_result) try: result_outcomes = run_result.parseoutcomes() except ValueError: assert False, formatted_output normalized_result_outcomes = { force_plural(name): outcome for name, outcome in result_outcomes.items() if name != "seconds" } assert normalized_result_outcomes == outcomes, formatted_output def format_run_result_output_for_assert(run_result): tpl = """ ---- stdout {} ---- stderr {} ---- """ return textwrap.dedent(tpl).format( run_result.stdout.str(), run_result.stderr.str() ) @pytest.fixture(name="default_conftest", autouse=True) def _default_conftest(testdir): testdir.makeconftest(textwrap.dedent(""" import pytest import pytest_twisted @pytest.hookimpl(tryfirst=True) def pytest_configure(config): pytest_twisted._use_asyncio_selector_if_required(config=config) """)) def skip_if_reactor_not(request, expected_reactor): actual_reactor = request.config.getoption("reactor", "default") if actual_reactor != expected_reactor: pytest.skip( "reactor is {} not {}".format(actual_reactor, expected_reactor), ) def skip_if_no_async_await(): return pytest.mark.skipif( not ASYNC_AWAIT, reason="async/await syntax not supported on Python <3.5", ) def skip_if_no_async_generators(): return pytest.mark.skipif( not ASYNC_GENERATORS, reason="async generators not support on Python <3.6", ) def skip_if_hypothesis_unavailable(): def hypothesis_unavailable(): try: import hypothesis # noqa: F401 except ImportError: return True return False return pytest.mark.skipif( hypothesis_unavailable(), reason="hypothesis not installed", ) @pytest.fixture def cmd_opts(request): reactor = request.config.getoption("reactor", "default") return ( sys.executable, "-m", "pytest", "-v", "--reactor={}".format(reactor), ) def test_inline_callbacks_in_pytest(): assert hasattr(pytest, 'inlineCallbacks') @pytest.mark.parametrize( 'decorator, should_warn', ( ('pytest.inlineCallbacks', True), ('pytest_twisted.inlineCallbacks', False), ), ) def test_inline_callbacks_in_pytest_deprecation( testdir, cmd_opts, decorator, should_warn, ): import_path, _, _ = decorator.rpartition('.') test_file = """ import {import_path} def test_deprecation(): @{decorator} def f(): yield 42 """.format(import_path=import_path, decorator=decorator) testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) expected_outcomes = {"passed": 1} if should_warn: expected_outcomes["warnings"] = 1 assert_outcomes(rr, expected_outcomes) def test_blockon_in_pytest(): assert hasattr(pytest, 'blockon') @pytest.mark.parametrize( 'function, should_warn', ( ('pytest.blockon', True), ('pytest_twisted.blockon', False), ), ) def test_blockon_in_pytest_deprecation( testdir, cmd_opts, function, should_warn, ): import_path, _, _ = function.rpartition('.') test_file = """ import warnings from twisted.internet import reactor, defer import pytest import {import_path} @pytest.fixture def foo(request): d = defer.Deferred() d.callback(None) {function}(d) def test_succeed(foo): pass """.format(import_path=import_path, function=function) testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) expected_outcomes = {"passed": 1} if should_warn: expected_outcomes["warnings"] = 1 assert_outcomes(rr, expected_outcomes) def test_fail_later(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer def test_fail(): def doit(): try: 1 / 0 except: d.errback() d = defer.Deferred() reactor.callLater(0.01, doit) return d """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"failed": 1}) def test_succeed_later(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer def test_succeed(): d = defer.Deferred() reactor.callLater(0.01, d.callback, 1) return d """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) def test_non_deferred(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer def test_succeed(): return 42 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) def test_exception(testdir, cmd_opts): test_file = """ def test_more_fail(): raise RuntimeError("foo") """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"failed": 1}) @pytest.fixture( name="empty_optional_call", params=["", "()"], ids=["no call", "empty call"], ) def empty_optional_call_fixture(request): return request.param def test_inlineCallbacks(testdir, cmd_opts, empty_optional_call): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest.fixture(scope="module", params=["fs", "imap", "web"]) def foo(request): return request.param @pytest_twisted.inlineCallbacks{optional_call} def test_succeed(foo): yield defer.succeed(foo) if foo == "web": raise RuntimeError("baz") """.format(optional_call=empty_optional_call) testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2, "failed": 1}) @skip_if_no_async_await() def test_async_await(testdir, cmd_opts, empty_optional_call): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest.fixture(scope="module", params=["fs", "imap", "web"]) def foo(request): return request.param @pytest_twisted.ensureDeferred{optional_call} async def test_succeed(foo): await defer.succeed(foo) if foo == "web": raise RuntimeError("baz") """.format(optional_call=empty_optional_call) testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2, "failed": 1}) def test_twisted_greenlet(testdir, cmd_opts): test_file = """ import pytest, greenlet MAIN = None @pytest.fixture(scope="session", autouse=True) def set_MAIN(request, twisted_greenlet): global MAIN MAIN = twisted_greenlet def test_MAIN(): assert MAIN is not None assert MAIN is greenlet.getcurrent() """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) def test_blockon_in_fixture(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest.fixture(scope="module", params=["fs", "imap", "web"]) def foo(request): d1, d2 = defer.Deferred(), defer.Deferred() reactor.callLater(0.01, d1.callback, 1) reactor.callLater(0.02, d2.callback, request.param) pytest_twisted.blockon(d1) return d2 @pytest_twisted.inlineCallbacks def test_succeed(foo): x = yield foo if x == "web": raise RuntimeError("baz") """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2, "failed": 1}) @skip_if_no_async_await() def test_blockon_in_fixture_async(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest.fixture(scope="module", params=["fs", "imap", "web"]) def foo(request): d1, d2 = defer.Deferred(), defer.Deferred() reactor.callLater(0.01, d1.callback, 1) reactor.callLater(0.02, d2.callback, request.param) pytest_twisted.blockon(d1) return d2 @pytest_twisted.ensureDeferred async def test_succeed(foo): x = await foo if x == "web": raise RuntimeError("baz") """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2, "failed": 1}) @skip_if_no_async_await() def test_async_fixture(testdir, cmd_opts): pytest_ini_file = """ [pytest] markers = redgreenblue """ testdir.makefile('.ini', pytest=pytest_ini_file) test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest_twisted.async_fixture( scope="function", params=["fs", "imap", "web"], ) async def foo(request): d1, d2 = defer.Deferred(), defer.Deferred() reactor.callLater(0.01, d1.callback, 1) reactor.callLater(0.02, d2.callback, request.param) await d1 return d2, @pytest_twisted.inlineCallbacks def test_succeed_blue(foo): x = yield foo[0] if x == "web": raise RuntimeError("baz") """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2, "failed": 1}) @skip_if_no_async_await() def test_async_fixture_no_arguments(testdir, cmd_opts, empty_optional_call): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest_twisted.async_fixture{optional_call} async def scope(request): return request.scope def test_is_function_scope(scope): assert scope == "function" """.format(optional_call=empty_optional_call) testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) @skip_if_no_async_generators() def test_async_yield_fixture_ordered_teardown(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted results = [] @pytest.fixture(scope='function') def sync_fixture(): yield 42 results.append(2) @pytest_twisted.async_yield_fixture(scope='function') async def async_fixture(sync_fixture): yield sync_fixture results.append(1) def test_first(async_fixture): assert async_fixture == 42 def test_second(): assert results == [1, 2] """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2}) @skip_if_no_async_generators() def test_async_yield_fixture_can_await(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer import pytest_twisted @pytest_twisted.async_yield_fixture() async def foo(): d1, d2 = defer.Deferred(), defer.Deferred() reactor.callLater(0.01, d1.callback, 1) reactor.callLater(0.02, d2.callback, 2) await d1 # Twisted doesn't allow calling back with a Deferred as a value. # This deferred is being wrapped up in a tuple to sneak through. # https://github.com/twisted/twisted/blob/c0f1394c7bfb04d97c725a353a1f678fa6a1c602/src/twisted/internet/defer.py#L459 yield d2, @pytest_twisted.ensureDeferred async def test(foo): x = await foo[0] assert x == 2 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) @skip_if_no_async_generators() def test_async_yield_fixture_failed_test(testdir, cmd_opts): test_file = """ import pytest_twisted @pytest_twisted.async_yield_fixture() async def foo(): yield 92 @pytest_twisted.ensureDeferred async def test(foo): assert False """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) rr.stdout.fnmatch_lines(lines2=["E*assert False"]) assert_outcomes(rr, {"failed": 1}) @skip_if_no_async_generators() def test_async_yield_fixture_test_exception(testdir, cmd_opts): test_file = """ import pytest_twisted class UniqueLocalException(Exception): pass @pytest_twisted.async_yield_fixture() async def foo(): yield 92 @pytest_twisted.ensureDeferred async def test(foo): raise UniqueLocalException("some message") """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) rr.stdout.fnmatch_lines(lines2=["E*.UniqueLocalException: some message*"]) assert_outcomes(rr, {"failed": 1}) @skip_if_no_async_generators() def test_async_yield_fixture_yields_twice(testdir, cmd_opts): test_file = """ import pytest_twisted @pytest_twisted.async_yield_fixture() async def foo(): yield 92 yield 36 @pytest_twisted.ensureDeferred async def test(foo): assert foo == 92 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1, "errors": 1}) @skip_if_no_async_generators() def test_async_yield_fixture_teardown_exception(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted class UniqueLocalException(Exception): pass @pytest_twisted.async_yield_fixture() async def foo(request): yield 13 raise UniqueLocalException("some message") @pytest_twisted.ensureDeferred async def test_succeed(foo): assert foo == 13 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) rr.stdout.fnmatch_lines(lines2=["E*.UniqueLocalException: some message*"]) assert_outcomes(rr, {"passed": 1, "errors": 1}) @skip_if_no_async_generators() def test_async_yield_fixture_no_arguments( testdir, cmd_opts, empty_optional_call, ): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest_twisted.async_yield_fixture{optional_call} async def scope(request): yield request.scope def test_is_function_scope(scope): assert scope == "function" """.format(optional_call=empty_optional_call) testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) @skip_if_no_async_generators() def test_async_yield_fixture_function_scope(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted check_me = 0 @pytest_twisted.async_yield_fixture(scope="function") async def foo(): global check_me if check_me != 0: raise Exception('check_me already modified before fixture run') check_me = 1 yield 42 if check_me != 2: raise Exception( 'check_me not updated properly: {}'.format(check_me), ) check_me = 0 def test_first(foo): global check_me assert check_me == 1 assert foo == 42 check_me = 2 def test_second(foo): global check_me assert check_me == 1 assert foo == 42 check_me = 2 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2}) @skip_if_no_async_await() def test_async_simple_fixture_in_fixture(testdir, cmd_opts): test_file = """ import itertools from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest_twisted.async_fixture(name='four') async def fixture_four(): return 4 @pytest_twisted.async_fixture(name='doublefour') async def fixture_doublefour(four): return 2 * four @pytest_twisted.ensureDeferred async def test_four(four): assert four == 4 @pytest_twisted.ensureDeferred async def test_doublefour(doublefour): assert doublefour == 8 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2}) @skip_if_no_async_generators() def test_async_yield_simple_fixture_in_fixture(testdir, cmd_opts): test_file = """ import itertools from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest_twisted.async_yield_fixture(name='four') async def fixture_four(): yield 4 @pytest_twisted.async_yield_fixture(name='doublefour') async def fixture_doublefour(four): yield 2 * four @pytest_twisted.ensureDeferred async def test_four(four): assert four == 4 @pytest_twisted.ensureDeferred async def test_doublefour(doublefour): assert doublefour == 8 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2}) @skip_if_no_async_await() @pytest.mark.parametrize('innerasync', [ pytest.param(truth, id='innerasync={}'.format(truth)) for truth in [True, False] ]) def test_async_fixture_in_fixture(testdir, cmd_opts, innerasync): maybe_async = 'async ' if innerasync else '' maybe_await = 'await ' if innerasync else '' test_file = """ import itertools from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest_twisted.async_fixture(name='increment') async def fixture_increment(): counts = itertools.count() {maybe_async}def increment(): return next(counts) return increment @pytest_twisted.async_fixture(name='doubleincrement') async def fixture_doubleincrement(increment): {maybe_async}def doubleincrement(): n = {maybe_await}increment() return n * 2 return doubleincrement @pytest_twisted.ensureDeferred async def test_increment(increment): first = {maybe_await}increment() second = {maybe_await}increment() assert (first, second) == (0, 1) @pytest_twisted.ensureDeferred async def test_doubleincrement(doubleincrement): first = {maybe_await}doubleincrement() second = {maybe_await}doubleincrement() assert (first, second) == (0, 2) """.format(maybe_async=maybe_async, maybe_await=maybe_await) testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2}) # assert_outcomes(rr, {"passed": 1}) @skip_if_no_async_generators() @pytest.mark.parametrize('innerasync', [ pytest.param(truth, id='innerasync={}'.format(truth)) for truth in [True, False] ]) def test_async_yield_fixture_in_fixture(testdir, cmd_opts, innerasync): maybe_async = 'async ' if innerasync else '' maybe_await = 'await ' if innerasync else '' test_file = """ import itertools from twisted.internet import reactor, defer import pytest import pytest_twisted @pytest_twisted.async_yield_fixture(name='increment') async def fixture_increment(): counts = itertools.count() {maybe_async}def increment(): return next(counts) yield increment @pytest_twisted.async_yield_fixture(name='doubleincrement') async def fixture_doubleincrement(increment): {maybe_async}def doubleincrement(): n = {maybe_await}increment() return n * 2 yield doubleincrement @pytest_twisted.ensureDeferred async def test_increment(increment): first = {maybe_await}increment() second = {maybe_await}increment() assert (first, second) == (0, 1) @pytest_twisted.ensureDeferred async def test_doubleincrement(doubleincrement): first = {maybe_await}doubleincrement() second = {maybe_await}doubleincrement() assert (first, second) == (0, 2) """.format(maybe_async=maybe_async, maybe_await=maybe_await) testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2}) def test_blockon_in_hook(testdir, cmd_opts, request): skip_if_reactor_not(request, "default") conftest_file = """ import pytest_twisted from twisted.internet import reactor, defer def pytest_configure(config): pytest_twisted.init_default_reactor() d1, d2 = defer.Deferred(), defer.Deferred() reactor.callLater(0.01, d1.callback, 1) reactor.callLater(0.02, d2.callback, 1) pytest_twisted.blockon(d1) pytest_twisted.blockon(d2) """ testdir.makeconftest(conftest_file) test_file = """ from twisted.internet import reactor, defer def test_succeed(): d = defer.Deferred() reactor.callLater(0.01, d.callback, 1) return d """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) def test_wrong_reactor(testdir, cmd_opts, request): skip_if_reactor_not(request, "default") conftest_file = """ def pytest_addhooks(): import twisted.internet.reactor twisted.internet.reactor = None """ testdir.makeconftest(conftest_file) test_file = """ def test_succeed(): pass """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert "WrongReactorAlreadyInstalledError" in rr.stderr.str() def test_blockon_in_hook_with_qt5reactor(testdir, cmd_opts, request): skip_if_reactor_not(request, "qt5reactor") conftest_file = """ import pytest_twisted import pytestqt from twisted.internet import defer def pytest_configure(config): pytest_twisted.init_qt5_reactor() d = defer.Deferred() from twisted.internet import reactor reactor.callLater(0.01, d.callback, 1) pytest_twisted.blockon(d) """ testdir.makeconftest(conftest_file) test_file = """ from twisted.internet import reactor, defer def test_succeed(): d = defer.Deferred() reactor.callLater(0.01, d.callback, 1) return d """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) def test_wrong_reactor_with_qt5reactor(testdir, cmd_opts, request): skip_if_reactor_not(request, "qt5reactor") conftest_file = """ def pytest_addhooks(): import twisted.internet.default twisted.internet.default.install() """ testdir.makeconftest(conftest_file) test_file = """ def test_succeed(): pass """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert "WrongReactorAlreadyInstalledError" in rr.stderr.str() def test_pytest_from_reactor_thread(testdir, cmd_opts, request): skip_if_reactor_not(request, "default") test_file = """ import pytest import pytest_twisted from twisted.internet import reactor, defer @pytest.fixture def fix(): d = defer.Deferred() reactor.callLater(0.01, d.callback, 42) return pytest_twisted.blockon(d) def test_simple(fix): assert fix == 42 @pytest_twisted.inlineCallbacks def test_fail(): d = defer.Deferred() reactor.callLater(0.01, d.callback, 1) yield d assert False """ testdir.makepyfile(test_file) runner_file = """ import pytest from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks from twisted.internet.threads import deferToThread codes = [] @inlineCallbacks def main(): try: codes.append((yield deferToThread(pytest.main, ['-k simple']))) codes.append((yield deferToThread(pytest.main, ['-k fail']))) finally: reactor.stop() if __name__ == '__main__': reactor.callLater(0, main) reactor.run() codes == [0, 1] or exit(1) """ testdir.makepyfile(runner=runner_file) # check test file is ok in standalone mode: rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1, "failed": 1}) # test embedded mode: assert testdir.run(sys.executable, "runner.py", timeout=timeout).ret == 0 def test_blockon_in_hook_with_asyncio(testdir, cmd_opts, request): skip_if_reactor_not(request, "asyncio") conftest_file = """ import pytest import pytest_twisted from twisted.internet import defer @pytest.hookimpl(tryfirst=True) def pytest_configure(config): pytest_twisted._use_asyncio_selector_if_required(config=config) pytest_twisted.init_asyncio_reactor() d = defer.Deferred() from twisted.internet import reactor reactor.callLater(0.01, d.callback, 1) pytest_twisted.blockon(d) """ testdir.makeconftest(conftest_file) test_file = """ from twisted.internet import reactor, defer def test_succeed(): d = defer.Deferred() reactor.callLater(0.01, d.callback, 1) return d """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) def test_wrong_reactor_with_asyncio(testdir, cmd_opts, request): skip_if_reactor_not(request, "asyncio") conftest_file = """ import pytest import pytest_twisted @pytest.hookimpl(tryfirst=True) def pytest_configure(config): pytest_twisted._use_asyncio_selector_if_required(config=config) def pytest_addhooks(): import twisted.internet.default twisted.internet.default.install() """ testdir.makeconftest(conftest_file) test_file = """ def test_succeed(): pass """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert "WrongReactorAlreadyInstalledError" in rr.stderr.str() @skip_if_no_async_generators() def test_async_fixture_module_scope(testdir, cmd_opts): test_file = """ from twisted.internet import reactor, defer import pytest import pytest_twisted check_me = 0 @pytest_twisted.async_yield_fixture(scope="module") async def foo(): global check_me if check_me != 0: raise Exception('check_me already modified before fixture run') check_me = 1 yield 42 if check_me != 3: raise Exception( 'check_me not updated properly: {}'.format(check_me), ) check_me = 0 def test_first(foo): global check_me assert check_me == 1 assert foo == 42 check_me = 2 def test_second(foo): global check_me assert check_me == 2 assert foo == 42 check_me = 3 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 2}) def test_inlinecallbacks_method_with_fixture_gets_self(testdir, cmd_opts): test_file = """ import pytest import pytest_twisted from twisted.internet import defer @pytest.fixture def foo(): return 37 class TestClass: @pytest_twisted.inlineCallbacks def test_self_isinstance(self, foo): d = defer.succeed(None) yield d assert isinstance(self, TestClass) """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts) assert_outcomes(rr, {"passed": 1}) def test_inlinecallbacks_method_with_fixture_gets_fixture(testdir, cmd_opts): test_file = """ import pytest import pytest_twisted from twisted.internet import defer @pytest.fixture def foo(): return 37 class TestClass: @pytest_twisted.inlineCallbacks def test_self_isinstance(self, foo): d = defer.succeed(None) yield d assert foo == 37 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) @skip_if_no_async_await() def test_ensuredeferred_method_with_fixture_gets_self(testdir, cmd_opts): test_file = """ import pytest import pytest_twisted @pytest.fixture def foo(): return 37 class TestClass: @pytest_twisted.ensureDeferred async def test_self_isinstance(self, foo): assert isinstance(self, TestClass) """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) @skip_if_no_async_await() def test_ensuredeferred_method_with_fixture_gets_fixture(testdir, cmd_opts): test_file = """ import pytest import pytest_twisted @pytest.fixture def foo(): return 37 class TestClass: @pytest_twisted.ensureDeferred async def test_self_isinstance(self, foo): assert foo == 37 """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) def test_import_pytest_twisted_in_conftest_py_not_a_problem(testdir, cmd_opts): conftest_file = """ import pytest import pytest_twisted @pytest.hookimpl(tryfirst=True) def pytest_configure(config): pytest_twisted._use_asyncio_selector_if_required(config=config) """ testdir.makeconftest(conftest_file) test_file = """ import pytest_twisted def test_succeed(): pass """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) @pytest.mark.parametrize(argnames="kill", argvalues=[False, True]) @pytest.mark.parametrize(argnames="event", argvalues=["shutdown"]) @pytest.mark.parametrize( argnames="phase", argvalues=["before", "during", "after"], ) def test_addSystemEventTrigger(testdir, cmd_opts, kill, event, phase): is_win32 = sys.platform == "win32" is_qt = os.environ.get("REACTOR", "").startswith("qt") is_kill = kill if (is_win32 or is_qt) and is_kill: pytest.xfail(reason="Needs handled on Windows and with qt5reactor.") test_string = "1kljgf90u0lkj13l4jjklsfdo89898y24hlkjalkjs38" test_file = """ import os import signal import pytest_twisted def output_stuff(): print({test_string!r}) @pytest_twisted.inlineCallbacks def test_succeed(): from twisted.internet import reactor reactor.addSystemEventTrigger({phase!r}, {event!r}, output_stuff) if {kill!r}: os.kill(os.getpid(), signal.SIGINT) yield """.format(kill=kill, event=event, phase=phase, test_string=test_string) testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) rr.stdout.fnmatch_lines(lines2=[test_string]) def test_sigint_for_regular_tests(testdir, cmd_opts): test_file = """ import os import signal import time import twisted.internet import twisted.internet.task def test_self_cancel(): os.kill(os.getpid(), signal.SIGINT) time.sleep(10) def test_should_not_run(): assert False """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) if sys.platform != "win32": # on Windows pytest isn't even reporting the status, just stopping... assert_outcomes(rr, {}) rr.stdout.re_match_lines(lines2=[r".* no tests ran in .*"]) pattern = r".*test_should_not_run.*" if pytest_version >= (5, 3, 0): rr.stdout.no_re_match_line(pat=pattern) else: assert re.match(pattern, rr.stdout.str()) is None def test_sigint_for_inline_callbacks_tests(testdir, cmd_opts): test_file = """ import os import signal import twisted.internet import twisted.internet.task import pytest_twisted @pytest_twisted.inlineCallbacks def test_self_cancel(): os.kill(os.getpid(), signal.SIGINT) yield twisted.internet.task.deferLater( twisted.internet.reactor, 9999, lambda: None, ) @pytest_twisted.inlineCallbacks def test_should_not_run(): assert False yield """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) if sys.platform != "win32": # on Windows pytest isn't even reporting the status, just stopping... assert_outcomes(rr, {}) rr.stdout.re_match_lines(lines2=[r".* no tests ran in .*"]) pattern = r".*test_should_not_run.*" if pytest_version >= (5, 3, 0): rr.stdout.no_re_match_line(pat=pattern) else: assert re.match(pattern, rr.stdout.str()) is None @skip_if_no_async_await() @skip_if_hypothesis_unavailable() def test_hypothesis_async_passes(testdir, cmd_opts): test_file = """ import hypothesis import hypothesis.strategies import pytest_twisted @hypothesis.given(x=hypothesis.strategies.integers()) @pytest_twisted.ensureDeferred async def test_async(x): assert isinstance(x, int) """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) @skip_if_hypothesis_unavailable() def test_hypothesis_inline_callbacks_passes(testdir, cmd_opts): test_file = """ import hypothesis import hypothesis.strategies import pytest_twisted @hypothesis.given(x=hypothesis.strategies.integers()) @pytest_twisted.inlineCallbacks def test_inline_callbacks(x): assert isinstance(x, int) return yield """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"passed": 1}) @skip_if_no_async_await() @skip_if_hypothesis_unavailable() def test_hypothesis_async_fails(testdir, cmd_opts): test_file = """ import hypothesis import hypothesis.strategies import pytest_twisted @hypothesis.given(x=hypothesis.strategies.integers()) @pytest_twisted.ensureDeferred async def test_async(x): assert isinstance(x, str) """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=timeout) assert_outcomes(rr, {"failed": 1}) @skip_if_hypothesis_unavailable() def test_hypothesis_inline_callbacks_fails(testdir, cmd_opts): test_file = """ import hypothesis import hypothesis.strategies import pytest_twisted @hypothesis.given(x=hypothesis.strategies.integers()) @pytest_twisted.inlineCallbacks def test_inline_callbacks(x): assert isinstance(x, str) return yield """ testdir.makepyfile(test_file) rr = testdir.run(*cmd_opts, timeout=3 * timeout) assert_outcomes(rr, {"failed": 1}) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710815523.0 pytest-twisted-1.14.1/tox.ini0000644000000000000000000000161114576174443014676 0ustar00rootroot[tox] envlist= py{27,py27,35,36,37,38,39,310,311,py37,py38,py39}-defaultreactor py{35,36,37,38,39,310,311,py37,py38,py39}-asyncioreactor py{35,36,37,38,39,310,311}-pyqt5reactor py{35,36,37,38,39,310,311}-pyside2reactor linting [testenv] deps= greenlet pytest twisted py37,py38,py39,pypy37,pypy38: hypothesis pyqt5reactor,pyside2reactor: pytest-qt pyqt5reactor,pyside2reactor: pytest-xvfb pyqt5reactor,pyside2reactor: pywin32; sys_platform == 'win32' extras= pyqt5reactor: pyqt5 pyside2reactor: pyside2 setenv= defaultreactor: REACTOR = default pyqt5reactor: REACTOR = qt5reactor pyside2reactor: REACTOR = qt5reactor asyncioreactor: REACTOR = asyncio commands= pytest --reactor={env:REACTOR} sitepackages=False download=true [testenv:linting] deps=flake8 commands=flake8 setup.py pytest_twisted.py testing [flake8] ignore=N802