@@ -324,8 +324,8 @@ class TestConfig:
324324 errors. `TestConfig.postproc` sets the default that is used when no other
325325 (more local) `postproc` is in effect.
326326
327- It receives one argument, which is a `TestFailure` or `TestError` instance
328- that was signaled by a failed or errored test (respectively).
327+ It receives one argument, which is a `TestFailure`, `TestError` or `TestWarning`
328+ instance that was signaled by a failed, errored or warned test (respectively).
329329
330330 `postproc` is called after sending the error to `printer`, just before
331331 resuming with the remaining tests. To continue processing, the `postproc`
@@ -334,6 +334,21 @@ class TestConfig:
334334 If you want a failure in a particular testset to abort the whole unit, you
335335 can use `terminate` as your `postproc`.
336336 """
337+ # It is overwhelmingly common that tests are invoked from a single thread,
338+ # so by default, all threads share the same printer. (It is not worth
339+ # complicating the common use case here to cater for the rare use case.)
340+ #
341+ # However, if you want different printers in different threads, that can
342+ # be done. As `printer`, use a `Shim` that contains a `ThreadLocalBox`.
343+ # In each thread, place in that box a custom object that has a `__call__`
344+ # method that takes the same args `print` does. Because `Shim` redirects
345+ # all attribute accesses, it will redirect the lookup of `__call__`
346+ # (it doesn't have its own `__call__`, so it assumes the client wants to
347+ # call the thing that is inside the box), and hence that method will then
348+ # be used for printing.
349+ #
350+ # TODO: This is subject to change later if I figure out a better design
351+ # TODO: that conveniently caters for *both* the common and rare use cases.
337352 printer = partial (print , file = sys .stderr )
338353 use_color = True
339354 postproc = None
@@ -511,8 +526,7 @@ def catch_signals(state):
511526 yield
512527 _threadlocals .catch_uncaught_signals .popleft ()
513528
514- # TODO: how to handle nesting level across multiple threads? Fully independent doesn't make sense.
515- _nesting_level = 0
529+ _threadlocals .nesting_level = 0
516530@contextmanager
517531def session (name = None ):
518532 """Context manager representing a test session.
@@ -523,7 +537,7 @@ def session(name=None):
523537 To terminate the session by the first failure in a particular testset,
524538 use `terminate` as `postproc` for that testset.
525539 """
526- if _nesting_level > 0 :
540+ if _threadlocals . nesting_level > 0 :
527541 raise RuntimeError ("A test `session` cannot be nested inside a `testset`." )
528542
529543 title = maybe_colorize ("SESSION" , TC .BRIGHT , TestConfig .CS .HEADING )
@@ -575,10 +589,9 @@ def makeindent(level):
575589 indent += " "
576590 return indent
577591
578- global _nesting_level
579- indent = makeindent (_nesting_level )
580- errmsg_indent = makeindent (_nesting_level + 1 )
581- _nesting_level += 1
592+ indent = makeindent (_threadlocals .nesting_level )
593+ errmsg_indent = makeindent (_threadlocals .nesting_level + 1 )
594+ _threadlocals .nesting_level += 1
582595
583596 title = "{}Testset" .format (indent )
584597 if name is not None :
@@ -653,8 +666,8 @@ def print_and_proceed(condition):
653666 finally :
654667 if postproc is not None :
655668 _threadlocals .postproc_stack .popleft ()
656- _nesting_level -= 1
657- assert _nesting_level >= 0
669+ _threadlocals . nesting_level -= 1
670+ assert _threadlocals . nesting_level >= 0
658671
659672 r2 , f2 , e2 , w2 = counters ()
660673 runs = r2 - r1
0 commit comments