Skip to content

Commit 611d8b7

Browse files
committed
test framework: small fixes
- use threadlocal stacks for `catch_uncaught_signals` and `postproc` - testset: restore state in a finally block
1 parent 60e1a3b commit 611d8b7

File tree

2 files changed

+19
-17
lines changed

2 files changed

+19
-17
lines changed

unpythonic/syntax/testingtools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def _observe(thunk):
7474
the dynamic extent of thunk propagated to this level.
7575
"""
7676
def intercept(condition):
77-
if not fixtures._catch_uncaught_signals[0]:
77+
if not fixtures._threadlocals.catch_uncaught_signals[0]:
7878
return # cancel and delegate to the next outer handler
7979

8080
# If we get an internal signal from this test framework itself, ignore

unpythonic/test/fixtures.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
from collections import deque
114114
from functools import partial
115115
from traceback import format_tb
116-
from threading import Lock
116+
import threading
117117
import sys
118118

119119
from ..conditions import handlers, find_restart, invoke
@@ -153,7 +153,7 @@
153153
The `+ N Warnings` message will be shown at the end of the nearest enclosing testset,
154154
and any testsets enclosing that one, up to the top level.
155155
"""
156-
_counter_update_lock = Lock()
156+
_counter_update_lock = threading.Lock()
157157
def _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
492493
def 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
515517
def 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
552554
def 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

Comments
 (0)