@@ -37,11 +37,35 @@ def __exit__(self, exc_type, exc_value, tb):
3737 return False
3838
3939
40+ # These are used to provide required context to things like subTest
41+ __current_test__ = None
42+ __test_result__ = None
43+
44+
45+ class SubtestContext :
46+ def __enter__ (self ):
47+ pass
48+
49+ def __exit__ (self , * exc_info ):
50+ if exc_info [0 ] is not None :
51+ # Exception raised
52+ global __test_result__ , __current_test__
53+ handle_test_exception (
54+ __current_test__ ,
55+ __test_result__ ,
56+ exc_info
57+ )
58+ # Suppress the exception as we've captured it above
59+ return True
60+
61+
62+
63+
4064class NullContext :
4165 def __enter__ (self ):
4266 pass
4367
44- def __exit__ (self , a , b , c ):
68+ def __exit__ (self , exc_type , exc_value , traceback ):
4569 pass
4670
4771
@@ -61,7 +85,7 @@ def doCleanups(self):
6185 func (* args , ** kwargs )
6286
6387 def subTest (self , msg = None , ** params ):
64- return NullContext ( )
88+ return SubtestContext ( msg = msg , params = params )
6589
6690 def skipTest (self , reason ):
6791 raise SkipTest (reason )
@@ -298,15 +322,29 @@ def __add__(self, other):
298322 return self
299323
300324
301- def capture_exc (e ):
325+ def capture_exc (exc , traceback ):
302326 buf = io .StringIO ()
303327 if hasattr (sys , "print_exception" ):
304- sys .print_exception (e , buf )
328+ sys .print_exception (exc , buf )
305329 elif traceback is not None :
306- traceback .print_exception (None , e , sys . exc_info ()[ 2 ] , file = buf )
330+ traceback .print_exception (None , exc , traceback , file = buf )
307331 return buf .getvalue ()
308332
309333
334+ def handle_test_exception (current_test : tuple , test_result : TestResult , exc_info : tuple ):
335+ exc = exc_info [1 ]
336+ traceback = exc_info [2 ]
337+ ex_str = capture_exc (exc , traceback )
338+ if isinstance (exc , AssertionError ):
339+ test_result .failuresNum += 1
340+ test_result .failures .append ((current_test , ex_str ))
341+ print (" FAIL" )
342+ else :
343+ test_result .errorsNum += 1
344+ test_result .errors .append ((current_test , ex_str ))
345+ print (" ERROR" )
346+
347+
310348def run_suite (c , test_result , suite_name = "" ):
311349 if isinstance (c , TestSuite ):
312350 c .run (test_result )
@@ -324,29 +362,34 @@ def run_suite(c, test_result, suite_name=""):
324362 except AttributeError :
325363 pass
326364
327- def run_one (m ):
365+ def run_one (test_function ):
366+ global __test_result__ , __current_test__
328367 print ("%s (%s) ..." % (name , suite_name ), end = "" )
329368 set_up ()
369+ __test_result__ = test_result
370+ test_container = f"({ suite_name } )"
371+ __current_test__ = (name , test_container )
330372 try :
331373 test_result .testsRun += 1
332- m ()
374+ test_globals = dict (** globals ())
375+ test_globals ["test_function" ] = test_function
376+ exec ("test_function()" , test_globals , test_globals )
377+ # No exception occurred, test passed
333378 print (" ok" )
334379 except SkipTest as e :
335380 print (" skipped:" , e .args [0 ])
336381 test_result .skippedNum += 1
337382 except Exception as ex :
338- ex_str = capture_exc (ex )
339- if isinstance (ex , AssertionError ):
340- test_result .failuresNum += 1
341- test_result .failures .append (((name , c ), ex_str ))
342- print (" FAIL" )
343- else :
344- test_result .errorsNum += 1
345- test_result .errors .append (((name , c ), ex_str ))
346- print (" ERROR" )
383+ handle_test_exception (
384+ current_test = (name , c ),
385+ test_result = test_result ,
386+ exc_info = sys .exc_info ()
387+ )
347388 # Uncomment to investigate failure in detail
348389 # raise
349390 finally :
391+ __test_result__ = None
392+ __current_test__ = None
350393 tear_down ()
351394 try :
352395 o .doCleanups ()
0 commit comments