diff --git a/README.rst b/README.rst index ebbaf1a..85da61d 100644 --- a/README.rst +++ b/README.rst @@ -340,6 +340,39 @@ function: pytest.fail("+++ Timeout +++") + +Session Timeout +=============== + +The above mentioned timeouts are all per test function. You can also set a +session timeout 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 + +Friendly timeouts +----------------- + +Session timeouts are "friendly" timeouts. The plugin checks the session time at the end of +each test function, and stops further tests from running if the session timeout is exceeded. + +Combining session and function +------------------------------ + +It works fine to combine both session and function timeouts. +For example, to limit test functions to 5 seconds and the full session to 100 seconds:: + + pytest --timeout=5 --session-timeout=100 + + Changelog ========= @@ -353,6 +386,7 @@ Unreleased 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. 2.2.0 ----- diff --git a/pytest_timeout.py b/pytest_timeout.py index 800d0cd..de4878a 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -20,6 +20,7 @@ __all__ = ("is_debugging", "Settings") SESSION_TIMEOUT_KEY = pytest.StashKey[float]() +SESSION_EXPIRE_KEY = pytest.StashKey[float]() HAVE_SIGALRM = hasattr(signal, "SIGALRM") @@ -104,6 +105,7 @@ def pytest_addoption(parser): type="bool", default=False, ) + parser.addini("session_timeout", SESSION_TIMEOUT_DESC) class TimeoutHooks: @@ -159,12 +161,18 @@ def pytest_configure(config): config._env_timeout_func_only = settings.func_only config._env_timeout_disable_debugger_detection = settings.disable_debugger_detection - timeout = config.getoption("--session-timeout") + 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 - config.stash[SESSION_TIMEOUT_KEY] = expire_time + timeout = 0 + config.stash[SESSION_TIMEOUT_KEY] = timeout + config.stash[SESSION_EXPIRE_KEY] = expire_time @pytest.hookimpl(hookwrapper=True) @@ -185,9 +193,9 @@ def pytest_runtest_protocol(item): hooks.pytest_timeout_cancel_timer(item=item) # check session timeout - expire_time = item.session.config.stash[SESSION_TIMEOUT_KEY] + expire_time = item.session.config.stash[SESSION_EXPIRE_KEY] if expire_time and (expire_time < time.time()): - timeout = item.session.config.getoption("--session-timeout") + timeout = item.session.config.stash[SESSION_TIMEOUT_KEY] item.session.shouldfail = f"session-timeout: {timeout} sec exceeded" @@ -223,7 +231,7 @@ def pytest_report_header(config): ) ) - session_timeout = config.getoption("--session-timeout") + session_timeout = config.getoption("session_timeout") if session_timeout: timeout_header.append("session timeout: %ss" % session_timeout) if timeout_header: diff --git a/test_pytest_timeout.py b/test_pytest_timeout.py index ac4e9fa..175eda8 100644 --- a/test_pytest_timeout.py +++ b/test_pytest_timeout.py @@ -623,17 +623,40 @@ def test_session_timeout(pytester): @pytest.fixture() def slow_setup_and_teardown(): - time.sleep(0.5) + time.sleep(1) yield - time.sleep(0.5) + time.sleep(1) def test_one(slow_setup_and_teardown): - time.sleep(0.5) + time.sleep(1) def test_two(slow_setup_and_teardown): - time.sleep(0.5) + time.sleep(1) """ ) - result = pytester.runpytest_subprocess("--session-timeout", "1.25") - result.stdout.fnmatch_lines(["*!! session-timeout: 1.25 sec exceeded !!!*"]) + result = pytester.runpytest_subprocess("--session-timeout", "2") + result.stdout.fnmatch_lines(["*!! session-timeout: 2.0 sec exceeded !!!*"]) + result.assert_outcomes(passed=1) + + +def test_ini_session_timeout(pytester): + pytester.makepyfile( + """ + import time + + def test_one(): + time.sleep(2) + + def test_two(): + time.sleep(2) + """ + ) + pytester.makeini( + """ + [pytest] + session_timeout = 1 + """ + ) + result = pytester.runpytest_subprocess() + result.stdout.fnmatch_lines(["*!! session-timeout: 1.0 sec exceeded !!!*"]) result.assert_outcomes(passed=1)