summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexTate <0xalextate@gmail.com>2022-12-04 19:37:55 (GMT)
committerGitHub <noreply@github.com>2022-12-04 19:37:55 (GMT)
commit72ec518203c3f3577a5e888b12f10bb49060e6c2 (patch)
tree08f93da53cbb5efaeaefce304798548a97c686fa
parent1012dc1b4367e05b92d67ea6925a39d50dce31b7 (diff)
downloadcpython-72ec518203c3f3577a5e888b12f10bb49060e6c2.zip
cpython-72ec518203c3f3577a5e888b12f10bb49060e6c2.tar.gz
cpython-72ec518203c3f3577a5e888b12f10bb49060e6c2.tar.bz2
gh-98458: unittest: bugfix for infinite loop while handling chained exceptions that contain cycles (#98459)
* Bugfix addressing infinite loop while handling self-referencing chained exception in TestResult._clean_tracebacks() * Bugfix extended to properly handle exception cycles in _clean_tracebacks. The "seen" set follows the approach used in the TracebackException class (thank you @iritkatriel for pointing it out) * adds a test for a single chained exception that holds a self-loop in its __cause__ and __context__ attributes
-rw-r--r--Lib/test/test_unittest/test_result.py56
-rw-r--r--Lib/unittest/result.py4
-rw-r--r--Misc/NEWS.d/next/Library/2022-10-19-18-31-53.gh-issue-98458.vwyq7O.rst1
3 files changed, 60 insertions, 1 deletions
diff --git a/Lib/test/test_unittest/test_result.py b/Lib/test/test_unittest/test_result.py
index e71d114..efd9c90 100644
--- a/Lib/test/test_unittest/test_result.py
+++ b/Lib/test/test_unittest/test_result.py
@@ -275,6 +275,62 @@ class Test_TestResult(unittest.TestCase):
self.assertEqual(len(dropped), 1)
self.assertIn("raise self.failureException(msg)", dropped[0])
+ def test_addFailure_filter_traceback_frames_chained_exception_self_loop(self):
+ class Foo(unittest.TestCase):
+ def test_1(self):
+ pass
+
+ def get_exc_info():
+ try:
+ loop = Exception("Loop")
+ loop.__cause__ = loop
+ loop.__context__ = loop
+ raise loop
+ except:
+ return sys.exc_info()
+
+ exc_info_tuple = get_exc_info()
+
+ test = Foo('test_1')
+ result = unittest.TestResult()
+ result.startTest(test)
+ result.addFailure(test, exc_info_tuple)
+ result.stopTest(test)
+
+ formatted_exc = result.failures[0][1]
+ self.assertEqual(formatted_exc.count("Exception: Loop\n"), 1)
+
+ def test_addFailure_filter_traceback_frames_chained_exception_cycle(self):
+ class Foo(unittest.TestCase):
+ def test_1(self):
+ pass
+
+ def get_exc_info():
+ try:
+ # Create two directionally opposed cycles
+ # __cause__ in one direction, __context__ in the other
+ A, B, C = Exception("A"), Exception("B"), Exception("C")
+ edges = [(C, B), (B, A), (A, C)]
+ for ex1, ex2 in edges:
+ ex1.__cause__ = ex2
+ ex2.__context__ = ex1
+ raise C
+ except:
+ return sys.exc_info()
+
+ exc_info_tuple = get_exc_info()
+
+ test = Foo('test_1')
+ result = unittest.TestResult()
+ result.startTest(test)
+ result.addFailure(test, exc_info_tuple)
+ result.stopTest(test)
+
+ formatted_exc = result.failures[0][1]
+ self.assertEqual(formatted_exc.count("Exception: A\n"), 1)
+ self.assertEqual(formatted_exc.count("Exception: B\n"), 1)
+ self.assertEqual(formatted_exc.count("Exception: C\n"), 1)
+
# "addError(test, err)"
# ...
# "Called when the test case test raises an unexpected exception err
diff --git a/Lib/unittest/result.py b/Lib/unittest/result.py
index 3da7005..5ca4c23 100644
--- a/Lib/unittest/result.py
+++ b/Lib/unittest/result.py
@@ -196,6 +196,7 @@ class TestResult(object):
ret = None
first = True
excs = [(exctype, value, tb)]
+ seen = {id(value)} # Detect loops in chained exceptions.
while excs:
(exctype, value, tb) = excs.pop()
# Skip test runner traceback levels
@@ -214,8 +215,9 @@ class TestResult(object):
if value is not None:
for c in (value.__cause__, value.__context__):
- if c is not None:
+ if c is not None and id(c) not in seen:
excs.append((type(c), c, c.__traceback__))
+ seen.add(id(c))
return ret
def _is_relevant_tb_level(self, tb):
diff --git a/Misc/NEWS.d/next/Library/2022-10-19-18-31-53.gh-issue-98458.vwyq7O.rst b/Misc/NEWS.d/next/Library/2022-10-19-18-31-53.gh-issue-98458.vwyq7O.rst
new file mode 100644
index 0000000..f74195c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-10-19-18-31-53.gh-issue-98458.vwyq7O.rst
@@ -0,0 +1 @@
+Fix infinite loop in unittest when a self-referencing chained exception is raised