diff --git a/README.rst b/README.rst index 02e5e8f..5bfa218 100644 --- a/README.rst +++ b/README.rst @@ -351,6 +351,8 @@ set of tests running in one session. A session all of the tests that will be run with one invocation of pytest. A session timeout is set with ``--session-timeout`` and is in seconds. +Elapsed session time uses a monotonic clock, so changes to the system clock +do not affect the timeout. The following example shows a session timeout of 10 minutes (600 seconds):: diff --git a/pytest_timeout.py b/pytest_timeout.py index 192a16c..370dbe4 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -165,7 +165,7 @@ def pytest_configure(config): if ini: timeout = _validate_timeout(config.getini("session_timeout"), "config file") if timeout is not None: - expire_time = time.time() + timeout + expire_time = time.monotonic() + timeout else: expire_time = 0 timeout = 0 @@ -192,7 +192,7 @@ def pytest_runtest_protocol(item): # check session timeout expire_time = item.session.config.stash[SESSION_EXPIRE_KEY] - if expire_time and (expire_time < time.time()): + if expire_time and (expire_time < time.monotonic()): timeout = item.session.config.stash[SESSION_TIMEOUT_KEY] item.session.shouldfail = f"session-timeout: {timeout} sec exceeded" diff --git a/test_pytest_timeout.py b/test_pytest_timeout.py index ff36c40..c7f6ad3 100644 --- a/test_pytest_timeout.py +++ b/test_pytest_timeout.py @@ -708,6 +708,46 @@ def test_two(slow_setup_and_teardown): result.assert_outcomes(passed=1) +@pytest.mark.parametrize( + ("wall_shift", "elapsed", "passed"), [(1000, 1, 2), (-1000, 11, 1)] +) +def test_session_timeout_ignores_wall_clock_changes( + pytester, wall_shift, elapsed, passed +): + pytester.makeconftest( + """ + from types import SimpleNamespace + import pytest + import pytest_timeout + + clock = SimpleNamespace(wall=100, elapsed=0) + + @pytest.hookimpl(tryfirst=True) + def pytest_configure(config): + pytest_timeout.time = SimpleNamespace( + time=lambda: clock.wall, + monotonic=lambda: clock.elapsed, + ) + """ + ) + pytester.makepyfile( + f""" + from conftest import clock + + def test_one(): + clock.wall += {wall_shift} + clock.elapsed += {elapsed} + + def test_two(): + pass + """ + ) + result = pytester.runpytest_subprocess("--session-timeout=10") + result.assert_outcomes(passed=passed) + if passed == 1: + result.stdout.fnmatch_lines(["*session-timeout: 10.0 sec exceeded*"]) + + def test_ini_session_timeout(pytester): pytester.makepyfile( """