gh-62965: Add debugging support to unittest (--debug --pm --pdb --trace)
Note: The --debug mode (break run with original exception for seamless external post-mortem handling - e.g. for IDEs, interactive, super-runners ...) was originally inspired by #23900 . Some test code was cherry-picked. Yet the implementation here became completely different, as the primitive TestCase.debug() method cannot really provide compatible test control flow consistent with modern features.
The other debugging modes (with in-line callback way of execution) were inspired by the original discussion in #62965 and pytest.
- Issue: gh-62965
at first glance this is likely good, i'll need to dive into the case or suite code to understand the postmortem hook stuff better. having some other eyeballs on this would be good as well.
Test code I used to try things out:
import sys, traceback, gc
import unittest
class TC(unittest.TestCase):
def clInstanceA(self):
print("TC.clInstanceA", self)
def clInstanceB(self):
print("TC.clInstanceB", self)
##CLIB / 0
def clInstanceC(self):
print("TC.clInstanceC", self)
##CLIC / 0
def setUp(self):
print("TC.setUp", self)
self.addCleanup(self.clInstanceA)
self.addCleanup(self.clInstanceB)
self.addCleanup(self.clInstanceC)
##0 / 0
def tearDown(self):
print("TC.tearDown", self)
##9 / 0
@classmethod
def clClass(self):
print("TC.clClass", self)
CLC / 0
@classmethod
def setUpClass(cls):
print("TC.setUpClass", cls)
cls.addClassCleanup(cls.clClass)
##breakpoint()
##SUC / 0
@classmethod
def tearDownClass(cls):
print("TC.tearDownClass", cls)
y = unittest.case._AutoDelRunner(lambda: print("-- delframe TDC"))
TDC / 0
print("TC.tearDownClassB", cls)
def test_0(self):
print("hello 0")
def test_1(self):
print("hello 1")
self.assertTrue(0)
def test_2(self):
print("hello 2")
2 / 0
def test_3(self):
print("hello 3")
with self.subTest():
3 / 0 # subTest
print("hello 3B")
def tearDownModule():
print("testdbg.tearDownModule")
##TDM / 0
x = unittest.case._AutoDelRunner(lambda: print("Del X"))
def clear_sysltb():
print("clear_sysltb", sys.last_traceback, sys.last_value)
sys.last_traceback = sys.last_value = sys.last_type = None
##gc.collect()
print("clear_sysltb_EXIT", sys.last_traceback, sys.last_value)
if __name__ == "__main__":
import atexit
atexit.register(clear_sysltb)
sys.stderr = sys.stdout # to work synchronously w/o '-u' in piped run
unittest.main()