Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ jobs:
fail-fast: false
matrix:
python:
- v: "3.8"
tox_env: "py38"
- v: "3.9"
tox_env: "py39"
- v: "3.10"
tox_env: "py310"
- v: "3.11"
Expand All @@ -28,9 +24,9 @@ jobs:
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v3
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python.v }}
- name: Install tox
Expand Down
45 changes: 25 additions & 20 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,50 +1,55 @@
---
repos:
- repo: https://github.com/psf/black
rev: 23.12.1
rev: 25.9.0
hooks:
- id: black
args: [--safe, --quiet, --target-version, py37]
args: [--safe, --quiet, --target-version, py310]
- repo: https://github.com/asottile/blacken-docs
rev: 1.16.0
rev: 1.20.0
hooks:
- id: blacken-docs
additional_dependencies: [black==23.12.1]
additional_dependencies: [black==25.9.0]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: fix-encoding-pragma
args: [--remove]
- id: check-ast
- id: check-case-conflict
- id: check-json
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: check-illegal-windows-names
- id: check-symlinks
- id: check-added-large-files
args: ['--maxkb=500']
- id: debug-statements
- id: destroyed-symlinks
- id: end-of-file-fixer
- id: mixed-line-ending
args: ["--fix=lf"]
- id: trailing-whitespace
language_version: python3
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
language_version: python3
additional_dependencies: [flake8-typing-imports==1.15.0]
- repo: https://github.com/PyCQA/flake8
additional_dependencies: [flake8-typing-imports==1.17.0]
- repo: https://github.com/pycqa/isort
rev: 7.0.0
hooks:
- id: flake8
language_version: python3
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.12.0
hooks:
- id: reorder-python-imports
- id: isort
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
rev: v3.21.1
hooks:
- id: pyupgrade
args: [--keep-percent-format, --py37-plus]
args: [--keep-percent-format, --py310-plus]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: rst-backticks
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.33.0
rev: v1.37.1
hooks:
- id: yamllint
7 changes: 3 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,9 @@ Changelog

x.y.z
-----
- Add support Python3.13 and Python3.14. Thanks Vladimir
Roshchin.
- Detect debuggers registered with sys.monitoring. Thanks Rich
Chiodo.
- Minimum support Python3.10 and pytest=8.0. Thanks Vladimir Roshchin.
- Add support Python3.13 and Python3.14. Thanks Vladimir Roshchin.
- Detect debuggers registered with sys.monitoring. Thanks Rich Chiodo.

2.3.1
-----
Expand Down
1 change: 1 addition & 0 deletions failure_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

pytest failure_demo.py
"""

import threading
import time

Expand Down
49 changes: 24 additions & 25 deletions pytest_timeout.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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
Expand All @@ -17,17 +18,13 @@

import pytest


__all__ = ("is_debugging", "Settings")
__all__ = ("Settings", "is_debugging")
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"
DEFAULT_METHOD = "signal" if HAVE_SIGALRM else "thread"
TIMEOUT_DESC = """
Timeout in seconds before dumping the stacks. Default is 0 which
means no timeout.
Expand Down Expand Up @@ -112,7 +109,7 @@ class TimeoutHooks:
"""Timeout specific hooks."""

@pytest.hookspec(firstresult=True)
def pytest_timeout_set_timer(item, settings):
def pytest_timeout_set_timer(self, item, settings):
"""Called at timeout setup.

'item' is a pytest node to setup timeout for.
Expand All @@ -122,7 +119,7 @@ def pytest_timeout_set_timer(item, settings):
"""

@pytest.hookspec(firstresult=True)
def pytest_timeout_cancel_timer(item):
def pytest_timeout_cancel_timer(self, item):
"""Called at timeout teardown.

'item' is a pytest node which was used for timeout setup.
Expand Down Expand Up @@ -223,19 +220,17 @@ def pytest_report_header(config):

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,
)
f"timeout: {config._env_timeout}s\n"
f"timeout method: {config._env_timeout_method}\n"
f"timeout func_only: {config._env_timeout_func_only}"
)

session_timeout = config.getoption("session_timeout")
if session_timeout:
timeout_header.append("session timeout: %ss" % session_timeout)
timeout_header.append(f"session timeout: {session_timeout}s")
if timeout_header:
return timeout_header
return None


@pytest.hookimpl(tryfirst=True)
Expand All @@ -249,10 +244,10 @@ def pytest_exception_interact(node):
def pytest_enter_pdb():
"""Stop the timeouts when we entered pdb.

This stops timeouts from triggering when pytest's builting pdb
This stops timeouts from triggering when pytest's building pdb
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This stops timeouts from triggering when pytest's building pdb
This stops timeouts from triggering when pytest's builtin pdb

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed this, thanks
And change black 25.9.0 to 25.11.0

support notices we entered pdb.
"""
# Since pdb.set_trace happens outside of any pytest control, we don't have
# Since pdb.set_trace happens outside 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
Expand Down Expand Up @@ -326,7 +321,7 @@ def cancel():
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)
timer.name = f"{__name__} {item.nodeid}"

def cancel():
timer.cancel()
Expand Down Expand Up @@ -427,14 +422,15 @@ def _parse_marker(marker):
elif kw == "func_only":
func_only = val
else:
raise TypeError("Invalid keyword argument for timeout marker: %s" % kw)
msg = f"Invalid keyword argument for timeout marker: {kw}"
raise TypeError(msg)
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:
if 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:
if len(marker.args) >= 2:
method = marker.args[1]
if len(marker.args) > 2:
raise TypeError("Too many arguments for timeout marker")
Expand All @@ -453,22 +449,25 @@ def _validate_timeout(timeout, where):
try:
return float(timeout)
except ValueError:
raise ValueError("Invalid timeout %s from %s" % (timeout, where))
msg = f"Invalid timeout {timeout} from {where}"
raise ValueError(msg)


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))
msg = f"Invalid method {method} from {where}"
raise ValueError(msg)
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))
msg = f"Invalid func_only value {func_only} from {where}"
raise ValueError(msg)
return func_only


Expand Down Expand Up @@ -555,5 +554,5 @@ def dump_stacks(terminal):
break
else:
thread_name = "<unknown>"
terminal.sep("~", title="Stack of %s (%s)" % (thread_name, thread_ident))
terminal.sep("~", title=f"Stack of {thread_name} ({thread_ident})")
terminal.write("".join(traceback.format_stack(frame)))
7 changes: 2 additions & 5 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ classifiers =
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
Programming Language :: Python :: 3.12
Expand All @@ -35,8 +32,8 @@ classifiers =
[options]
py_modules = pytest_timeout
install_requires =
pytest>=7.0.0
python_requires = >=3.7
pytest>=8.0.0
python_requires = >=3.10

[options.entry_points]
pytest11 =
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Setuptools install script for pytest-timeout."""

from setuptools import setup

if __name__ == "__main__":
Expand Down
19 changes: 6 additions & 13 deletions test_pytest_timeout.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

from pytest_timeout import PYTEST_FAILURE_MESSAGE


MATCH_FAILURE_MESSAGE = f"*Failed: {PYTEST_FAILURE_MESSAGE}*"
pytest_plugins = "pytester"

Expand Down Expand Up @@ -136,7 +135,7 @@ def test_foo():
@pytest.mark.parametrize("scope", ["function", "class", "module", "session"])
def test_fix_setup(meth, scope, pytester):
pytester.makepyfile(
"""
f"""
import time, pytest

class TestFoo:
Expand All @@ -147,9 +146,7 @@ def fix(self):

def test_foo(self, fix):
pass
""".format(
scope=scope
)
"""
)
result = pytester.runpytest_subprocess("--timeout=1", f"--timeout-method={meth}")
assert result.ret > 0
Expand Down Expand Up @@ -466,15 +463,13 @@ def test_suppresses_timeout_when_debugger_is_entered(
pytester, debugging_module, debugging_set_trace
):
p1 = pytester.makepyfile(
"""
f"""
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 = pytester.spawn_pytest(str(p1))
child.expect("test_foo")
Expand Down Expand Up @@ -512,15 +507,13 @@ def test_disable_debugger_detection_flag(
pytester, debugging_module, debugging_set_trace
):
p1 = pytester.makepyfile(
"""
f"""
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 = pytester.spawn_pytest(f"{p1} --timeout-disable-debugger-detection")
child.expect("test_foo")
Expand Down
6 changes: 3 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[pytest]
minversion = 7.0
minversion = 8.0
addopts = -ra

[tox]
envlist = py38,py39,py310,py311,py312,py313,py314,pypy3
envlist = py310,py311,py312,py313,py314,pypy3

[testenv]
deps = pytest
Expand All @@ -16,7 +16,7 @@ commands = pytest {posargs}
[testenv:linting]
skip_install = True
basepython = python3
deps = pre-commit>=1.11.0
deps = pre-commit>=4.0.0
commands = pre-commit run --all-files --show-diff-on-failure


Expand Down