diff options
| author | pefavel <[email protected]> | 2025-09-23 15:06:19 +0300 |
|---|---|---|
| committer | pefavel <[email protected]> | 2025-09-23 15:24:02 +0300 |
| commit | 13183af1efa6b08ffa7a942d1282ee35f66094f2 (patch) | |
| tree | 5d84cb0849019ba34df77c0d55c5180755d41918 /contrib/python/pytest-timeout/py3 | |
| parent | 555a474f62a7f1948772f5375e7f70bb511937aa (diff) | |
Add test timeout to nemesis
feat: add per test timeout for nemesis
commit_hash:0808539c32e3ddf9c1fa99b669fd5d0efc4d767d
Diffstat (limited to 'contrib/python/pytest-timeout/py3')
| -rw-r--r-- | contrib/python/pytest-timeout/py3/.dist-info/METADATA | 623 | ||||
| -rw-r--r-- | contrib/python/pytest-timeout/py3/.dist-info/entry_points.txt | 2 | ||||
| -rw-r--r-- | contrib/python/pytest-timeout/py3/.dist-info/top_level.txt | 1 | ||||
| -rw-r--r-- | contrib/python/pytest-timeout/py3/LICENSE | 22 | ||||
| -rw-r--r-- | contrib/python/pytest-timeout/py3/README.rst | 588 | ||||
| -rw-r--r-- | contrib/python/pytest-timeout/py3/pytest_timeout.py | 558 | ||||
| -rw-r--r-- | contrib/python/pytest-timeout/py3/ya.make | 27 |
7 files changed, 1821 insertions, 0 deletions
diff --git a/contrib/python/pytest-timeout/py3/.dist-info/METADATA b/contrib/python/pytest-timeout/py3/.dist-info/METADATA new file mode 100644 index 00000000000..90ae6ab7865 --- /dev/null +++ b/contrib/python/pytest-timeout/py3/.dist-info/METADATA @@ -0,0 +1,623 @@ +Metadata-Version: 2.4 +Name: pytest-timeout +Version: 2.4.0 +Summary: pytest plugin to abort hanging tests +Home-page: https://github.com/pytest-dev/pytest-timeout +Author: Floris Bruynooghe +Author-email: [email protected] +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: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Software Development :: Testing +Classifier: Framework :: Pytest +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: pytest>=7.0.0 +Dynamic: license-file + +============== +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 ``--forked`` option of the pytest-forked_ +plugin. + +.. _pytest-forked: https://pypi.org/project/pytest-forked/ + +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``. + +For the decorated function, a decorator will override +``timeout_func_only = true`` in the pytest ini file to the default +value. If you need to keep this option for a decorated test, you +must specify the option explicitly again: + +.. code:: python + + @pytest.mark.timeout(60, func_only=True) + def test_foo(): + pass + + +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 +<https://docs.pytest.org/en/latest/how-to/writing_hook_functions.html>`_ 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 +++") + + + +Session Timeout +=============== + +The above mentioned timeouts are all per test function. +The "per test function" timeouts will stop an individual test +from taking too long. We may also want to limit the time of the entire +set of tests running in one session. A session all of the tests +that will be run with one invokation of pytest. + +A session timeout is set with `--session-timeout` and is in seconds. + +The following example shows a session timeout of 10 minutes (600 seconds):: + + pytest --session-timeout=600 + +You can also set the session timeout the pytest configuration file using the ``session_timeout`` option: + + .. code:: ini + + [pytest] + session_timeout = 600 + +Cooperative timeouts +-------------------- + +Session timeouts are cooperative timeouts. pytest-timeout checks the +session time at the end of each test function, and stops further tests +from running if the session timeout is exceeded. The session will +results in a test failure if this occurs. + +In particular this means if a test does not finish of itself, it will +only be interrupted if there is also a function timeout set. A +session timeout is not enough to ensure that a test-suite is +guaranteed to finish. + +Combining session and function timeouts +--------------------------------------- + +It works fine to combine both session and function timeouts. In fact +when using a session timeout it is recommended to also provide a +function timeout. + +For example, to limit test functions to 5 seconds and the full session +to 100 seconds:: + + pytest --timeout=5 --session-timeout=100 + + +Changelog +========= + +2.4.0 +----- + +- Detect debuggers registered with sys.monitoring. Thanks Rich + Chiodo. +- Make it clear the timeout message comes from pytest-timeout. Thanks + Pedro Brochado. + +2.3.1 +----- + +- Fixup some build errors, mostly README syntax which stopped twine + from uploading. + +2.3.0 +----- + +- Fix debugger detection for recent VSCode, this compiles pydevd using + cython which is now correctly detected. Thanks Adrian Gielniewski. +- Switched to using Pytest's ``TerminalReporter`` instead of writing + directly to ``sys.{stdout,stderr}``. + This change also switches all output from ``sys.stderr`` to ``sys.stdout``. + Thanks Pedro Algarvio. +- Pytest 7.0.0 is now the minimum supported version. Thanks Pedro Algarvio. +- Add ``--session-timeout`` option and ``session_timeout`` setting. + Thanks Brian Okken. + +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. diff --git a/contrib/python/pytest-timeout/py3/.dist-info/entry_points.txt b/contrib/python/pytest-timeout/py3/.dist-info/entry_points.txt new file mode 100644 index 00000000000..f7ac24a8d49 --- /dev/null +++ b/contrib/python/pytest-timeout/py3/.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +timeout = pytest_timeout diff --git a/contrib/python/pytest-timeout/py3/.dist-info/top_level.txt b/contrib/python/pytest-timeout/py3/.dist-info/top_level.txt new file mode 100644 index 00000000000..34eec7a2ee6 --- /dev/null +++ b/contrib/python/pytest-timeout/py3/.dist-info/top_level.txt @@ -0,0 +1 @@ +pytest_timeout diff --git a/contrib/python/pytest-timeout/py3/LICENSE b/contrib/python/pytest-timeout/py3/LICENSE new file mode 100644 index 00000000000..e968817fc44 --- /dev/null +++ b/contrib/python/pytest-timeout/py3/LICENSE @@ -0,0 +1,22 @@ +The 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. diff --git a/contrib/python/pytest-timeout/py3/README.rst b/contrib/python/pytest-timeout/py3/README.rst new file mode 100644 index 00000000000..a426d6f0799 --- /dev/null +++ b/contrib/python/pytest-timeout/py3/README.rst @@ -0,0 +1,588 @@ +============== +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 ``--forked`` option of the pytest-forked_ +plugin. + +.. _pytest-forked: https://pypi.org/project/pytest-forked/ + +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``. + +For the decorated function, a decorator will override +``timeout_func_only = true`` in the pytest ini file to the default +value. If you need to keep this option for a decorated test, you +must specify the option explicitly again: + +.. code:: python + + @pytest.mark.timeout(60, func_only=True) + def test_foo(): + pass + + +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 +<https://docs.pytest.org/en/latest/how-to/writing_hook_functions.html>`_ 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 +++") + + + +Session Timeout +=============== + +The above mentioned timeouts are all per test function. +The "per test function" timeouts will stop an individual test +from taking too long. We may also want to limit the time of the entire +set of tests running in one session. A session all of the tests +that will be run with one invokation of pytest. + +A session timeout is set with `--session-timeout` and is in seconds. + +The following example shows a session timeout of 10 minutes (600 seconds):: + + pytest --session-timeout=600 + +You can also set the session timeout the pytest configuration file using the ``session_timeout`` option: + + .. code:: ini + + [pytest] + session_timeout = 600 + +Cooperative timeouts +-------------------- + +Session timeouts are cooperative timeouts. pytest-timeout checks the +session time at the end of each test function, and stops further tests +from running if the session timeout is exceeded. The session will +results in a test failure if this occurs. + +In particular this means if a test does not finish of itself, it will +only be interrupted if there is also a function timeout set. A +session timeout is not enough to ensure that a test-suite is +guaranteed to finish. + +Combining session and function timeouts +--------------------------------------- + +It works fine to combine both session and function timeouts. In fact +when using a session timeout it is recommended to also provide a +function timeout. + +For example, to limit test functions to 5 seconds and the full session +to 100 seconds:: + + pytest --timeout=5 --session-timeout=100 + + +Changelog +========= + +2.4.0 +----- + +- Detect debuggers registered with sys.monitoring. Thanks Rich + Chiodo. +- Make it clear the timeout message comes from pytest-timeout. Thanks + Pedro Brochado. + +2.3.1 +----- + +- Fixup some build errors, mostly README syntax which stopped twine + from uploading. + +2.3.0 +----- + +- Fix debugger detection for recent VSCode, this compiles pydevd using + cython which is now correctly detected. Thanks Adrian Gielniewski. +- Switched to using Pytest's ``TerminalReporter`` instead of writing + directly to ``sys.{stdout,stderr}``. + This change also switches all output from ``sys.stderr`` to ``sys.stdout``. + Thanks Pedro Algarvio. +- Pytest 7.0.0 is now the minimum supported version. Thanks Pedro Algarvio. +- Add ``--session-timeout`` option and ``session_timeout`` setting. + Thanks Brian Okken. + +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. diff --git a/contrib/python/pytest-timeout/py3/pytest_timeout.py b/contrib/python/pytest-timeout/py3/pytest_timeout.py new file mode 100644 index 00000000000..7c220699e23 --- /dev/null +++ b/contrib/python/pytest-timeout/py3/pytest_timeout.py @@ -0,0 +1,558 @@ +"""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 signal +import sys +import threading +import time +import traceback +from collections import namedtuple + +import pytest + + +__all__ = ("is_debugging", "Settings") +SESSION_TIMEOUT_KEY = pytest.StashKey[float]() +SESSION_EXPIRE_KEY = pytest.StashKey[float]() +PYTEST_FAILURE_MESSAGE = "Timeout (>%ss) from pytest-timeout." + +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() +SESSION_TIMEOUT_DESC = """ +Timeout in seconds for entire session. Default is None which +means no timeout. Timeout is checked between tests, and will not interrupt a test +in progress. +""".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"] +) + + +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, + ) + group.addoption( + "--session-timeout", + action="store", + dest="session_timeout", + default=None, + type=float, + metavar="SECONDS", + help=SESSION_TIMEOUT_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, + ) + parser.addini("session_timeout", SESSION_TIMEOUT_DESC) + + +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) + + +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 + + timeout = config.getoption("session_timeout") + if timeout is None: + ini = config.getini("session_timeout") + if ini: + timeout = _validate_timeout(config.getini("session_timeout"), "config file") + if timeout is not None: + expire_time = time.time() + timeout + else: + expire_time = 0 + timeout = 0 + config.stash[SESSION_TIMEOUT_KEY] = timeout + config.stash[SESSION_EXPIRE_KEY] = expire_time + + [email protected](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) + + # check session timeout + expire_time = item.session.config.stash[SESSION_EXPIRE_KEY] + if expire_time and (expire_time < time.time()): + timeout = item.session.config.stash[SESSION_TIMEOUT_KEY] + item.session.shouldfail = f"session-timeout: {timeout} sec exceeded" + + [email protected](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) + + [email protected](tryfirst=True) +def pytest_report_header(config): + """Add timeout config to pytest header.""" + timeout_header = [] + + if config._env_timeout: + timeout_header.append( + "timeout: %ss\ntimeout method: %s\ntimeout func_only: %s" + % ( + config._env_timeout, + config._env_timeout_method, + config._env_timeout_func_only, + ) + ) + + session_timeout = config.getoption("session_timeout") + if session_timeout: + timeout_header.append("session timeout: %ss" % session_timeout) + if timeout_header: + return timeout_header + + [email protected](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) + + +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() + trace_module = None + if trace_func: + trace_module = inspect.getmodule(trace_func) or inspect.getmodule( + trace_func.__class__ + ) + if trace_module: + parts = trace_module.__name__.split(".") + for name in KNOWN_DEBUGGING_MODULES: + if any(part.startswith(name) for part in parts): + return True + + # For 3.12, sys.monitoring is used for tracing. Check if any debugger has been registered. + if hasattr(sys, "monitoring"): + return sys.monitoring.get_tool(sys.monitoring.DEBUGGER_ID) != None + return False + + +SUPPRESS_TIMEOUT = False + + [email protected](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 + + [email protected](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()) + terminal = item.config.get_terminal_writer() + if nthreads > 1: + terminal.sep("+", title="Timeout") + dump_stacks(terminal) + if nthreads > 1: + terminal.sep("+", title="Timeout") + pytest.fail(PYTEST_FAILURE_MESSAGE % 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 + terminal = item.config.get_terminal_writer() + 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 + terminal.sep("+", title="Timeout") + caplog = item.config.pluginmanager.getplugin("_capturelog") + if caplog and hasattr(item, "capturelog_handler"): + log = item.capturelog_handler.stream.getvalue() + if log: + terminal.sep("~", title="Captured log") + terminal.write(log) + if stdout: + terminal.sep("~", title="Captured stdout") + terminal.write(stdout) + if stderr: + terminal.sep("~", title="Captured stderr") + terminal.write(stderr) + dump_stacks(terminal) + terminal.sep("+", title="Timeout") + except Exception: + traceback.print_exc() + finally: + terminal.flush() + sys.stdout.flush() + sys.stderr.flush() + os._exit(1) + + +def dump_stacks(terminal): + """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 = "<unknown>" + terminal.sep("~", title="Stack of %s (%s)" % (thread_name, thread_ident)) + terminal.write("".join(traceback.format_stack(frame))) diff --git a/contrib/python/pytest-timeout/py3/ya.make b/contrib/python/pytest-timeout/py3/ya.make new file mode 100644 index 00000000000..5f98848548d --- /dev/null +++ b/contrib/python/pytest-timeout/py3/ya.make @@ -0,0 +1,27 @@ +# Generated by devtools/yamaker (pypi). + +PY3_LIBRARY() + +VERSION(2.4.0) + +LICENSE(MIT) + +PEERDIR( + contrib/python/pytest +) + +NO_LINT() + +PY_SRCS( + TOP_LEVEL + pytest_timeout.py +) + +RESOURCE_FILES( + PREFIX contrib/python/pytest-timeout/py3/ + .dist-info/METADATA + .dist-info/entry_points.txt + .dist-info/top_level.txt +) + +END() |
