113113from collections import deque
114114from functools import partial
115115from traceback import format_tb
116- from threading import Lock
116+ import threading
117117import sys
118118
119119from ..conditions import handlers , find_restart , invoke
153153The `+ N Warnings` message will be shown at the end of the nearest enclosing testset,
154154and any testsets enclosing that one, up to the top level.
155155"""
156- _counter_update_lock = Lock ()
156+ _counter_update_lock = threading . Lock ()
157157def _update (counter , delta ):
158158 """Update a global test counter in a thread-safe way.
159159
@@ -487,7 +487,8 @@ def returns_normally(expr):
487487 # our arg, and:
488488 return True
489489
490- _catch_uncaught_signals = deque ([True ]) # on by default
490+ _threadlocals = threading .local ()
491+ _threadlocals .catch_uncaught_signals = deque ([True ]) # on by default
491492@contextmanager
492493def catch_signals (state ):
493494 """Context manager.
@@ -506,10 +507,11 @@ def catch_signals(state):
506507 `with catch_signals` blocks can be nested; the most recent (i.e.
507508 dynamically innermost) one wins.
508509 """
509- _catch_uncaught_signals .appendleft (state )
510+ _threadlocals . catch_uncaught_signals .appendleft (state )
510511 yield
511- _catch_uncaught_signals .popleft ()
512+ _threadlocals . catch_uncaught_signals .popleft ()
512513
514+ # TODO: how to handle nesting level across multiple threads? Fully independent doesn't make sense.
513515_nesting_level = 0
514516@contextmanager
515517def session (name = None ):
@@ -547,7 +549,7 @@ def session(name=None):
547549 maybe_colorize ("END" , TC .BRIGHT , TestConfig .CS .HEADING ))
548550
549551# We use a stack for postprocs so that the local overrides can be nested.
550- _postproc_stack = deque ()
552+ _threadlocals . postproc_stack = deque ()
551553@contextmanager
552554def testset (name = None , postproc = None ):
553555 """Context manager representing a test set.
@@ -599,7 +601,7 @@ def print_and_proceed(condition):
599601 TC .BRIGHT , TestConfig .CS .WARNING ) + str (condition )
600602 # So any other signal must come from another source.
601603 else :
602- if not _catch_uncaught_signals [0 ]:
604+ if not _threadlocals . catch_uncaught_signals [0 ]:
603605 return # cancel and delegate to the next outer handler
604606 # To highlight the error in the summary, count it as an errored test.
605607 _update (tests_run , + 1 )
@@ -609,9 +611,9 @@ def print_and_proceed(condition):
609611 TestConfig .printer (msg )
610612
611613 # the custom callback
612- if _postproc_stack :
613- r = _postproc_stack [0 ]
614- elif TestConfig .postproc is not None :
614+ if _threadlocals . postproc_stack :
615+ r = _threadlocals . postproc_stack [0 ]
616+ elif TestConfig .postproc is not None : # the global default is shared across all threads.
615617 r = TestConfig .postproc
616618 else :
617619 r = None
@@ -631,7 +633,7 @@ def print_and_proceed(condition):
631633 # Otherwise we just return normally (cancel and delegate to the next outer handler).
632634
633635 if postproc is not None :
634- _postproc_stack .appendleft (postproc )
636+ _threadlocals . postproc_stack .appendleft (postproc )
635637
636638 # The test[] macro signals a condition (using `cerror`), does not raise an
637639 # exception. That gives it the superpower to resume the rest of the tests.
@@ -648,11 +650,11 @@ def print_and_proceed(condition):
648650 TC .BRIGHT , TestConfig .CS .ERROR )
649651 msg += describe_exception (err )
650652 TestConfig .printer (msg )
651-
652- if postproc is not None :
653- _postproc_stack .popleft ()
654- _nesting_level -= 1
655- assert _nesting_level >= 0
653+ finally :
654+ if postproc is not None :
655+ _threadlocals . postproc_stack .popleft ()
656+ _nesting_level -= 1
657+ assert _nesting_level >= 0
656658
657659 r2 , f2 , e2 , w2 = counters ()
658660 runs = r2 - r1
0 commit comments