From bc179a882ab371731daefeaceb997f0e6dc5f164 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 19 Jan 2022 06:44:42 -0300 Subject: [PATCH 01/24] Fix typo in README --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 70fbd79..9bad12f 100644 --- a/README.rst +++ b/README.rst @@ -237,8 +237,8 @@ debugging frameworks modules OR if pytest itself drops you into a pdb session using ``--pdb`` or similar. -Extending pytest-timeout with plugings -====================================== +Extending pytest-timeout with plugins +===================================== ``pytest-timeout`` provides two hooks that can be used for extending the tool. These hooks are used for for setting the timeout timer and cancelling it it the timeout is not From 60f8115b7ccf02c519289d73b30c9fb1681d93af Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 20 Jan 2022 17:49:29 -0300 Subject: [PATCH 02/24] Fix linting (#118) --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 9bad12f..260d27b 100644 --- a/README.rst +++ b/README.rst @@ -313,6 +313,7 @@ function: import pytest import pytest_timeout + def on_timeout(): if pytest_timeout.is_debugging(): return From cc669a4206b95ffeb33cb26cac0d5a6a5bcdaacb Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 25 Jan 2022 19:12:29 -0300 Subject: [PATCH 03/24] Remove linting CI job, use pre-commit.ci instead (#119) * Remove linting CI job, use pre-commit.ci instead * Add pre-commit badge to README --- .github/workflows/main.yml | 2 -- README.rst | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 44600a2..da52e69 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,8 +21,6 @@ jobs: tox_env: "py39" - v: "3.10" tox_env: "py310" - - v: "3.9" - tox_env: "linting" os: [ubuntu-latest, windows-latest] steps: - name: Set Git to use LF diff --git a/README.rst b/README.rst index 260d27b..ccf4e6a 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ pytest-timeout ============== -|python| |version| |anaconda| |ci| +|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 @@ -16,6 +16,9 @@ pytest-timeout .. |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 + **This is not the timeout you are looking for!** .. warning:: From 761a7fc9742e4222dca18ceb8b2ad8dd858828d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:15:56 +0100 Subject: [PATCH 04/24] [pre-commit.ci] pre-commit autoupdate (#120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 20.8b1 → 22.1.0](https://github.com/psf/black/compare/20.8b1...22.1.0) - [github.com/asottile/blacken-docs: v1.10.0 → v1.12.1](https://github.com/asottile/blacken-docs/compare/v1.10.0...v1.12.1) - [github.com/pre-commit/pre-commit-hooks: v3.4.0 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v3.4.0...v4.1.0) - https://gitlab.com/pycqa/flake8 → https://github.com/PyCQA/flake8 - [github.com/PyCQA/flake8: 3.8.4 → 4.0.1](https://github.com/PyCQA/flake8/compare/3.8.4...4.0.1) - https://gitlab.com/pycqa/flake8 → https://github.com/PyCQA/flake8 - [github.com/PyCQA/flake8: 3.8.4 → 4.0.1](https://github.com/PyCQA/flake8/compare/3.8.4...4.0.1) - [github.com/asottile/reorder_python_imports: v2.4.0 → v2.7.1](https://github.com/asottile/reorder_python_imports/compare/v2.4.0...v2.7.1) - [github.com/asottile/pyupgrade: v2.10.0 → v2.31.0](https://github.com/asottile/pyupgrade/compare/v2.10.0...v2.31.0) - [github.com/pre-commit/pygrep-hooks: v1.8.0 → v1.9.0](https://github.com/pre-commit/pygrep-hooks/compare/v1.8.0...v1.9.0) - [github.com/adrienverge/yamllint.git: v1.26.0 → v1.26.3](https://github.com/adrienverge/yamllint.git/compare/v1.26.0...v1.26.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cd429eb..654dd60 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,17 +1,17 @@ --- repos: - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.1.0 hooks: - id: black args: [--safe, --quiet, --target-version, py36] - repo: https://github.com/asottile/blacken-docs - rev: v1.10.0 + rev: v1.12.1 hooks: - id: blacken-docs additional_dependencies: [black==20.8b1] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.1.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -20,8 +20,8 @@ repos: - id: check-yaml - id: debug-statements language_version: python3 - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 hooks: - id: flake8 language_version: python3 @@ -30,25 +30,25 @@ repos: rev: v0.3.3 hooks: - id: pep257 - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 hooks: - id: flake8 language_version: python3 - repo: https://github.com/asottile/reorder_python_imports - rev: v2.4.0 + rev: v2.7.1 hooks: - id: reorder-python-imports - repo: https://github.com/asottile/pyupgrade - rev: v2.10.0 + rev: v2.31.0 hooks: - id: pyupgrade args: [--keep-percent-format, --py36-plus] - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.8.0 + rev: v1.9.0 hooks: - id: rst-backticks - repo: https://github.com/adrienverge/yamllint.git - rev: v1.26.0 + rev: v1.26.3 hooks: - id: yamllint From 7985dadecbcfe8412aa84cf2e197bbc8d18265a2 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Thu, 10 Feb 2022 04:26:31 +0800 Subject: [PATCH 05/24] Fix typos (#121) --- README.rst | 4 ++-- pytest_timeout.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index ccf4e6a..b7f44b6 100644 --- a/README.rst +++ b/README.rst @@ -352,7 +352,7 @@ Changelog thread to avoid crash. - Fix pycharm debugger detection so timeouts are not triggered during debugger usage. -- Dropped support for Python 2, minimum pytest version upported is 5.0.0. +- Dropped support for Python 2, minimum pytest version supported is 5.0.0. 1.4.2 ----- @@ -387,7 +387,7 @@ Changelog 1.3.2 ----- -- This changelog was ommitted for the 1.3.2 release and was added +- 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 diff --git a/pytest_timeout.py b/pytest_timeout.py index da02c0d..2e560bb 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -482,7 +482,7 @@ def write_title(title, stream=None, sep="~"): def write(text, stream=None): """Write text to stream. - Pretty stupid really, only here for symetry with .write_title(). + Pretty stupid really, only here for symmetry with .write_title(). """ if stream is None: stream = sys.stderr From d68013ad89027aa31d8f35cdb2414519d2db82c1 Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Fri, 4 Mar 2022 10:00:18 -0800 Subject: [PATCH 06/24] Update _validate_func_only to return None so it can utilize the environment default --- pytest_timeout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_timeout.py b/pytest_timeout.py index 2e560bb..2e15542 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -384,7 +384,7 @@ def _validate_method(method, where): def _validate_func_only(func_only, where): if func_only is None: - return False + return None if not isinstance(func_only, bool): raise ValueError("Invalid func_only value %s from %s" % (func_only, where)) return func_only From e7a77f6768042a836900a3df2eb4e5eebf48b8be Mon Sep 17 00:00:00 2001 From: Jacob Schaer Date: Sun, 6 Mar 2022 13:29:50 -0800 Subject: [PATCH 07/24] Add unit test Unit test for marker override of func_only --- test_pytest_timeout.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test_pytest_timeout.py b/test_pytest_timeout.py index 1ff2213..0e640a0 100644 --- a/test_pytest_timeout.py +++ b/test_pytest_timeout.py @@ -355,7 +355,30 @@ def test_ini_timeout_func_only(testdir): @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 """ From 0cc90b2932daa3c7d824f0afcf7c80043b15cc81 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 16:48:21 +0000 Subject: [PATCH 08/24] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test_pytest_timeout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_pytest_timeout.py b/test_pytest_timeout.py index 0e640a0..096e57c 100644 --- a/test_pytest_timeout.py +++ b/test_pytest_timeout.py @@ -378,7 +378,7 @@ def test_ini_timeout_func_only_marker_override(testdir): @pytest.fixture def slow(): time.sleep(2) - @pytest.mark.timeout(1.5) + @pytest.mark.timeout(1.5) def test_foo(slow): pass """ From d3a5c9d168508cd93b3245a2f5478135c7e707b6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 23:27:32 +0000 Subject: [PATCH 09/24] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v2.7.1 → v3.0.1](https://github.com/asottile/reorder_python_imports/compare/v2.7.1...v3.0.1) - [github.com/asottile/pyupgrade: v2.31.0 → v2.31.1](https://github.com/asottile/pyupgrade/compare/v2.31.0...v2.31.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 654dd60..758bc91 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,11 +36,11 @@ repos: - id: flake8 language_version: python3 - repo: https://github.com/asottile/reorder_python_imports - rev: v2.7.1 + rev: v3.0.1 hooks: - id: reorder-python-imports - repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 + rev: v2.31.1 hooks: - id: pyupgrade args: [--keep-percent-format, --py36-plus] From 7037b9488215e535308049fde5913b1edaa313d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 20:27:56 +0000 Subject: [PATCH 10/24] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.1.0 → 22.3.0](https://github.com/psf/black/compare/22.1.0...22.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 758bc91..4fb641b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ --- repos: - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black args: [--safe, --quiet, --target-version, py36] From 7745a09ffe6c2f46d0807a99f1d12458f87c0055 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 21:30:04 +0000 Subject: [PATCH 11/24] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.1.0 → v4.2.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.1.0...v4.2.0) - [github.com/asottile/pyupgrade: v2.31.1 → v2.32.0](https://github.com/asottile/pyupgrade/compare/v2.31.1...v2.32.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4fb641b..1bf4283 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: - id: blacken-docs additional_dependencies: [black==20.8b1] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -40,7 +40,7 @@ repos: hooks: - id: reorder-python-imports - repo: https://github.com/asottile/pyupgrade - rev: v2.31.1 + rev: v2.32.0 hooks: - id: pyupgrade args: [--keep-percent-format, --py36-plus] From 1da037ca82e52006c9915fa93a17a9cdde006474 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 20:48:06 +0000 Subject: [PATCH 12/24] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.0.1 → v3.1.0](https://github.com/asottile/reorder_python_imports/compare/v3.0.1...v3.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1bf4283..a74e138 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: - id: flake8 language_version: python3 - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.1.0 hooks: - id: reorder-python-imports - repo: https://github.com/asottile/pyupgrade From 0a3e9a9554b7277507aff26d208e1f4e7367c3ce Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 20:58:14 +0000 Subject: [PATCH 13/24] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.32.0 → v2.32.1](https://github.com/asottile/pyupgrade/compare/v2.32.0...v2.32.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a74e138..49f5d0c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: hooks: - id: reorder-python-imports - repo: https://github.com/asottile/pyupgrade - rev: v2.32.0 + rev: v2.32.1 hooks: - id: pyupgrade args: [--keep-percent-format, --py36-plus] From 28d4d3023e891c72d19bd4e9b3889dbe31fcac08 Mon Sep 17 00:00:00 2001 From: Reza Gharibi Date: Sat, 21 May 2022 13:38:30 +0430 Subject: [PATCH 14/24] Fix typos --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index b7f44b6..9533d22 100644 --- a/README.rst +++ b/README.rst @@ -244,11 +244,11 @@ Extending pytest-timeout with plugins ===================================== ``pytest-timeout`` provides two hooks that can be used for extending the tool. These -hooks are used for for setting the timeout timer and cancelling it it the timeout is not +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 ieration. +traceback and points on timed out ``await`` instead of the running loop iteration. See `pytest hooks documentation `_ for more info From 5aff2a9d3c07a41246698023f383b68ebe834546 Mon Sep 17 00:00:00 2001 From: Ievgen Popovych Date: Mon, 6 Jun 2022 17:27:27 +0300 Subject: [PATCH 15/24] Simplify handling of `func_only` ...by setting default on config file parsing level. pytest itself will validate that the value is a valid boolean, and if it is not set it will supply default `False` value. We later rely on this global default being always set and correct - falling back to it on test level. --- pytest_timeout.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/pytest_timeout.py b/pytest_timeout.py index 2e15542..2bb4f0b 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -70,7 +70,7 @@ def pytest_addoption(parser): ) parser.addini("timeout", TIMEOUT_DESC) parser.addini("timeout_method", METHOD_DESC) - parser.addini("timeout_func_only", FUNC_ONLY_DESC, type="bool") + parser.addini("timeout_func_only", FUNC_ONLY_DESC, type="bool", default=False) class TimeoutHooks: @@ -299,12 +299,7 @@ def get_env_settings(config): method = DEFAULT_METHOD func_only = config.getini("timeout_func_only") - if func_only == []: - # No value set - func_only = None - if func_only is not None: - func_only = _validate_func_only(func_only, "config file") - return Settings(timeout, method, func_only or False) + return Settings(timeout, method, func_only) def _get_item_settings(item, marker=None): @@ -323,8 +318,6 @@ def _get_item_settings(item, marker=None): method = item.config._env_timeout_method if func_only is None: func_only = item.config._env_timeout_func_only - if func_only is None: - func_only = False return Settings(timeout, method, func_only) From 7d4c413a6344edd8340628354419a324e68c6cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20K=C3=B6ch?= Date: Mon, 18 Jul 2022 19:29:25 +0100 Subject: [PATCH 16/24] Milder language on README As per https://github.com/pytest-dev/pytest-timeout/issues/124#issuecomment-1155633546 --- README.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 9533d22..7a5448b 100644 --- a/README.rst +++ b/README.rst @@ -19,13 +19,15 @@ 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 -**This is not the timeout you are looking for!** .. warning:: Please read this README carefully and only use this plugin if you - understand the consequences. Remember your test suite needs to be - **fast**, timeouts are a last resort not an expected failure mode. + 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 From 389a669bee1fc46cade96f77ce4265d232c352f8 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Tue, 31 Jan 2023 17:22:28 -0800 Subject: [PATCH 17/24] add --disable-debugger-detection flag --- README.rst | 4 ++ pytest_timeout.py | 94 +++++++++++++++++++++++++++++++++--------- test_pytest_timeout.py | 88 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index 7a5448b..df3bcf3 100644 --- a/README.rst +++ b/README.rst @@ -241,6 +241,10 @@ 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 ===================================== diff --git a/pytest_timeout.py b/pytest_timeout.py index 2bb4f0b..6177db0 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -40,11 +40,17 @@ 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. +""".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"]) +Settings = namedtuple( + "Settings", ["timeout", "method", "func_only", "disable_debugger_detection"] +) @pytest.hookimpl @@ -68,9 +74,21 @@ def pytest_addoption(parser): choices=["signal", "thread"], help=METHOD_DESC, ) + group.addoption( + "--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: @@ -107,19 +125,24 @@ 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): Set a timeout, timeout " + "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 " + "*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.", + "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) @@ -238,7 +261,7 @@ def pytest_timeout_set_timer(item, settings): def handler(signum, frame): __tracebackhide__ = True - timeout_sigalrm(item, settings.timeout) + timeout_sigalrm(item, settings) def cancel(): signal.setitimer(signal.ITIMER_REAL, 0) @@ -248,9 +271,7 @@ def 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.timeout) - ) + timer = threading.Timer(settings.timeout, timeout_timer, (item, settings)) timer.name = "%s %s" % (__name__, item.nodeid) def cancel(): @@ -299,12 +320,21 @@ def get_env_settings(config): method = DEFAULT_METHOD func_only = config.getini("timeout_func_only") - return Settings(timeout, method, 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 = None + timeout = method = func_only = disable_debugger_detection = None if not marker: marker = item.get_closest_marker("timeout") if marker is not None: @@ -312,13 +342,18 @@ def _get_item_settings(item, marker=None): 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 - return Settings(timeout, method, 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): @@ -329,7 +364,7 @@ def _parse_marker(marker): """ if not marker.args and not marker.kwargs: raise TypeError("Timeout marker must have at least one argument") - timeout = method = func_only = NOTSET = object() + timeout = method = func_only = disable_debugger_detection = NOTSET = object() for kw, val in marker.kwargs.items(): if kw == "timeout": timeout = val @@ -337,6 +372,8 @@ def _parse_marker(marker): method = val elif kw == "func_only": func_only = val + elif kw == "disable_debugger_detection": + disable_debugger_detection = val else: raise TypeError("Invalid keyword argument for timeout marker: %s" % kw) if len(marker.args) >= 1 and timeout is not NOTSET: @@ -347,7 +384,13 @@ def _parse_marker(marker): raise TypeError("Multiple values for method argument of timeout marker") elif len(marker.args) >= 2: method = marker.args[1] - if len(marker.args) > 2: + if len(marker.args) >= 3 and disable_debugger_detection is not NOTSET: + raise TypeError( + "Multiple values for disable_debugger_detection argument of timeout marker" + ) + elif len(marker.args) >= 3: + disable_debugger_detection = marker.args[2] + if len(marker.args) > 3: raise TypeError("Too many arguments for timeout marker") if timeout is NOTSET: timeout = None @@ -355,7 +398,9 @@ def _parse_marker(marker): method = None if func_only is NOTSET: func_only = None - return Settings(timeout, method, func_only) + if disable_debugger_detection is NOTSET: + disable_debugger_detection = None + return Settings(timeout, method, func_only, disable_debugger_detection) def _validate_timeout(timeout, where): @@ -383,14 +428,25 @@ def _validate_func_only(func_only, where): return func_only -def timeout_sigalrm(item, timeout): +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 is_debugging(): + if not settings.disable_debugger_detection and is_debugging(): return __tracebackhide__ = True nthreads = len(threading.enumerate()) @@ -399,16 +455,16 @@ def timeout_sigalrm(item, timeout): dump_stacks() if nthreads > 1: write_title("Timeout", sep="+") - pytest.fail("Timeout >%ss" % timeout) + pytest.fail("Timeout >%ss" % settings.timeout) -def timeout_timer(item, 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 is_debugging(): + if not settings.disable_debugger_detection and is_debugging(): return try: capman = item.config.pluginmanager.getplugin("capturemanager") diff --git a/test_pytest_timeout.py b/test_pytest_timeout.py index 096e57c..1db4da7 100644 --- a/test_pytest_timeout.py +++ b/test_pytest_timeout.py @@ -486,6 +486,94 @@ def test_foo(): 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} --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 + + +@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_marker( + testdir, debugging_module, debugging_set_trace +): + p1 = testdir.makepyfile( + """ + import pytest, {debugging_module} + + @pytest.mark.timeout(1, disable_debugger_detection=True) + 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(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 From 198b7921138334da11ccfc77e74075099a015613 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Wed, 1 Feb 2023 09:31:08 -0800 Subject: [PATCH 18/24] better cli option name --- pytest_timeout.py | 2 +- test_pytest_timeout.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pytest_timeout.py b/pytest_timeout.py index 6177db0..6d7cb55 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -75,7 +75,7 @@ def pytest_addoption(parser): help=METHOD_DESC, ) group.addoption( - "--disable-debugger-detection", + "--timeout-disable-debugger-detection", dest="timeout_disable_debugger_detection", action="store_true", help=DISABLE_DEBUGGER_DETECTION_DESC, diff --git a/test_pytest_timeout.py b/test_pytest_timeout.py index 1db4da7..b3b6dd2 100644 --- a/test_pytest_timeout.py +++ b/test_pytest_timeout.py @@ -520,7 +520,7 @@ def test_foo(): debugging_module=debugging_module, debugging_set_trace=debugging_set_trace ) ) - child = testdir.spawn_pytest(f"{p1} --disable-debugger-detection") + child = testdir.spawn_pytest(f"{p1} --timeout-disable-debugger-detection") child.expect("test_foo") time.sleep(1.2) result = child.read().decode().lower() From 6c3a5b405d7135b7ecfc7cb6bd0abad9bc00d20c Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Thu, 2 Feb 2023 10:19:57 -0800 Subject: [PATCH 19/24] remove marker support, update description --- pytest_timeout.py | 18 ++++------------- test_pytest_timeout.py | 44 ------------------------------------------ 2 files changed, 4 insertions(+), 58 deletions(-) diff --git a/pytest_timeout.py b/pytest_timeout.py index 6d7cb55..674aab6 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -42,7 +42,7 @@ """.strip() DISABLE_DEBUGGER_DETECTION_DESC = """ When specified, disables debugger detection. breakpoint(), pdb.set_trace(), etc. -will be interrupted. +will be interrupted by the timeout. """.strip() # bdb covers pdb, ipdb, and possibly others @@ -364,7 +364,7 @@ def _parse_marker(marker): """ if not marker.args and not marker.kwargs: raise TypeError("Timeout marker must have at least one argument") - timeout = method = func_only = disable_debugger_detection = NOTSET = object() + timeout = method = func_only = NOTSET = object() for kw, val in marker.kwargs.items(): if kw == "timeout": timeout = val @@ -372,8 +372,6 @@ def _parse_marker(marker): method = val elif kw == "func_only": func_only = val - elif kw == "disable_debugger_detection": - disable_debugger_detection = val else: raise TypeError("Invalid keyword argument for timeout marker: %s" % kw) if len(marker.args) >= 1 and timeout is not NOTSET: @@ -384,13 +382,7 @@ def _parse_marker(marker): raise TypeError("Multiple values for method argument of timeout marker") elif len(marker.args) >= 2: method = marker.args[1] - if len(marker.args) >= 3 and disable_debugger_detection is not NOTSET: - raise TypeError( - "Multiple values for disable_debugger_detection argument of timeout marker" - ) - elif len(marker.args) >= 3: - disable_debugger_detection = marker.args[2] - if len(marker.args) > 3: + if len(marker.args) > 2: raise TypeError("Too many arguments for timeout marker") if timeout is NOTSET: timeout = None @@ -398,9 +390,7 @@ def _parse_marker(marker): method = None if func_only is NOTSET: func_only = None - if disable_debugger_detection is NOTSET: - disable_debugger_detection = None - return Settings(timeout, method, func_only, disable_debugger_detection) + return Settings(timeout, method, func_only, None) def _validate_timeout(timeout, where): diff --git a/test_pytest_timeout.py b/test_pytest_timeout.py index b3b6dd2..da735eb 100644 --- a/test_pytest_timeout.py +++ b/test_pytest_timeout.py @@ -530,50 +530,6 @@ def test_foo(): assert "fail" 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_marker( - testdir, debugging_module, debugging_set_trace -): - p1 = testdir.makepyfile( - """ - import pytest, {debugging_module} - - @pytest.mark.timeout(1, disable_debugger_detection=True) - 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(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 From cc9cce7d7e4f01241dde5252dbb3ca8dd61a3b31 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Thu, 2 Feb 2023 14:00:45 -0800 Subject: [PATCH 20/24] update changelog --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index df3bcf3..ddf025c 100644 --- a/README.rst +++ b/README.rst @@ -332,6 +332,12 @@ function: Changelog ========= +2.2.0 +----- + +- Add ``--timeout-disable-debugger-detection`` flag, thanks + Michael Peters + 2.1.0 ----- From 2eb4f9c351425ff1401cebce8512fb80c0db9bc2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 21 Apr 2023 09:21:10 +1000 Subject: [PATCH 21/24] Added Python 3.11 --- .github/workflows/main.yml | 2 ++ setup.py | 1 + tox.ini | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index da52e69..ae98403 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,6 +21,8 @@ jobs: tox_env: "py39" - v: "3.10" tox_env: "py310" + - v: "3.11" + tox_env: "py311" os: [ubuntu-latest, windows-latest] steps: - name: Set Git to use LF diff --git a/setup.py b/setup.py index 212c0c1..755f168 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,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", ], diff --git a/tox.ini b/tox.ini index bc5e988..9084531 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ minversion = 2.8 addopts = -ra [tox] -envlist = py36,py37,py38,py39,py310,pypy3 +envlist = py36,py37,py38,py39,py310,py311,pypy3 [testenv] deps = pytest From 2ee3361f87cee1eabc115983cdf51308ec85ed2e Mon Sep 17 00:00:00 2001 From: youssefelmasry Date: Tue, 29 Aug 2023 15:05:59 +0000 Subject: [PATCH 22/24] update python versions --- .github/workflows/main.yml | 6 ++---- setup.py | 3 +-- tox.ini | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ae98403..c4c27ba 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,8 +8,6 @@ jobs: fail-fast: false matrix: python: - - v: "3.6" - tox_env: "py36" - v: "3.7" tox_env: "py37" - v: "3.7" @@ -29,9 +27,9 @@ jobs: run: | git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python.v }} - name: Install tox diff --git a/setup.py b/setup.py index 755f168..25b880a 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ py_modules=["pytest_timeout"], entry_points={"pytest11": ["timeout = pytest_timeout"]}, install_requires=["pytest>=5.0.0"], - python_requires=">=3.6", + python_requires=">=3.7", classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -31,7 +31,6 @@ "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/tox.ini b/tox.ini index 9084531..966ea49 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ minversion = 2.8 addopts = -ra [tox] -envlist = py36,py37,py38,py39,py310,py311,pypy3 +envlist = py37,py38,py39,py310,py311,pypy3 [testenv] deps = pytest From 263db99191bc98a8d392a04159279b4060e5e56e Mon Sep 17 00:00:00 2001 From: Scott Talbert Date: Wed, 4 Oct 2023 22:27:29 -0400 Subject: [PATCH 23/24] Add a pyproject.toml and setup.cfg This enables building/installing without using deprecated setup.py (but setup.py is still available). --- pyproject.toml | 3 +++ setup.cfg | 39 +++++++++++++++++++++++++++++++++++++++ setup.py | 41 ++--------------------------------------- 3 files changed, 44 insertions(+), 39 deletions(-) create mode 100644 pyproject.toml create mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fed528d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..051cdf3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,39 @@ +[metadata] +name = pytest-timeout +description = pytest plugin to abort hanging tests +long_description = file: README.rst +version = 2.1.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 diff --git a/setup.py b/setup.py index 25b880a..4289c21 100644 --- a/setup.py +++ b/setup.py @@ -1,42 +1,5 @@ """Setuptools install script for pytest-timeout.""" from setuptools import setup -with open("README.rst", encoding="utf-8") as f: - long_description = f.read() - - -setup( - name="pytest-timeout", - description="pytest plugin to abort hanging tests", - long_description=long_description, - version="2.1.0", - author="Floris Bruynooghe", - author_email="flub@devork.be", - url="https://github.com/pytest-dev/pytest-timeout", - license="MIT", - py_modules=["pytest_timeout"], - entry_points={"pytest11": ["timeout = pytest_timeout"]}, - install_requires=["pytest>=5.0.0"], - python_requires=">=3.7", - 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", - ], -) +if __name__ == "__main__": + setup() From 78cc24f0d91ca24a7448779fcf5c2f22470b6ae4 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 8 Oct 2023 12:08:56 +0200 Subject: [PATCH 24/24] version bump --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 051cdf3..be6b63f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ name = pytest-timeout description = pytest plugin to abort hanging tests long_description = file: README.rst -version = 2.1.0 +version = 2.2.0 author = Floris Bruynooghe author_email = flub@devork.be url = https://github.com/pytest-dev/pytest-timeout