././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1696760037.96903 pytest-timeout-2.2.0/0000755000175000017500000000000014510500346013227 5ustar00flubflub././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1574283337.0 pytest-timeout-2.2.0/LICENSE0000644000175000017500000000207513565324111014243 0ustar00flubflubThe MIT License Copyright (C) 2012, 2014 Floris Bruynooghe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1574283337.0 pytest-timeout-2.2.0/MANIFEST.in0000644000175000017500000000015313565324111014767 0ustar00flubflubinclude MANIFEST.in include LICENSE include test_pytest_timeout.py include failure_demo.py include tox.ini ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1696760037.96903 pytest-timeout-2.2.0/PKG-INFO0000644000175000017500000004204714510500346014333 0ustar00flubflubMetadata-Version: 2.1 Name: pytest-timeout Version: 2.2.0 Summary: pytest plugin to abort hanging tests Home-page: https://github.com/pytest-dev/pytest-timeout Author: Floris Bruynooghe Author-email: flub@devork.be License: MIT Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: Plugins Classifier: Intended Audience :: Developers Classifier: License :: DFSG approved Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only 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 :: Pytest Requires-Python: >=3.7 License-File: LICENSE Requires-Dist: pytest>=5.0.0 ============== pytest-timeout ============== |python| |version| |anaconda| |ci| |pre-commit| .. |version| image:: https://img.shields.io/pypi/v/pytest-timeout.svg :target: https://pypi.python.org/pypi/pytest-timeout .. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-timeout.svg :target: https://anaconda.org/conda-forge/pytest-timeout .. |ci| image:: https://github.com/pytest-dev/pytest-timeout/workflows/build/badge.svg :target: https://github.com/pytest-dev/pytest-timeout/actions .. |python| image:: https://img.shields.io/pypi/pyversions/pytest-timeout.svg :target: https://pypi.python.org/pypi/pytest-timeout/ .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-timeout/master.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-timeout/master .. warning:: Please read this README carefully and only use this plugin if you understand the consequences. This plugin is designed to catch excessively long test durations like deadlocked or hanging tests, it is not designed for precise timings or performance regressions. Remember your test suite should aim to be **fast**, with timeouts being a last resort, not an expected failure mode. This plugin will time each test and terminate it when it takes too long. Termination may or may not be graceful, please see below, but when aborting it will show a stack dump of all thread running at the time. This is useful when running tests under a continuous integration server or simply if you don't know why the test suite hangs. .. note:: While by default on POSIX systems pytest will continue to execute the tests after a test has timed out this is not always possible. Often the only sure way to interrupt a hanging test is by terminating the entire process. As this is a hard termination (``os._exit()``) it will result in no teardown, JUnit XML output etc. But the plugin will ensure you will have the debugging output on stderr nevertheless, which is the most important part at this stage. See below for detailed information on the timeout methods and their side-effects. The pytest-timeout plugin has been tested on Python 3.6 and higher, including PyPy3. See tox.ini for currently tested versions. Usage ===== Install is as simple as e.g.:: pip install pytest-timeout Now you can run the test suite while setting a timeout in seconds, any individual test which takes longer than the given duration will be terminated:: pytest --timeout=300 Furthermore you can also use a decorator to set the timeout for an individual test. If combined with the ``--timeout`` flag this will override the timeout for this individual test: .. code:: python @pytest.mark.timeout(60) def test_foo(): pass By default the plugin will not time out any tests, you must specify a valid timeout for the plugin to interrupt long-running tests. A timeout is always specified as a number of seconds, and can be defined in a number of ways, from low to high priority: 1. You can set a global timeout in the `pytest configuration file`__ using the ``timeout`` option. E.g.: .. code:: ini [pytest] timeout = 300 2. The ``PYTEST_TIMEOUT`` environment variable sets a global timeout overriding a possible value in the configuration file. 3. The ``--timeout`` command line option sets a global timeout overriding both the environment variable and configuration option. 4. Using the ``timeout`` marker_ on test items you can specify timeouts on a per-item basis: .. code:: python @pytest.mark.timeout(300) def test_foo(): pass __ https://docs.pytest.org/en/latest/reference.html#ini-options-ref .. _marker: https://docs.pytest.org/en/latest/mark.html Setting a timeout to 0 seconds disables the timeout, so if you have a global timeout set you can still disable the timeout by using the mark. Timeout Methods =============== Interrupting tests which hang is not always as simple and can be platform dependent. Furthermore some methods of terminating a test might conflict with the code under test itself. The pytest-timeout plugin tries to pick the most suitable method based on your platform, but occasionally you may need to specify a specific timeout method explicitly. If a timeout method does not work your safest bet is to use the *thread* method. thread ------ This is the surest and most portable method. It is also the default on systems not supporting the *signal* method. For each test item the pytest-timeout plugin starts a timer thread which will terminate the whole process after the specified timeout. When a test item finishes this timer thread is cancelled and the test run continues. The downsides of this method are that there is a relatively large overhead for running each test and that test runs are not completed. This means that other pytest features, like e.g. JUnit XML output or fixture teardown, will not function normally. The second issue might be alleviated by using the ``--boxed`` option of the pytest-xdist_ plugin. .. _pytest-xdist: https://pypi.org/project/pytest-xdist/ The benefit of this method is that it will always work. Furthermore it will still provide you debugging information by printing the stacks of all the threads in the application to stderr. signal ------ If the system supports the SIGALRM signal the *signal* method will be used by default. This method schedules an alarm when the test item starts and cancels the alarm when the test finishes. If the alarm expires during the test the signal handler will dump the stack of any other threads running to stderr and use ``pytest.fail()`` to interrupt the test. The benefit of this method is that the pytest process is not terminated and the test run can complete normally. The main issue to look out for with this method is that it may interfere with the code under test. If the code under test uses SIGALRM itself things will go wrong and you will have to choose the *thread* method. Specifying the Timeout Method ----------------------------- The timeout method can be specified by using the ``timeout_method`` option in the `pytest configuration file`__, the ``--timeout_method`` command line parameter or the ``timeout`` marker_. Simply set their value to the string ``thread`` or ``signal`` to override the default method. On a marker this is done using the ``method`` keyword: .. code:: python @pytest.mark.timeout(method="thread") def test_foo(): pass __ https://docs.pytest.org/en/latest/reference.html#ini-options-ref .. _marker: https://docs.pytest.org/en/latest/mark.html The ``timeout`` Marker API ========================== The full signature of the timeout marker is: .. code:: python pytest.mark.timeout(timeout=0, method=DEFAULT_METHOD) You can use either positional or keyword arguments for both the timeout and the method. Neither needs to be present. See the marker api documentation_ and examples_ for the various ways markers can be applied to test items. .. _documentation: https://docs.pytest.org/en/latest/mark.html .. _examples: https://docs.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules Timeouts in Fixture Teardown ============================ The plugin will happily terminate timeouts in the finalisers of fixtures. The timeout specified applies to the entire process of setting up fixtures, running the tests and finalising the fixtures. However when a timeout occurs in a fixture finaliser and the test suite continues, i.e. the signal method is used, it must be realised that subsequent fixtures which need to be finalised might not have been executed, which could result in a broken test-suite anyway. In case of doubt the thread method which terminates the entire process might result in clearer output. Avoiding timeouts in Fixtures ============================= The timeout applies to the entire test including any fixtures which may need to be setup or torn down for the test (the exact affected fixtures depends on which scope they are and whether other tests will still use the same fixture). If the timeouts really are too short to include fixture durations, firstly make the timeouts larger ;). If this really isn't an option a ``timeout_func_only`` boolean setting exists which can be set in the pytest ini configuration file, as documented in ``pytest --help``. Debugger Detection ================== This plugin tries to avoid triggering the timeout when a debugger is detected. This is mostly a convenience so you do not need to remember to disable the timeout when interactively debugging. The way this plugin detects whether or not a debugging session is active is by checking if a trace function is set and if one is, it check to see if the module it belongs to is present in a set of known debugging frameworks modules OR if pytest itself drops you into a pdb session using ``--pdb`` or similar. This functionality can be disabled with the ``--disable-debugger-detection`` flag or the corresponding ``timeout_disable_debugger_detection`` ini setting / environment variable. Extending pytest-timeout with plugins ===================================== ``pytest-timeout`` provides two hooks that can be used for extending the tool. These hooks are used for setting the timeout timer and cancelling it if the timeout is not reached. For example, ``pytest-asyncio`` can provide asyncio-specific code that generates better traceback and points on timed out ``await`` instead of the running loop iteration. See `pytest hooks documentation `_ for more info regarding to use custom hooks. ``pytest_timeout_set_timer`` ---------------------------- .. code:: python @pytest.hookspec(firstresult=True) def pytest_timeout_set_timer(item, settings): """Called at timeout setup. 'item' is a pytest node to setup timeout for. 'settings' is Settings namedtuple (described below). Can be overridden by plugins for alternative timeout implementation strategies. """ ``Settings`` ------------ When ``pytest_timeout_set_timer`` is called, ``settings`` argument is passed. The argument has ``Settings`` namedtuple type with the following fields: +-----------+-------+--------------------------------------------------------+ |Attribute | Index | Value | +===========+=======+========================================================+ | timeout | 0 | timeout in seconds or ``None`` for no timeout | +-----------+-------+--------------------------------------------------------+ | method | 1 | Method mechanism, | | | | ``'signal'`` and ``'thread'`` are supported by default | +-----------+-------+--------------------------------------------------------+ | func_only | 2 | Apply timeout to test function only if ``True``, | | | | wrap all test function and its fixtures otherwise | +-----------+-------+--------------------------------------------------------+ ``pytest_timeout_cancel_timer`` ------------------------------- .. code:: python @pytest.hookspec(firstresult=True) def pytest_timeout_cancel_timer(item): """Called at timeout teardown. 'item' is a pytest node which was used for timeout setup. Can be overridden by plugins for alternative timeout implementation strategies. """ ``is_debugging`` ---------------- When the timeout occurs, user can open the debugger session. In this case, the timeout should be discarded. A custom hook can check this case by calling ``is_debugging()`` function: .. code:: python import pytest import pytest_timeout def on_timeout(): if pytest_timeout.is_debugging(): return pytest.fail("+++ Timeout +++") Changelog ========= 2.2.0 ----- - Add ``--timeout-disable-debugger-detection`` flag, thanks Michael Peters 2.1.0 ----- - Get terminal width from shutil instead of deprecated py, thanks Andrew Svetlov. - Add an API for extending ``pytest-timeout`` functionality with third-party plugins, thanks Andrew Svetlov. 2.0.2 ----- - Fix debugger detection on OSX, thanks Alexander Pacha. 2.0.1 ----- - Fix Python 2 removal, thanks Nicusor Picatureanu. 2.0.0 ----- - Increase pytest requirement to >=5.0.0. Thanks Dominic Davis-Foster. - Use thread timeout method when plugin is not called from main thread to avoid crash. - Fix pycharm debugger detection so timeouts are not triggered during debugger usage. - Dropped support for Python 2, minimum pytest version supported is 5.0.0. 1.4.2 ----- - Fix compatibility when run with pytest pre-releases, thanks Bruno Oliveira, - Fix detection of third-party debuggers, thanks Bruno Oliveira. 1.4.1 ----- - Fix coverage compatibility which was broken by 1.4.0. 1.4.0 ----- - Better detection of when we are debugging, thanks Mattwmaster58. 1.3.4 ----- - Give the threads a name to help debugging, thanks Thomas Grainger. - Changed location to https://github.com/pytest-dev/pytest-timeout because bitbucket is dropping mercurial support. Thanks Thomas Grainger and Bruno Oliveira. 1.3.3 ----- - Fix support for pytest >= 3.10. 1.3.2 ----- - This changelog was omitted for the 1.3.2 release and was added afterwards. Apologies for the confusion. - Fix pytest 3.7.3 compatibility. The capture API had changed slightly and this needed fixing. Thanks Bruno Oliveira for the contribution. 1.3.1 ----- - Fix deprecation warning on Python 3.6. Thanks Mickaël Schoentgen - Create a valid tag for the release. Somehow this didn't happen for 1.3.0, that tag points to a non-existing commit. 1.3.0 ----- - Make it possible to only run the timeout timer on the test function and not the whole fixture setup + test + teardown duration. Thanks Pedro Algarvio for the work! - Use the new pytest marker API, Thanks Pedro Algarvio for the work! 1.2.1 ----- - Fix for pytest 3.3, thanks Bruno Oliveira. - Update supported python versions: - Add CPython 3.6. - Drop CPyhon 2.6 (as did pytest 3.3) - Drop CPyhon 3.3 - Drop CPyhon 3.4 1.2.0 ----- * Allow using floats as timeout instead of only integers, thanks Tom Myers. 1.1.0 ----- * Report (default) timeout duration in header, thanks Holger Krekel. 1.0.0 ----- * Bump version to 1.0 to commit to semantic versioning. * Fix issue #12: Now compatible with pytest 2.8, thanks Holger Krekel. * No longer test with pexpect on py26 as it is no longer supported * Require pytest 2.8 and use new hookimpl decorator 0.5 --- * Timeouts will no longer be triggered when inside an interactive pdb session started by ``pytest.set_trace()`` / ``pdb.set_trace()``. * Add pypy3 environment to tox.ini. * Transfer repository to pytest-dev team account. 0.4 --- * Support timeouts happening in (session scoped) finalizers. * Change command line option --timeout_method into --timeout-method for consistency with pytest 0.3 --- * Added the PYTEST_TIMEOUT environment variable as a way of specifying the timeout (closes issue #2). * More flexible marker argument parsing: you can now specify the method using a positional argument. * The plugin is now enabled by default. There is no longer a need to specify ``timeout=0`` in the configuration file or on the command line simply so that a marker would work. 0.2 --- * Add a marker to modify the timeout delay using a @pytest.timeout(N) syntax, thanks to Laurant Brack for the initial code. * Allow the timeout marker to select the timeout method using the ``method`` keyword argument. * Rename the --nosigalrm option to --method=thread to future proof support for eventlet and gevent. Thanks to Ronny Pfannschmidt for the hint. * Add ``timeout`` and ``timeout_method`` items to the configuration file so you can enable and configure the plugin using the ini file. Thanks to Holger Krekel and Ronny Pfannschmidt for the hints. * Tested (and fixed) for python 2.6, 2.7 and 3.2. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696446464.0 pytest-timeout-2.2.0/README.rst0000644000175000017500000003752614507334000014731 0ustar00flubflub============== pytest-timeout ============== |python| |version| |anaconda| |ci| |pre-commit| .. |version| image:: https://img.shields.io/pypi/v/pytest-timeout.svg :target: https://pypi.python.org/pypi/pytest-timeout .. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-timeout.svg :target: https://anaconda.org/conda-forge/pytest-timeout .. |ci| image:: https://github.com/pytest-dev/pytest-timeout/workflows/build/badge.svg :target: https://github.com/pytest-dev/pytest-timeout/actions .. |python| image:: https://img.shields.io/pypi/pyversions/pytest-timeout.svg :target: https://pypi.python.org/pypi/pytest-timeout/ .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-timeout/master.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-timeout/master .. warning:: Please read this README carefully and only use this plugin if you understand the consequences. This plugin is designed to catch excessively long test durations like deadlocked or hanging tests, it is not designed for precise timings or performance regressions. Remember your test suite should aim to be **fast**, with timeouts being a last resort, not an expected failure mode. This plugin will time each test and terminate it when it takes too long. Termination may or may not be graceful, please see below, but when aborting it will show a stack dump of all thread running at the time. This is useful when running tests under a continuous integration server or simply if you don't know why the test suite hangs. .. note:: While by default on POSIX systems pytest will continue to execute the tests after a test has timed out this is not always possible. Often the only sure way to interrupt a hanging test is by terminating the entire process. As this is a hard termination (``os._exit()``) it will result in no teardown, JUnit XML output etc. But the plugin will ensure you will have the debugging output on stderr nevertheless, which is the most important part at this stage. See below for detailed information on the timeout methods and their side-effects. The pytest-timeout plugin has been tested on Python 3.6 and higher, including PyPy3. See tox.ini for currently tested versions. Usage ===== Install is as simple as e.g.:: pip install pytest-timeout Now you can run the test suite while setting a timeout in seconds, any individual test which takes longer than the given duration will be terminated:: pytest --timeout=300 Furthermore you can also use a decorator to set the timeout for an individual test. If combined with the ``--timeout`` flag this will override the timeout for this individual test: .. code:: python @pytest.mark.timeout(60) def test_foo(): pass By default the plugin will not time out any tests, you must specify a valid timeout for the plugin to interrupt long-running tests. A timeout is always specified as a number of seconds, and can be defined in a number of ways, from low to high priority: 1. You can set a global timeout in the `pytest configuration file`__ using the ``timeout`` option. E.g.: .. code:: ini [pytest] timeout = 300 2. The ``PYTEST_TIMEOUT`` environment variable sets a global timeout overriding a possible value in the configuration file. 3. The ``--timeout`` command line option sets a global timeout overriding both the environment variable and configuration option. 4. Using the ``timeout`` marker_ on test items you can specify timeouts on a per-item basis: .. code:: python @pytest.mark.timeout(300) def test_foo(): pass __ https://docs.pytest.org/en/latest/reference.html#ini-options-ref .. _marker: https://docs.pytest.org/en/latest/mark.html Setting a timeout to 0 seconds disables the timeout, so if you have a global timeout set you can still disable the timeout by using the mark. Timeout Methods =============== Interrupting tests which hang is not always as simple and can be platform dependent. Furthermore some methods of terminating a test might conflict with the code under test itself. The pytest-timeout plugin tries to pick the most suitable method based on your platform, but occasionally you may need to specify a specific timeout method explicitly. If a timeout method does not work your safest bet is to use the *thread* method. thread ------ This is the surest and most portable method. It is also the default on systems not supporting the *signal* method. For each test item the pytest-timeout plugin starts a timer thread which will terminate the whole process after the specified timeout. When a test item finishes this timer thread is cancelled and the test run continues. The downsides of this method are that there is a relatively large overhead for running each test and that test runs are not completed. This means that other pytest features, like e.g. JUnit XML output or fixture teardown, will not function normally. The second issue might be alleviated by using the ``--boxed`` option of the pytest-xdist_ plugin. .. _pytest-xdist: https://pypi.org/project/pytest-xdist/ The benefit of this method is that it will always work. Furthermore it will still provide you debugging information by printing the stacks of all the threads in the application to stderr. signal ------ If the system supports the SIGALRM signal the *signal* method will be used by default. This method schedules an alarm when the test item starts and cancels the alarm when the test finishes. If the alarm expires during the test the signal handler will dump the stack of any other threads running to stderr and use ``pytest.fail()`` to interrupt the test. The benefit of this method is that the pytest process is not terminated and the test run can complete normally. The main issue to look out for with this method is that it may interfere with the code under test. If the code under test uses SIGALRM itself things will go wrong and you will have to choose the *thread* method. Specifying the Timeout Method ----------------------------- The timeout method can be specified by using the ``timeout_method`` option in the `pytest configuration file`__, the ``--timeout_method`` command line parameter or the ``timeout`` marker_. Simply set their value to the string ``thread`` or ``signal`` to override the default method. On a marker this is done using the ``method`` keyword: .. code:: python @pytest.mark.timeout(method="thread") def test_foo(): pass __ https://docs.pytest.org/en/latest/reference.html#ini-options-ref .. _marker: https://docs.pytest.org/en/latest/mark.html The ``timeout`` Marker API ========================== The full signature of the timeout marker is: .. code:: python pytest.mark.timeout(timeout=0, method=DEFAULT_METHOD) You can use either positional or keyword arguments for both the timeout and the method. Neither needs to be present. See the marker api documentation_ and examples_ for the various ways markers can be applied to test items. .. _documentation: https://docs.pytest.org/en/latest/mark.html .. _examples: https://docs.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules Timeouts in Fixture Teardown ============================ The plugin will happily terminate timeouts in the finalisers of fixtures. The timeout specified applies to the entire process of setting up fixtures, running the tests and finalising the fixtures. However when a timeout occurs in a fixture finaliser and the test suite continues, i.e. the signal method is used, it must be realised that subsequent fixtures which need to be finalised might not have been executed, which could result in a broken test-suite anyway. In case of doubt the thread method which terminates the entire process might result in clearer output. Avoiding timeouts in Fixtures ============================= The timeout applies to the entire test including any fixtures which may need to be setup or torn down for the test (the exact affected fixtures depends on which scope they are and whether other tests will still use the same fixture). If the timeouts really are too short to include fixture durations, firstly make the timeouts larger ;). If this really isn't an option a ``timeout_func_only`` boolean setting exists which can be set in the pytest ini configuration file, as documented in ``pytest --help``. Debugger Detection ================== This plugin tries to avoid triggering the timeout when a debugger is detected. This is mostly a convenience so you do not need to remember to disable the timeout when interactively debugging. The way this plugin detects whether or not a debugging session is active is by checking if a trace function is set and if one is, it check to see if the module it belongs to is present in a set of known debugging frameworks modules OR if pytest itself drops you into a pdb session using ``--pdb`` or similar. This functionality can be disabled with the ``--disable-debugger-detection`` flag or the corresponding ``timeout_disable_debugger_detection`` ini setting / environment variable. Extending pytest-timeout with plugins ===================================== ``pytest-timeout`` provides two hooks that can be used for extending the tool. These hooks are used for setting the timeout timer and cancelling it if the timeout is not reached. For example, ``pytest-asyncio`` can provide asyncio-specific code that generates better traceback and points on timed out ``await`` instead of the running loop iteration. See `pytest hooks documentation `_ for more info regarding to use custom hooks. ``pytest_timeout_set_timer`` ---------------------------- .. code:: python @pytest.hookspec(firstresult=True) def pytest_timeout_set_timer(item, settings): """Called at timeout setup. 'item' is a pytest node to setup timeout for. 'settings' is Settings namedtuple (described below). Can be overridden by plugins for alternative timeout implementation strategies. """ ``Settings`` ------------ When ``pytest_timeout_set_timer`` is called, ``settings`` argument is passed. The argument has ``Settings`` namedtuple type with the following fields: +-----------+-------+--------------------------------------------------------+ |Attribute | Index | Value | +===========+=======+========================================================+ | timeout | 0 | timeout in seconds or ``None`` for no timeout | +-----------+-------+--------------------------------------------------------+ | method | 1 | Method mechanism, | | | | ``'signal'`` and ``'thread'`` are supported by default | +-----------+-------+--------------------------------------------------------+ | func_only | 2 | Apply timeout to test function only if ``True``, | | | | wrap all test function and its fixtures otherwise | +-----------+-------+--------------------------------------------------------+ ``pytest_timeout_cancel_timer`` ------------------------------- .. code:: python @pytest.hookspec(firstresult=True) def pytest_timeout_cancel_timer(item): """Called at timeout teardown. 'item' is a pytest node which was used for timeout setup. Can be overridden by plugins for alternative timeout implementation strategies. """ ``is_debugging`` ---------------- When the timeout occurs, user can open the debugger session. In this case, the timeout should be discarded. A custom hook can check this case by calling ``is_debugging()`` function: .. code:: python import pytest import pytest_timeout def on_timeout(): if pytest_timeout.is_debugging(): return pytest.fail("+++ Timeout +++") Changelog ========= 2.2.0 ----- - Add ``--timeout-disable-debugger-detection`` flag, thanks Michael Peters 2.1.0 ----- - Get terminal width from shutil instead of deprecated py, thanks Andrew Svetlov. - Add an API for extending ``pytest-timeout`` functionality with third-party plugins, thanks Andrew Svetlov. 2.0.2 ----- - Fix debugger detection on OSX, thanks Alexander Pacha. 2.0.1 ----- - Fix Python 2 removal, thanks Nicusor Picatureanu. 2.0.0 ----- - Increase pytest requirement to >=5.0.0. Thanks Dominic Davis-Foster. - Use thread timeout method when plugin is not called from main thread to avoid crash. - Fix pycharm debugger detection so timeouts are not triggered during debugger usage. - Dropped support for Python 2, minimum pytest version supported is 5.0.0. 1.4.2 ----- - Fix compatibility when run with pytest pre-releases, thanks Bruno Oliveira, - Fix detection of third-party debuggers, thanks Bruno Oliveira. 1.4.1 ----- - Fix coverage compatibility which was broken by 1.4.0. 1.4.0 ----- - Better detection of when we are debugging, thanks Mattwmaster58. 1.3.4 ----- - Give the threads a name to help debugging, thanks Thomas Grainger. - Changed location to https://github.com/pytest-dev/pytest-timeout because bitbucket is dropping mercurial support. Thanks Thomas Grainger and Bruno Oliveira. 1.3.3 ----- - Fix support for pytest >= 3.10. 1.3.2 ----- - This changelog was omitted for the 1.3.2 release and was added afterwards. Apologies for the confusion. - Fix pytest 3.7.3 compatibility. The capture API had changed slightly and this needed fixing. Thanks Bruno Oliveira for the contribution. 1.3.1 ----- - Fix deprecation warning on Python 3.6. Thanks Mickaël Schoentgen - Create a valid tag for the release. Somehow this didn't happen for 1.3.0, that tag points to a non-existing commit. 1.3.0 ----- - Make it possible to only run the timeout timer on the test function and not the whole fixture setup + test + teardown duration. Thanks Pedro Algarvio for the work! - Use the new pytest marker API, Thanks Pedro Algarvio for the work! 1.2.1 ----- - Fix for pytest 3.3, thanks Bruno Oliveira. - Update supported python versions: - Add CPython 3.6. - Drop CPyhon 2.6 (as did pytest 3.3) - Drop CPyhon 3.3 - Drop CPyhon 3.4 1.2.0 ----- * Allow using floats as timeout instead of only integers, thanks Tom Myers. 1.1.0 ----- * Report (default) timeout duration in header, thanks Holger Krekel. 1.0.0 ----- * Bump version to 1.0 to commit to semantic versioning. * Fix issue #12: Now compatible with pytest 2.8, thanks Holger Krekel. * No longer test with pexpect on py26 as it is no longer supported * Require pytest 2.8 and use new hookimpl decorator 0.5 --- * Timeouts will no longer be triggered when inside an interactive pdb session started by ``pytest.set_trace()`` / ``pdb.set_trace()``. * Add pypy3 environment to tox.ini. * Transfer repository to pytest-dev team account. 0.4 --- * Support timeouts happening in (session scoped) finalizers. * Change command line option --timeout_method into --timeout-method for consistency with pytest 0.3 --- * Added the PYTEST_TIMEOUT environment variable as a way of specifying the timeout (closes issue #2). * More flexible marker argument parsing: you can now specify the method using a positional argument. * The plugin is now enabled by default. There is no longer a need to specify ``timeout=0`` in the configuration file or on the command line simply so that a marker would work. 0.2 --- * Add a marker to modify the timeout delay using a @pytest.timeout(N) syntax, thanks to Laurant Brack for the initial code. * Allow the timeout marker to select the timeout method using the ``method`` keyword argument. * Rename the --nosigalrm option to --method=thread to future proof support for eventlet and gevent. Thanks to Ronny Pfannschmidt for the hint. * Add ``timeout`` and ``timeout_method`` items to the configuration file so you can enable and configure the plugin using the ini file. Thanks to Holger Krekel and Ronny Pfannschmidt for the hints. * Tested (and fixed) for python 2.6, 2.7 and 3.2. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1626635195.0 pytest-timeout-2.2.0/failure_demo.py0000644000175000017500000000125114075075673016253 0ustar00flubflub"""Demonstration of timeout failures using pytest_timeout. To use this demo, invoke pytest on it:: pytest failure_demo.py """ import threading import time import pytest def sleep(s): """Sleep for a while, possibly triggering a timeout. Also adds another function on the stack showing off the stack. """ # Separate function to demonstrate nested calls time.sleep(s) @pytest.mark.timeout(1) def test_simple(): """Basic timeout demonstration.""" sleep(2) def _run(): sleep(2) @pytest.mark.timeout(1) def test_thread(): """Timeout when multiple threads are running.""" t = threading.Thread(target=_run) t.start() sleep(2) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696759578.0 pytest-timeout-2.2.0/pyproject.toml0000644000175000017500000000012114510477432016146 0ustar00flubflub[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1696760037.96903 pytest-timeout-2.2.0/pytest_timeout.egg-info/0000755000175000017500000000000014510500346020017 5ustar00flubflub././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696760037.0 pytest-timeout-2.2.0/pytest_timeout.egg-info/PKG-INFO0000644000175000017500000004204714510500345021122 0ustar00flubflubMetadata-Version: 2.1 Name: pytest-timeout Version: 2.2.0 Summary: pytest plugin to abort hanging tests Home-page: https://github.com/pytest-dev/pytest-timeout Author: Floris Bruynooghe Author-email: flub@devork.be License: MIT Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: Plugins Classifier: Intended Audience :: Developers Classifier: License :: DFSG approved Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only 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 :: Pytest Requires-Python: >=3.7 License-File: LICENSE Requires-Dist: pytest>=5.0.0 ============== pytest-timeout ============== |python| |version| |anaconda| |ci| |pre-commit| .. |version| image:: https://img.shields.io/pypi/v/pytest-timeout.svg :target: https://pypi.python.org/pypi/pytest-timeout .. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-timeout.svg :target: https://anaconda.org/conda-forge/pytest-timeout .. |ci| image:: https://github.com/pytest-dev/pytest-timeout/workflows/build/badge.svg :target: https://github.com/pytest-dev/pytest-timeout/actions .. |python| image:: https://img.shields.io/pypi/pyversions/pytest-timeout.svg :target: https://pypi.python.org/pypi/pytest-timeout/ .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest-timeout/master.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest-timeout/master .. warning:: Please read this README carefully and only use this plugin if you understand the consequences. This plugin is designed to catch excessively long test durations like deadlocked or hanging tests, it is not designed for precise timings or performance regressions. Remember your test suite should aim to be **fast**, with timeouts being a last resort, not an expected failure mode. This plugin will time each test and terminate it when it takes too long. Termination may or may not be graceful, please see below, but when aborting it will show a stack dump of all thread running at the time. This is useful when running tests under a continuous integration server or simply if you don't know why the test suite hangs. .. note:: While by default on POSIX systems pytest will continue to execute the tests after a test has timed out this is not always possible. Often the only sure way to interrupt a hanging test is by terminating the entire process. As this is a hard termination (``os._exit()``) it will result in no teardown, JUnit XML output etc. But the plugin will ensure you will have the debugging output on stderr nevertheless, which is the most important part at this stage. See below for detailed information on the timeout methods and their side-effects. The pytest-timeout plugin has been tested on Python 3.6 and higher, including PyPy3. See tox.ini for currently tested versions. Usage ===== Install is as simple as e.g.:: pip install pytest-timeout Now you can run the test suite while setting a timeout in seconds, any individual test which takes longer than the given duration will be terminated:: pytest --timeout=300 Furthermore you can also use a decorator to set the timeout for an individual test. If combined with the ``--timeout`` flag this will override the timeout for this individual test: .. code:: python @pytest.mark.timeout(60) def test_foo(): pass By default the plugin will not time out any tests, you must specify a valid timeout for the plugin to interrupt long-running tests. A timeout is always specified as a number of seconds, and can be defined in a number of ways, from low to high priority: 1. You can set a global timeout in the `pytest configuration file`__ using the ``timeout`` option. E.g.: .. code:: ini [pytest] timeout = 300 2. The ``PYTEST_TIMEOUT`` environment variable sets a global timeout overriding a possible value in the configuration file. 3. The ``--timeout`` command line option sets a global timeout overriding both the environment variable and configuration option. 4. Using the ``timeout`` marker_ on test items you can specify timeouts on a per-item basis: .. code:: python @pytest.mark.timeout(300) def test_foo(): pass __ https://docs.pytest.org/en/latest/reference.html#ini-options-ref .. _marker: https://docs.pytest.org/en/latest/mark.html Setting a timeout to 0 seconds disables the timeout, so if you have a global timeout set you can still disable the timeout by using the mark. Timeout Methods =============== Interrupting tests which hang is not always as simple and can be platform dependent. Furthermore some methods of terminating a test might conflict with the code under test itself. The pytest-timeout plugin tries to pick the most suitable method based on your platform, but occasionally you may need to specify a specific timeout method explicitly. If a timeout method does not work your safest bet is to use the *thread* method. thread ------ This is the surest and most portable method. It is also the default on systems not supporting the *signal* method. For each test item the pytest-timeout plugin starts a timer thread which will terminate the whole process after the specified timeout. When a test item finishes this timer thread is cancelled and the test run continues. The downsides of this method are that there is a relatively large overhead for running each test and that test runs are not completed. This means that other pytest features, like e.g. JUnit XML output or fixture teardown, will not function normally. The second issue might be alleviated by using the ``--boxed`` option of the pytest-xdist_ plugin. .. _pytest-xdist: https://pypi.org/project/pytest-xdist/ The benefit of this method is that it will always work. Furthermore it will still provide you debugging information by printing the stacks of all the threads in the application to stderr. signal ------ If the system supports the SIGALRM signal the *signal* method will be used by default. This method schedules an alarm when the test item starts and cancels the alarm when the test finishes. If the alarm expires during the test the signal handler will dump the stack of any other threads running to stderr and use ``pytest.fail()`` to interrupt the test. The benefit of this method is that the pytest process is not terminated and the test run can complete normally. The main issue to look out for with this method is that it may interfere with the code under test. If the code under test uses SIGALRM itself things will go wrong and you will have to choose the *thread* method. Specifying the Timeout Method ----------------------------- The timeout method can be specified by using the ``timeout_method`` option in the `pytest configuration file`__, the ``--timeout_method`` command line parameter or the ``timeout`` marker_. Simply set their value to the string ``thread`` or ``signal`` to override the default method. On a marker this is done using the ``method`` keyword: .. code:: python @pytest.mark.timeout(method="thread") def test_foo(): pass __ https://docs.pytest.org/en/latest/reference.html#ini-options-ref .. _marker: https://docs.pytest.org/en/latest/mark.html The ``timeout`` Marker API ========================== The full signature of the timeout marker is: .. code:: python pytest.mark.timeout(timeout=0, method=DEFAULT_METHOD) You can use either positional or keyword arguments for both the timeout and the method. Neither needs to be present. See the marker api documentation_ and examples_ for the various ways markers can be applied to test items. .. _documentation: https://docs.pytest.org/en/latest/mark.html .. _examples: https://docs.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules Timeouts in Fixture Teardown ============================ The plugin will happily terminate timeouts in the finalisers of fixtures. The timeout specified applies to the entire process of setting up fixtures, running the tests and finalising the fixtures. However when a timeout occurs in a fixture finaliser and the test suite continues, i.e. the signal method is used, it must be realised that subsequent fixtures which need to be finalised might not have been executed, which could result in a broken test-suite anyway. In case of doubt the thread method which terminates the entire process might result in clearer output. Avoiding timeouts in Fixtures ============================= The timeout applies to the entire test including any fixtures which may need to be setup or torn down for the test (the exact affected fixtures depends on which scope they are and whether other tests will still use the same fixture). If the timeouts really are too short to include fixture durations, firstly make the timeouts larger ;). If this really isn't an option a ``timeout_func_only`` boolean setting exists which can be set in the pytest ini configuration file, as documented in ``pytest --help``. Debugger Detection ================== This plugin tries to avoid triggering the timeout when a debugger is detected. This is mostly a convenience so you do not need to remember to disable the timeout when interactively debugging. The way this plugin detects whether or not a debugging session is active is by checking if a trace function is set and if one is, it check to see if the module it belongs to is present in a set of known debugging frameworks modules OR if pytest itself drops you into a pdb session using ``--pdb`` or similar. This functionality can be disabled with the ``--disable-debugger-detection`` flag or the corresponding ``timeout_disable_debugger_detection`` ini setting / environment variable. Extending pytest-timeout with plugins ===================================== ``pytest-timeout`` provides two hooks that can be used for extending the tool. These hooks are used for setting the timeout timer and cancelling it if the timeout is not reached. For example, ``pytest-asyncio`` can provide asyncio-specific code that generates better traceback and points on timed out ``await`` instead of the running loop iteration. See `pytest hooks documentation `_ for more info regarding to use custom hooks. ``pytest_timeout_set_timer`` ---------------------------- .. code:: python @pytest.hookspec(firstresult=True) def pytest_timeout_set_timer(item, settings): """Called at timeout setup. 'item' is a pytest node to setup timeout for. 'settings' is Settings namedtuple (described below). Can be overridden by plugins for alternative timeout implementation strategies. """ ``Settings`` ------------ When ``pytest_timeout_set_timer`` is called, ``settings`` argument is passed. The argument has ``Settings`` namedtuple type with the following fields: +-----------+-------+--------------------------------------------------------+ |Attribute | Index | Value | +===========+=======+========================================================+ | timeout | 0 | timeout in seconds or ``None`` for no timeout | +-----------+-------+--------------------------------------------------------+ | method | 1 | Method mechanism, | | | | ``'signal'`` and ``'thread'`` are supported by default | +-----------+-------+--------------------------------------------------------+ | func_only | 2 | Apply timeout to test function only if ``True``, | | | | wrap all test function and its fixtures otherwise | +-----------+-------+--------------------------------------------------------+ ``pytest_timeout_cancel_timer`` ------------------------------- .. code:: python @pytest.hookspec(firstresult=True) def pytest_timeout_cancel_timer(item): """Called at timeout teardown. 'item' is a pytest node which was used for timeout setup. Can be overridden by plugins for alternative timeout implementation strategies. """ ``is_debugging`` ---------------- When the timeout occurs, user can open the debugger session. In this case, the timeout should be discarded. A custom hook can check this case by calling ``is_debugging()`` function: .. code:: python import pytest import pytest_timeout def on_timeout(): if pytest_timeout.is_debugging(): return pytest.fail("+++ Timeout +++") Changelog ========= 2.2.0 ----- - Add ``--timeout-disable-debugger-detection`` flag, thanks Michael Peters 2.1.0 ----- - Get terminal width from shutil instead of deprecated py, thanks Andrew Svetlov. - Add an API for extending ``pytest-timeout`` functionality with third-party plugins, thanks Andrew Svetlov. 2.0.2 ----- - Fix debugger detection on OSX, thanks Alexander Pacha. 2.0.1 ----- - Fix Python 2 removal, thanks Nicusor Picatureanu. 2.0.0 ----- - Increase pytest requirement to >=5.0.0. Thanks Dominic Davis-Foster. - Use thread timeout method when plugin is not called from main thread to avoid crash. - Fix pycharm debugger detection so timeouts are not triggered during debugger usage. - Dropped support for Python 2, minimum pytest version supported is 5.0.0. 1.4.2 ----- - Fix compatibility when run with pytest pre-releases, thanks Bruno Oliveira, - Fix detection of third-party debuggers, thanks Bruno Oliveira. 1.4.1 ----- - Fix coverage compatibility which was broken by 1.4.0. 1.4.0 ----- - Better detection of when we are debugging, thanks Mattwmaster58. 1.3.4 ----- - Give the threads a name to help debugging, thanks Thomas Grainger. - Changed location to https://github.com/pytest-dev/pytest-timeout because bitbucket is dropping mercurial support. Thanks Thomas Grainger and Bruno Oliveira. 1.3.3 ----- - Fix support for pytest >= 3.10. 1.3.2 ----- - This changelog was omitted for the 1.3.2 release and was added afterwards. Apologies for the confusion. - Fix pytest 3.7.3 compatibility. The capture API had changed slightly and this needed fixing. Thanks Bruno Oliveira for the contribution. 1.3.1 ----- - Fix deprecation warning on Python 3.6. Thanks Mickaël Schoentgen - Create a valid tag for the release. Somehow this didn't happen for 1.3.0, that tag points to a non-existing commit. 1.3.0 ----- - Make it possible to only run the timeout timer on the test function and not the whole fixture setup + test + teardown duration. Thanks Pedro Algarvio for the work! - Use the new pytest marker API, Thanks Pedro Algarvio for the work! 1.2.1 ----- - Fix for pytest 3.3, thanks Bruno Oliveira. - Update supported python versions: - Add CPython 3.6. - Drop CPyhon 2.6 (as did pytest 3.3) - Drop CPyhon 3.3 - Drop CPyhon 3.4 1.2.0 ----- * Allow using floats as timeout instead of only integers, thanks Tom Myers. 1.1.0 ----- * Report (default) timeout duration in header, thanks Holger Krekel. 1.0.0 ----- * Bump version to 1.0 to commit to semantic versioning. * Fix issue #12: Now compatible with pytest 2.8, thanks Holger Krekel. * No longer test with pexpect on py26 as it is no longer supported * Require pytest 2.8 and use new hookimpl decorator 0.5 --- * Timeouts will no longer be triggered when inside an interactive pdb session started by ``pytest.set_trace()`` / ``pdb.set_trace()``. * Add pypy3 environment to tox.ini. * Transfer repository to pytest-dev team account. 0.4 --- * Support timeouts happening in (session scoped) finalizers. * Change command line option --timeout_method into --timeout-method for consistency with pytest 0.3 --- * Added the PYTEST_TIMEOUT environment variable as a way of specifying the timeout (closes issue #2). * More flexible marker argument parsing: you can now specify the method using a positional argument. * The plugin is now enabled by default. There is no longer a need to specify ``timeout=0`` in the configuration file or on the command line simply so that a marker would work. 0.2 --- * Add a marker to modify the timeout delay using a @pytest.timeout(N) syntax, thanks to Laurant Brack for the initial code. * Allow the timeout marker to select the timeout method using the ``method`` keyword argument. * Rename the --nosigalrm option to --method=thread to future proof support for eventlet and gevent. Thanks to Ronny Pfannschmidt for the hint. * Add ``timeout`` and ``timeout_method`` items to the configuration file so you can enable and configure the plugin using the ini file. Thanks to Holger Krekel and Ronny Pfannschmidt for the hints. * Tested (and fixed) for python 2.6, 2.7 and 3.2. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696760037.0 pytest-timeout-2.2.0/pytest_timeout.egg-info/SOURCES.txt0000644000175000017500000000054714510500345021710 0ustar00flubflubLICENSE MANIFEST.in README.rst failure_demo.py pyproject.toml pytest_timeout.py setup.cfg setup.py test_pytest_timeout.py tox.ini pytest_timeout.egg-info/PKG-INFO pytest_timeout.egg-info/SOURCES.txt pytest_timeout.egg-info/dependency_links.txt pytest_timeout.egg-info/entry_points.txt pytest_timeout.egg-info/requires.txt pytest_timeout.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696760037.0 pytest-timeout-2.2.0/pytest_timeout.egg-info/dependency_links.txt0000644000175000017500000000000114510500345024064 0ustar00flubflub ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696760037.0 pytest-timeout-2.2.0/pytest_timeout.egg-info/entry_points.txt0000644000175000017500000000004414510500345023312 0ustar00flubflub[pytest11] timeout = pytest_timeout ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696760037.0 pytest-timeout-2.2.0/pytest_timeout.egg-info/requires.txt0000644000175000017500000000001614510500345022413 0ustar00flubflubpytest>=5.0.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696760037.0 pytest-timeout-2.2.0/pytest_timeout.egg-info/top_level.txt0000644000175000017500000000001714510500345022546 0ustar00flubflubpytest_timeout ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696446464.0 pytest-timeout-2.2.0/pytest_timeout.py0000644000175000017500000004226014507334000016701 0ustar00flubflub"""Timeout for tests to stop hanging testruns. This plugin will dump the stack and terminate the test. This can be useful when running tests on a continuous integration server. If the platform supports SIGALRM this is used to raise an exception in the test, otherwise os._exit(1) is used. """ import inspect import os import shutil import signal import sys import threading import traceback from collections import namedtuple import pytest __all__ = ("is_debugging", "Settings") HAVE_SIGALRM = hasattr(signal, "SIGALRM") if HAVE_SIGALRM: DEFAULT_METHOD = "signal" else: DEFAULT_METHOD = "thread" TIMEOUT_DESC = """ Timeout in seconds before dumping the stacks. Default is 0 which means no timeout. """.strip() METHOD_DESC = """ Timeout mechanism to use. 'signal' uses SIGALRM, 'thread' uses a timer thread. If unspecified 'signal' is used on platforms which support SIGALRM, otherwise 'thread' is used. """.strip() FUNC_ONLY_DESC = """ When set to True, defers the timeout evaluation to only the test function body, ignoring the time it takes when evaluating any fixtures used in the test. """.strip() DISABLE_DEBUGGER_DETECTION_DESC = """ When specified, disables debugger detection. breakpoint(), pdb.set_trace(), etc. will be interrupted by the timeout. """.strip() # bdb covers pdb, ipdb, and possibly others # pydevd covers PyCharm, VSCode, and possibly others KNOWN_DEBUGGING_MODULES = {"pydevd", "bdb", "pydevd_frame_evaluator"} Settings = namedtuple( "Settings", ["timeout", "method", "func_only", "disable_debugger_detection"] ) @pytest.hookimpl def pytest_addoption(parser): """Add options to control the timeout plugin.""" group = parser.getgroup( "timeout", "Interrupt test run and dump stacks of all threads after a test times out", ) group.addoption("--timeout", type=float, help=TIMEOUT_DESC) group.addoption( "--timeout_method", action="store", choices=["signal", "thread"], help="Deprecated, use --timeout-method", ) group.addoption( "--timeout-method", dest="timeout_method", action="store", choices=["signal", "thread"], help=METHOD_DESC, ) group.addoption( "--timeout-disable-debugger-detection", dest="timeout_disable_debugger_detection", action="store_true", help=DISABLE_DEBUGGER_DETECTION_DESC, ) parser.addini("timeout", TIMEOUT_DESC) parser.addini("timeout_method", METHOD_DESC) parser.addini("timeout_func_only", FUNC_ONLY_DESC, type="bool", default=False) parser.addini( "timeout_disable_debugger_detection", DISABLE_DEBUGGER_DETECTION_DESC, type="bool", default=False, ) class TimeoutHooks: """Timeout specific hooks.""" @pytest.hookspec(firstresult=True) def pytest_timeout_set_timer(item, settings): """Called at timeout setup. 'item' is a pytest node to setup timeout for. Can be overridden by plugins for alternative timeout implementation strategies. """ @pytest.hookspec(firstresult=True) def pytest_timeout_cancel_timer(item): """Called at timeout teardown. 'item' is a pytest node which was used for timeout setup. Can be overridden by plugins for alternative timeout implementation strategies. """ def pytest_addhooks(pluginmanager): """Register timeout-specific hooks.""" pluginmanager.add_hookspecs(TimeoutHooks) @pytest.hookimpl def pytest_configure(config): """Register the marker so it shows up in --markers output.""" config.addinivalue_line( "markers", "timeout(timeout, method=None, func_only=False, " "disable_debugger_detection=False): Set a timeout, timeout " "method and func_only evaluation on just one test item. The first " "argument, *timeout*, is the timeout in seconds while the keyword, " "*method*, takes the same values as the --timeout-method option. The " "*func_only* keyword, when set to True, defers the timeout evaluation " "to only the test function body, ignoring the time it takes when " "evaluating any fixtures used in the test. The " "*disable_debugger_detection* keyword, when set to True, disables " "debugger detection, allowing breakpoint(), pdb.set_trace(), etc. " "to be interrupted", ) settings = get_env_settings(config) config._env_timeout = settings.timeout config._env_timeout_method = settings.method config._env_timeout_func_only = settings.func_only config._env_timeout_disable_debugger_detection = settings.disable_debugger_detection @pytest.hookimpl(hookwrapper=True) def pytest_runtest_protocol(item): """Hook in timeouts to the runtest protocol. If the timeout is set on the entire test, including setup and teardown, then this hook installs the timeout. Otherwise pytest_runtest_call is used. """ hooks = item.config.pluginmanager.hook settings = _get_item_settings(item) is_timeout = settings.timeout is not None and settings.timeout > 0 if is_timeout and settings.func_only is False: hooks.pytest_timeout_set_timer(item=item, settings=settings) yield if is_timeout and settings.func_only is False: hooks.pytest_timeout_cancel_timer(item=item) @pytest.hookimpl(hookwrapper=True) def pytest_runtest_call(item): """Hook in timeouts to the test function call only. If the timeout is set on only the test function this hook installs the timeout, otherwise pytest_runtest_protocol is used. """ hooks = item.config.pluginmanager.hook settings = _get_item_settings(item) is_timeout = settings.timeout is not None and settings.timeout > 0 if is_timeout and settings.func_only is True: hooks.pytest_timeout_set_timer(item=item, settings=settings) yield if is_timeout and settings.func_only is True: hooks.pytest_timeout_cancel_timer(item=item) @pytest.hookimpl(tryfirst=True) def pytest_report_header(config): """Add timeout config to pytest header.""" if config._env_timeout: return [ "timeout: %ss\ntimeout method: %s\ntimeout func_only: %s" % ( config._env_timeout, config._env_timeout_method, config._env_timeout_func_only, ) ] @pytest.hookimpl(tryfirst=True) def pytest_exception_interact(node): """Stop the timeout when pytest enters pdb in post-mortem mode.""" hooks = node.config.pluginmanager.hook hooks.pytest_timeout_cancel_timer(item=node) @pytest.hookimpl def pytest_enter_pdb(): """Stop the timeouts when we entered pdb. This stops timeouts from triggering when pytest's builting pdb support notices we entered pdb. """ # Since pdb.set_trace happens outside of any pytest control, we don't have # any pytest ``item`` here, so we cannot use timeout_teardown. Thus, we # need another way to signify that the timeout should not be performed. global SUPPRESS_TIMEOUT SUPPRESS_TIMEOUT = True def is_debugging(trace_func=None): """Detect if a debugging session is in progress. This looks at both pytest's builtin pdb support as well as externally installed debuggers using some heuristics. This is done by checking if either of the following conditions is true: 1. Examines the trace function to see if the module it originates from is in KNOWN_DEBUGGING_MODULES. 2. Check is SUPPRESS_TIMEOUT is set to True. :param trace_func: the current trace function, if not given will use sys.gettrace(). Used to unit-test this function. """ global SUPPRESS_TIMEOUT, KNOWN_DEBUGGING_MODULES if SUPPRESS_TIMEOUT: return True if trace_func is None: trace_func = sys.gettrace() if trace_func and inspect.getmodule(trace_func): parts = inspect.getmodule(trace_func).__name__.split(".") for name in KNOWN_DEBUGGING_MODULES: if any(part.startswith(name) for part in parts): return True return False SUPPRESS_TIMEOUT = False @pytest.hookimpl(trylast=True) def pytest_timeout_set_timer(item, settings): """Setup up a timeout trigger and handler.""" timeout_method = settings.method if ( timeout_method == "signal" and threading.current_thread() is not threading.main_thread() ): timeout_method = "thread" if timeout_method == "signal": def handler(signum, frame): __tracebackhide__ = True timeout_sigalrm(item, settings) def cancel(): signal.setitimer(signal.ITIMER_REAL, 0) signal.signal(signal.SIGALRM, signal.SIG_DFL) item.cancel_timeout = cancel signal.signal(signal.SIGALRM, handler) signal.setitimer(signal.ITIMER_REAL, settings.timeout) elif timeout_method == "thread": timer = threading.Timer(settings.timeout, timeout_timer, (item, settings)) timer.name = "%s %s" % (__name__, item.nodeid) def cancel(): timer.cancel() timer.join() item.cancel_timeout = cancel timer.start() return True @pytest.hookimpl(trylast=True) def pytest_timeout_cancel_timer(item): """Cancel the timeout trigger if it was set.""" # When skipping is raised from a pytest_runtest_setup function # (as is the case when using the pytest.mark.skipif marker) we # may be called without our setup counterpart having been # called. cancel = getattr(item, "cancel_timeout", None) if cancel: cancel() return True def get_env_settings(config): """Return the configured timeout settings. This looks up the settings in the environment and config file. """ timeout = config.getvalue("timeout") if timeout is None: timeout = _validate_timeout( os.environ.get("PYTEST_TIMEOUT"), "PYTEST_TIMEOUT environment variable" ) if timeout is None: ini = config.getini("timeout") if ini: timeout = _validate_timeout(ini, "config file") method = config.getvalue("timeout_method") if method is None: ini = config.getini("timeout_method") if ini: method = _validate_method(ini, "config file") if method is None: method = DEFAULT_METHOD func_only = config.getini("timeout_func_only") disable_debugger_detection = config.getvalue("timeout_disable_debugger_detection") if disable_debugger_detection is None: ini = config.getini("timeout_disable_debugger_detection") if ini: disable_debugger_detection = _validate_disable_debugger_detection( ini, "config file" ) return Settings(timeout, method, func_only, disable_debugger_detection) def _get_item_settings(item, marker=None): """Return (timeout, method) for an item.""" timeout = method = func_only = disable_debugger_detection = None if not marker: marker = item.get_closest_marker("timeout") if marker is not None: settings = _parse_marker(item.get_closest_marker(name="timeout")) timeout = _validate_timeout(settings.timeout, "marker") method = _validate_method(settings.method, "marker") func_only = _validate_func_only(settings.func_only, "marker") disable_debugger_detection = _validate_disable_debugger_detection( settings.disable_debugger_detection, "marker" ) if timeout is None: timeout = item.config._env_timeout if method is None: method = item.config._env_timeout_method if func_only is None: func_only = item.config._env_timeout_func_only if disable_debugger_detection is None: disable_debugger_detection = item.config._env_timeout_disable_debugger_detection return Settings(timeout, method, func_only, disable_debugger_detection) def _parse_marker(marker): """Return (timeout, method) tuple from marker. Either could be None. The values are not interpreted, so could still be bogus and even the wrong type. """ if not marker.args and not marker.kwargs: raise TypeError("Timeout marker must have at least one argument") timeout = method = func_only = NOTSET = object() for kw, val in marker.kwargs.items(): if kw == "timeout": timeout = val elif kw == "method": method = val elif kw == "func_only": func_only = val else: raise TypeError("Invalid keyword argument for timeout marker: %s" % kw) if len(marker.args) >= 1 and timeout is not NOTSET: raise TypeError("Multiple values for timeout argument of timeout marker") elif len(marker.args) >= 1: timeout = marker.args[0] if len(marker.args) >= 2 and method is not NOTSET: raise TypeError("Multiple values for method argument of timeout marker") elif len(marker.args) >= 2: method = marker.args[1] if len(marker.args) > 2: raise TypeError("Too many arguments for timeout marker") if timeout is NOTSET: timeout = None if method is NOTSET: method = None if func_only is NOTSET: func_only = None return Settings(timeout, method, func_only, None) def _validate_timeout(timeout, where): if timeout is None: return None try: return float(timeout) except ValueError: raise ValueError("Invalid timeout %s from %s" % (timeout, where)) def _validate_method(method, where): if method is None: return None if method not in ["signal", "thread"]: raise ValueError("Invalid method %s from %s" % (method, where)) return method def _validate_func_only(func_only, where): if func_only is None: return None if not isinstance(func_only, bool): raise ValueError("Invalid func_only value %s from %s" % (func_only, where)) return func_only def _validate_disable_debugger_detection(disable_debugger_detection, where): if disable_debugger_detection is None: return None if not isinstance(disable_debugger_detection, bool): raise ValueError( "Invalid disable_debugger_detection value %s from %s" % (disable_debugger_detection, where) ) return disable_debugger_detection def timeout_sigalrm(item, settings): """Dump stack of threads and raise an exception. This will output the stacks of any threads other then the current to stderr and then raise an AssertionError, thus terminating the test. """ if not settings.disable_debugger_detection and is_debugging(): return __tracebackhide__ = True nthreads = len(threading.enumerate()) if nthreads > 1: write_title("Timeout", sep="+") dump_stacks() if nthreads > 1: write_title("Timeout", sep="+") pytest.fail("Timeout >%ss" % settings.timeout) def timeout_timer(item, settings): """Dump stack of threads and call os._exit(). This disables the capturemanager and dumps stdout and stderr. Then the stacks are dumped and os._exit(1) is called. """ if not settings.disable_debugger_detection and is_debugging(): return try: capman = item.config.pluginmanager.getplugin("capturemanager") if capman: capman.suspend_global_capture(item) stdout, stderr = capman.read_global_capture() else: stdout, stderr = None, None write_title("Timeout", sep="+") caplog = item.config.pluginmanager.getplugin("_capturelog") if caplog and hasattr(item, "capturelog_handler"): log = item.capturelog_handler.stream.getvalue() if log: write_title("Captured log") write(log) if stdout: write_title("Captured stdout") write(stdout) if stderr: write_title("Captured stderr") write(stderr) dump_stacks() write_title("Timeout", sep="+") except Exception: traceback.print_exc() finally: sys.stdout.flush() sys.stderr.flush() os._exit(1) def dump_stacks(): """Dump the stacks of all threads except the current thread.""" current_ident = threading.current_thread().ident for thread_ident, frame in sys._current_frames().items(): if thread_ident == current_ident: continue for t in threading.enumerate(): if t.ident == thread_ident: thread_name = t.name break else: thread_name = "" write_title("Stack of %s (%s)" % (thread_name, thread_ident)) write("".join(traceback.format_stack(frame))) def write_title(title, stream=None, sep="~"): """Write a section title. If *stream* is None sys.stderr will be used, *sep* is used to draw the line. """ if stream is None: stream = sys.stderr width, height = shutil.get_terminal_size() fill = int((width - len(title) - 2) / 2) line = " ".join([sep * fill, title, sep * fill]) if len(line) < width: line += sep * (width - len(line)) stream.write("\n" + line + "\n") def write(text, stream=None): """Write text to stream. Pretty stupid really, only here for symmetry with .write_title(). """ if stream is None: stream = sys.stderr stream.write(text) ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1696760037.96903 pytest-timeout-2.2.0/setup.cfg0000644000175000017500000000225114510500346015050 0ustar00flubflub[metadata] name = pytest-timeout description = pytest plugin to abort hanging tests long_description = file: README.rst version = 2.2.0 author = Floris Bruynooghe author_email = flub@devork.be url = https://github.com/pytest-dev/pytest-timeout license = MIT classifiers = Development Status :: 5 - Production/Stable Environment :: Console Environment :: Plugins Intended Audience :: Developers License :: DFSG approved License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Topic :: Software Development :: Testing Framework :: Pytest [options] py_modules = pytest_timeout install_requires = pytest>=5.0.0 python_requires = >=3.7 [options.entry_points] pytest11 = timeout = pytest_timeout [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696759578.0 pytest-timeout-2.2.0/setup.py0000644000175000017500000000017114510477432014751 0ustar00flubflub"""Setuptools install script for pytest-timeout.""" from setuptools import setup if __name__ == "__main__": setup() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696446464.0 pytest-timeout-2.2.0/test_pytest_timeout.py0000644000175000017500000003412714507334000017743 0ustar00flubflubimport os.path import signal import sys import time import pexpect import pytest pytest_plugins = "pytester" have_sigalrm = pytest.mark.skipif( not hasattr(signal, "SIGALRM"), reason="OS does not have SIGALRM" ) have_spawn = pytest.mark.skipif( not hasattr(pexpect, "spawn"), reason="pexpect does not have spawn" ) @pytest.fixture def testdir(testdir): if hasattr(testdir, "runpytest_subprocess"): # on pytest-2.8 runpytest runs inline by default # patch the testdir instance to use the subprocess method testdir.runpytest = testdir.runpytest_subprocess return testdir def test_header(testdir): testdir.makepyfile( """ def test_x(): pass """ ) result = testdir.runpytest("--timeout=1") result.stdout.fnmatch_lines( ["timeout: 1.0s", "timeout method:*", "timeout func_only:*"] ) @have_sigalrm def test_sigalrm(testdir): testdir.makepyfile( """ import time def test_foo(): time.sleep(2) """ ) result = testdir.runpytest("--timeout=1") result.stdout.fnmatch_lines(["*Failed: Timeout >1.0s*"]) def test_thread(testdir): testdir.makepyfile( """ import time def test_foo(): time.sleep(2) """ ) result = testdir.runpytest("--timeout=1", "--timeout-method=thread") result.stderr.fnmatch_lines( [ "*++ Timeout ++*", "*~~ Stack of MainThread* ~~*", "*File *, line *, in *", "*++ Timeout ++*", ] ) assert "++ Timeout ++" in result.stderr.lines[-1] @pytest.mark.skipif( hasattr(sys, "pypy_version_info"), reason="pypy coverage seems broken currently" ) def test_cov(testdir): # This test requires pytest-cov pytest.importorskip("pytest_cov") testdir.makepyfile( """ import time def test_foo(): time.sleep(2) """ ) result = testdir.runpytest( "--timeout=1", "--cov=test_cov", "--timeout-method=thread" ) result.stderr.fnmatch_lines( [ "*++ Timeout ++*", "*~~ Stack of MainThread* ~~*", "*File *, line *, in *", "*++ Timeout ++*", ] ) assert "++ Timeout ++" in result.stderr.lines[-1] def test_timeout_env(testdir, monkeypatch): testdir.makepyfile( """ import time def test_foo(): time.sleep(2) """ ) monkeypatch.setitem(os.environ, "PYTEST_TIMEOUT", "1") result = testdir.runpytest() assert result.ret > 0 # @pytest.mark.parametrize('meth', [have_sigalrm('signal'), 'thread']) # def test_func_fix(meth, testdir): # testdir.makepyfile(""" # import time, pytest # @pytest.fixture(scope='function') # def fix(): # time.sleep(2) # def test_foo(fix): # pass # """) # result = testdir.runpytest('--timeout=1', # '--timeout-method={0}'.format(meth)) # assert result.ret > 0 # assert 'Timeout' in result.stdout.str() + result.stderr.str() @pytest.mark.parametrize("meth", [pytest.param("signal", marks=have_sigalrm), "thread"]) @pytest.mark.parametrize("scope", ["function", "class", "module", "session"]) def test_fix_setup(meth, scope, testdir): testdir.makepyfile( """ import time, pytest class TestFoo: @pytest.fixture(scope='{scope}') def fix(self): time.sleep(2) def test_foo(self, fix): pass """.format( scope=scope ) ) result = testdir.runpytest("--timeout=1", f"--timeout-method={meth}") assert result.ret > 0 assert "Timeout" in result.stdout.str() + result.stderr.str() def test_fix_setup_func_only(testdir): testdir.makepyfile( """ import time, pytest class TestFoo: @pytest.fixture def fix(self): time.sleep(0.1) @pytest.mark.timeout(func_only=True) def test_foo(self, fix): pass """ ) result = testdir.runpytest("--timeout=1") assert result.ret == 0 assert "Timeout" not in result.stdout.str() + result.stderr.str() @pytest.mark.parametrize("meth", [pytest.param("signal", marks=have_sigalrm), "thread"]) @pytest.mark.parametrize("scope", ["function", "class", "module", "session"]) def test_fix_finalizer(meth, scope, testdir): testdir.makepyfile( """ import time, pytest class TestFoo: @pytest.fixture def fix(self, request): print('fix setup') def fin(): print('fix finaliser') time.sleep(2) request.addfinalizer(fin) def test_foo(self, fix): pass """ ) result = testdir.runpytest("--timeout=1", "-s", f"--timeout-method={meth}") assert result.ret > 0 assert "Timeout" in result.stdout.str() + result.stderr.str() def test_fix_finalizer_func_only(testdir): testdir.makepyfile( """ import time, pytest class TestFoo: @pytest.fixture def fix(self, request): print('fix setup') def fin(): print('fix finaliser') time.sleep(0.1) request.addfinalizer(fin) @pytest.mark.timeout(func_only=True) def test_foo(self, fix): pass """ ) result = testdir.runpytest("--timeout=1", "-s") assert result.ret == 0 assert "Timeout" not in result.stdout.str() + result.stderr.str() @have_sigalrm def test_timeout_mark_sigalrm(testdir): testdir.makepyfile( """ import time, pytest @pytest.mark.timeout(1) def test_foo(): time.sleep(2) assert False """ ) result = testdir.runpytest() result.stdout.fnmatch_lines(["*Failed: Timeout >1.0s*"]) def test_timeout_mark_timer(testdir): testdir.makepyfile( """ import time, pytest @pytest.mark.timeout(1) def test_foo(): time.sleep(2) """ ) result = testdir.runpytest("--timeout-method=thread") result.stderr.fnmatch_lines(["*++ Timeout ++*"]) def test_timeout_mark_non_int(testdir): testdir.makepyfile( """ import time, pytest @pytest.mark.timeout(0.01) def test_foo(): time.sleep(1) """ ) result = testdir.runpytest("--timeout-method=thread") result.stderr.fnmatch_lines(["*++ Timeout ++*"]) def test_timeout_mark_non_number(testdir): testdir.makepyfile( """ import pytest @pytest.mark.timeout('foo') def test_foo(): pass """ ) result = testdir.runpytest() result.stdout.fnmatch_lines(["*ValueError*"]) def test_timeout_mark_args(testdir): testdir.makepyfile( """ import pytest @pytest.mark.timeout(1, 2) def test_foo(): pass """ ) result = testdir.runpytest() result.stdout.fnmatch_lines(["*ValueError*"]) def test_timeout_mark_method_nokw(testdir): testdir.makepyfile( """ import time, pytest @pytest.mark.timeout(1, 'thread') def test_foo(): time.sleep(2) """ ) result = testdir.runpytest() result.stderr.fnmatch_lines(["*+ Timeout +*"]) def test_timeout_mark_noargs(testdir): testdir.makepyfile( """ import pytest @pytest.mark.timeout def test_foo(): pass """ ) result = testdir.runpytest() result.stdout.fnmatch_lines(["*TypeError*"]) def test_ini_timeout(testdir): testdir.makepyfile( """ import time def test_foo(): time.sleep(2) """ ) testdir.makeini( """ [pytest] timeout = 1 """ ) result = testdir.runpytest() assert result.ret def test_ini_timeout_func_only(testdir): testdir.makepyfile( """ import time, pytest @pytest.fixture def slow(): time.sleep(2) def test_foo(slow): pass """ ) testdir.makeini( """ [pytest] timeout = 1 timeout_func_only = true """ ) result = testdir.runpytest() assert result.ret == 0 def test_ini_timeout_func_only_marker_override(testdir): testdir.makepyfile( """ import time, pytest @pytest.fixture def slow(): time.sleep(2) @pytest.mark.timeout(1.5) def test_foo(slow): pass """ ) testdir.makeini( """ [pytest] timeout = 1 timeout_func_only = true """ ) result = testdir.runpytest() assert result.ret == 0 def test_ini_method(testdir): testdir.makepyfile( """ import time def test_foo(): time.sleep(2) """ ) testdir.makeini( """ [pytest] timeout = 1 timeout_method = thread """ ) result = testdir.runpytest() assert "=== 1 failed in " not in result.outlines[-1] def test_timeout_marker_inheritance(testdir): testdir.makepyfile( """ import time, pytest @pytest.mark.timeout(timeout=2) class TestFoo: @pytest.mark.timeout(timeout=3) def test_foo_2(self): time.sleep(2) def test_foo_1(self): time.sleep(1) """ ) result = testdir.runpytest("--timeout=1", "-s") assert result.ret == 0 assert "Timeout" not in result.stdout.str() + result.stderr.str() def test_marker_help(testdir): result = testdir.runpytest("--markers") result.stdout.fnmatch_lines(["@pytest.mark.timeout(*"]) @pytest.mark.parametrize( ["debugging_module", "debugging_set_trace"], [ ("pdb", "set_trace()"), pytest.param( "ipdb", "set_trace()", marks=pytest.mark.xfail( reason="waiting on https://github.com/pytest-dev/pytest/pull/7207" " to allow proper testing" ), ), pytest.param( "pydevd", "settrace(port=4678)", marks=pytest.mark.xfail(reason="in need of way to setup pydevd server"), ), ], ) @have_spawn def test_suppresses_timeout_when_debugger_is_entered( testdir, debugging_module, debugging_set_trace ): p1 = testdir.makepyfile( """ import pytest, {debugging_module} @pytest.mark.timeout(1) def test_foo(): {debugging_module}.{debugging_set_trace} """.format( debugging_module=debugging_module, debugging_set_trace=debugging_set_trace ) ) child = testdir.spawn_pytest(str(p1)) child.expect("test_foo") time.sleep(0.2) child.send("c\n") child.sendeof() result = child.read().decode().lower() if child.isalive(): child.terminate(force=True) assert "timeout >1.0s" not in result assert "fail" not in result @pytest.mark.parametrize( ["debugging_module", "debugging_set_trace"], [ ("pdb", "set_trace()"), pytest.param( "ipdb", "set_trace()", marks=pytest.mark.xfail( reason="waiting on https://github.com/pytest-dev/pytest/pull/7207" " to allow proper testing" ), ), pytest.param( "pydevd", "settrace(port=4678)", marks=pytest.mark.xfail(reason="in need of way to setup pydevd server"), ), ], ) @have_spawn def test_disable_debugger_detection_flag( testdir, debugging_module, debugging_set_trace ): p1 = testdir.makepyfile( """ import pytest, {debugging_module} @pytest.mark.timeout(1) def test_foo(): {debugging_module}.{debugging_set_trace} """.format( debugging_module=debugging_module, debugging_set_trace=debugging_set_trace ) ) child = testdir.spawn_pytest(f"{p1} --timeout-disable-debugger-detection") child.expect("test_foo") time.sleep(1.2) result = child.read().decode().lower() if child.isalive(): child.terminate(force=True) assert "timeout >1.0s" in result assert "fail" in result def test_is_debugging(monkeypatch): import pytest_timeout assert not pytest_timeout.is_debugging() # create a fake module named "custom.pydevd" with a trace function on it from types import ModuleType module_name = "custom.pydevd" module = ModuleType(module_name) monkeypatch.setitem(sys.modules, module_name, module) def custom_trace(*args): pass custom_trace.__module__ = module_name module.custom_trace = custom_trace assert pytest_timeout.is_debugging(custom_trace) def test_not_main_thread(testdir): testdir.makepyfile( """ import threading import pytest_timeout current_timeout_setup = pytest_timeout.timeout_setup def new_timeout_setup(item): threading.Thread( target=current_timeout_setup, args=(item), ).join() pytest_timeout.timeout_setup = new_timeout_setup def test_x(): pass """ ) result = testdir.runpytest("--timeout=1") result.stdout.fnmatch_lines( ["timeout: 1.0s", "timeout method:*", "timeout func_only:*"] ) def test_plugin_interface(testdir): testdir.makeconftest( """ import pytest @pytest.mark.tryfirst def pytest_timeout_set_timer(item, settings): print() print("pytest_timeout_set_timer") return True @pytest.mark.tryfirst def pytest_timeout_cancel_timer(item): print() print("pytest_timeout_cancel_timer") return True """ ) testdir.makepyfile( """ import pytest @pytest.mark.timeout(1) def test_foo(): pass """ ) result = testdir.runpytest("-s") result.stdout.fnmatch_lines( [ "pytest_timeout_set_timer", "pytest_timeout_cancel_timer", ] ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1696446464.0 pytest-timeout-2.2.0/tox.ini0000644000175000017500000000100414507334000014533 0ustar00flubflub[pytest] minversion = 2.8 addopts = -ra [tox] envlist = py37,py38,py39,py310,py311,pypy3 [testenv] deps = pytest pexpect ipdb pytest-cov pytest-github-actions-annotate-failures commands = pytest {posargs} [testenv:linting] skip_install = True basepython = python3 deps = pre-commit>=1.11.0 commands = pre-commit run --all-files --show-diff-on-failure [flake8] disable-noqa = True max-line-length = 88 extend-ignore = E203 # whitespace before : is not PEP8 compliant (& conflicts with black)