10
10
import os
11
11
import signal
12
12
import sys
13
+ import time
13
14
import threading
14
15
import traceback
15
16
from collections import namedtuple
43
44
When specified, disables debugger detection. breakpoint(), pdb.set_trace(), etc.
44
45
will be interrupted by the timeout.
45
46
""" .strip ()
47
+ SUITE_TIMEOUT_DESC = """
48
+ Timeout in minutes for entire suite. Default is None which
49
+ means no timeout. Timeout is checked between tests, and will not interrupt a test
50
+ in progress. Can be specified as a float for partial minutes.
51
+ """ .strip ()
46
52
47
53
# bdb covers pdb, ipdb, and possibly others
48
54
# pydevd covers PyCharm, VSCode, and possibly others
@@ -79,6 +85,15 @@ def pytest_addoption(parser):
79
85
action = "store_true" ,
80
86
help = DISABLE_DEBUGGER_DETECTION_DESC ,
81
87
)
88
+ group .addoption (
89
+ "--suite-timeout" ,
90
+ action = "store" ,
91
+ dest = "suite_timeout" ,
92
+ default = None ,
93
+ type = float ,
94
+ metavar = "minutes" ,
95
+ help = SUITE_TIMEOUT_DESC ,
96
+ )
82
97
parser .addini ("timeout" , TIMEOUT_DESC )
83
98
parser .addini ("timeout_method" , METHOD_DESC )
84
99
parser .addini ("timeout_func_only" , FUNC_ONLY_DESC , type = "bool" , default = False )
@@ -119,9 +134,13 @@ def pytest_addhooks(pluginmanager):
119
134
pluginmanager .add_hookspecs (TimeoutHooks )
120
135
121
136
137
+ _suite_expire_time = 0
138
+
139
+
122
140
@pytest .hookimpl
123
141
def pytest_configure (config ):
124
142
"""Register the marker so it shows up in --markers output."""
143
+ global _suite_expire_time , _suite_timeout_minutes
125
144
config .addinivalue_line (
126
145
"markers" ,
127
146
"timeout(timeout, method=None, func_only=False, "
@@ -143,6 +162,11 @@ def pytest_configure(config):
143
162
config ._env_timeout_func_only = settings .func_only
144
163
config ._env_timeout_disable_debugger_detection = settings .disable_debugger_detection
145
164
165
+ _suite_timeout_minutes = config .getoption ("--suite-timeout" )
166
+ if _suite_timeout_minutes :
167
+ _suite_expire_time = time .time () + (_suite_timeout_minutes * 60 )
168
+
169
+
146
170
147
171
@pytest .hookimpl (hookwrapper = True )
148
172
def pytest_runtest_protocol (item ):
@@ -507,3 +531,9 @@ def dump_stacks(terminal):
507
531
thread_name = "<unknown>"
508
532
terminal .sep ("~" , title = "Stack of %s (%s)" % (thread_name , thread_ident ))
509
533
terminal .write ("" .join (traceback .format_stack (frame )))
534
+
535
+
536
+ def pytest_runtest_logfinish (nodeid , location ):
537
+ if _suite_expire_time and _suite_expire_time < time .time ():
538
+ pytest .exit (f"suite-timeout: { _suite_timeout_minutes } minutes exceeded" ,
539
+ returncode = 0 )
0 commit comments