diff options
author | Julien Danjou <julien@danjou.info> | 2020-11-02 15:16:25 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-02 16:16:25 +0200 |
commit | 64366fa9b3ba71b8a503a8719eff433f4ea49eb9 (patch) | |
tree | 8aa6d76484553d2f29bfe5d03709a86c0abe547f /Lib | |
parent | bpo-42230: Improve asyncio documentation regarding accepting sets vs iterable... (diff) | |
download | cpython-64366fa9b3ba71b8a503a8719eff433f4ea49eb9.tar.gz cpython-64366fa9b3ba71b8a503a8719eff433f4ea49eb9.tar.bz2 cpython-64366fa9b3ba71b8a503a8719eff433f4ea49eb9.zip |
bpo-41435: Add sys._current_exceptions() function (GH-21689)
This adds a new function named sys._current_exceptions() which is equivalent ot
sys._current_frames() except that it returns the exceptions currently handled
by other threads. It is equivalent to calling sys.exc_info() for each running
thread.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_sys.py | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 30c29a26a99..332ed8f550c 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -432,6 +432,73 @@ class SysModuleTest(unittest.TestCase): leave_g.set() t.join() + @threading_helper.reap_threads + def test_current_exceptions(self): + import threading + import traceback + + # Spawn a thread that blocks at a known place. Then the main + # thread does sys._current_frames(), and verifies that the frames + # returned make sense. + entered_g = threading.Event() + leave_g = threading.Event() + thread_info = [] # the thread's id + + def f123(): + g456() + + def g456(): + thread_info.append(threading.get_ident()) + entered_g.set() + while True: + try: + raise ValueError("oops") + except ValueError: + if leave_g.wait(timeout=support.LONG_TIMEOUT): + break + + t = threading.Thread(target=f123) + t.start() + entered_g.wait() + + # At this point, t has finished its entered_g.set(), although it's + # impossible to guess whether it's still on that line or has moved on + # to its leave_g.wait(). + self.assertEqual(len(thread_info), 1) + thread_id = thread_info[0] + + d = sys._current_exceptions() + for tid in d: + self.assertIsInstance(tid, int) + self.assertGreater(tid, 0) + + main_id = threading.get_ident() + self.assertIn(main_id, d) + self.assertIn(thread_id, d) + self.assertEqual((None, None, None), d.pop(main_id)) + + # Verify that the captured thread frame is blocked in g456, called + # from f123. This is a litte tricky, since various bits of + # threading.py are also in the thread's call stack. + exc_type, exc_value, exc_tb = d.pop(thread_id) + stack = traceback.extract_stack(exc_tb.tb_frame) + for i, (filename, lineno, funcname, sourceline) in enumerate(stack): + if funcname == "f123": + break + else: + self.fail("didn't find f123() on thread's call stack") + + self.assertEqual(sourceline, "g456()") + + # And the next record must be for g456(). + filename, lineno, funcname, sourceline = stack[i+1] + self.assertEqual(funcname, "g456") + self.assertTrue(sourceline.startswith("if leave_g.wait(")) + + # Reap the spawned thread. + leave_g.set() + t.join() + def test_attributes(self): self.assertIsInstance(sys.api_version, int) self.assertIsInstance(sys.argv, list) |