mirror of
https://github.com/python/cpython.git
synced 2025-11-25 04:34:37 +00:00
bpo-43843: libregrtest uses threading.excepthook (GH-25400)
test.libregrtest now marks a test as ENV_CHANGED (altered the execution environment) if a thread raises an exception but does not catch it. It sets a hook on threading.excepthook. Use --fail-env-changed option to mark the test as failed. libregrtest regrtest_unraisable_hook() explicitly flushs sys.stdout, sys.stderr and sys.__stderr__.
This commit is contained in:
parent
75ec103b3a
commit
b136b1aac4
6 changed files with 91 additions and 4 deletions
|
|
@ -10,7 +10,8 @@ try:
|
|||
except ImportError:
|
||||
gc = None
|
||||
|
||||
from test.libregrtest.utils import setup_unraisable_hook
|
||||
from test.libregrtest.utils import (setup_unraisable_hook,
|
||||
setup_threading_excepthook)
|
||||
|
||||
|
||||
def setup_tests(ns):
|
||||
|
|
@ -81,6 +82,7 @@ def setup_tests(ns):
|
|||
sys.addaudithook(_test_audit_hook)
|
||||
|
||||
setup_unraisable_hook()
|
||||
setup_threading_excepthook()
|
||||
|
||||
if ns.timeout is not None:
|
||||
# For a slow buildbot worker, increase SHORT_TIMEOUT and LONG_TIMEOUT
|
||||
|
|
|
|||
|
|
@ -68,14 +68,23 @@ def print_warning(msg):
|
|||
orig_unraisablehook = None
|
||||
|
||||
|
||||
def flush_std_streams():
|
||||
if sys.stdout is not None:
|
||||
sys.stdout.flush()
|
||||
if sys.stderr is not None:
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
def regrtest_unraisable_hook(unraisable):
|
||||
global orig_unraisablehook
|
||||
support.environment_altered = True
|
||||
print_warning("Unraisable exception")
|
||||
old_stderr = sys.stderr
|
||||
try:
|
||||
flush_std_streams()
|
||||
sys.stderr = sys.__stderr__
|
||||
orig_unraisablehook(unraisable)
|
||||
sys.stderr.flush()
|
||||
finally:
|
||||
sys.stderr = old_stderr
|
||||
|
||||
|
|
@ -86,6 +95,30 @@ def setup_unraisable_hook():
|
|||
sys.unraisablehook = regrtest_unraisable_hook
|
||||
|
||||
|
||||
orig_threading_excepthook = None
|
||||
|
||||
|
||||
def regrtest_threading_excepthook(args):
|
||||
global orig_threading_excepthook
|
||||
support.environment_altered = True
|
||||
print_warning(f"Uncaught thread exception: {args.exc_type.__name__}")
|
||||
old_stderr = sys.stderr
|
||||
try:
|
||||
flush_std_streams()
|
||||
sys.stderr = sys.__stderr__
|
||||
orig_threading_excepthook(args)
|
||||
sys.stderr.flush()
|
||||
finally:
|
||||
sys.stderr = old_stderr
|
||||
|
||||
|
||||
def setup_threading_excepthook():
|
||||
global orig_threading_excepthook
|
||||
import threading
|
||||
orig_threading_excepthook = threading.excepthook
|
||||
threading.excepthook = regrtest_threading_excepthook
|
||||
|
||||
|
||||
def clear_caches():
|
||||
# Clear the warnings registry, so they can be displayed again
|
||||
for mod in sys.modules.values():
|
||||
|
|
|
|||
|
|
@ -1236,7 +1236,7 @@ class ArgsTestCase(BaseTestCase):
|
|||
|
||||
def test_unraisable_exc(self):
|
||||
# --fail-env-changed must catch unraisable exception.
|
||||
# The exceptioin must be displayed even if sys.stderr is redirected.
|
||||
# The exception must be displayed even if sys.stderr is redirected.
|
||||
code = textwrap.dedent(r"""
|
||||
import unittest
|
||||
import weakref
|
||||
|
|
@ -1267,6 +1267,37 @@ class ArgsTestCase(BaseTestCase):
|
|||
self.assertIn("Warning -- Unraisable exception", output)
|
||||
self.assertIn("Exception: weakref callback bug", output)
|
||||
|
||||
def test_threading_excepthook(self):
|
||||
# --fail-env-changed must catch uncaught thread exception.
|
||||
# The exception must be displayed even if sys.stderr is redirected.
|
||||
code = textwrap.dedent(r"""
|
||||
import threading
|
||||
import unittest
|
||||
from test.support import captured_stderr
|
||||
|
||||
class MyObject:
|
||||
pass
|
||||
|
||||
def func_bug():
|
||||
raise Exception("bug in thread")
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_threading_excepthook(self):
|
||||
with captured_stderr() as stderr:
|
||||
thread = threading.Thread(target=func_bug)
|
||||
thread.start()
|
||||
thread.join()
|
||||
self.assertEqual(stderr.getvalue(), '')
|
||||
""")
|
||||
testname = self.create_test(code=code)
|
||||
|
||||
output = self.run_tests("--fail-env-changed", "-v", testname, exitcode=3)
|
||||
self.check_executed_tests(output, [testname],
|
||||
env_changed=[testname],
|
||||
fail_env_changed=True)
|
||||
self.assertIn("Warning -- Uncaught thread exception", output)
|
||||
self.assertIn("Exception: bug in thread", output)
|
||||
|
||||
def test_cleanup(self):
|
||||
dirname = os.path.join(self.tmptestdir, "test_python_123")
|
||||
os.mkdir(dirname)
|
||||
|
|
|
|||
|
|
@ -323,8 +323,11 @@ class ErrorHandlerTest(unittest.TestCase):
|
|||
self.check_result(handled=True)
|
||||
|
||||
def test_threading_not_handled(self):
|
||||
ThreadingErrorTestServer(SystemExit)
|
||||
self.check_result(handled=False)
|
||||
with threading_helper.catch_threading_exception() as cm:
|
||||
ThreadingErrorTestServer(SystemExit)
|
||||
self.check_result(handled=False)
|
||||
|
||||
self.assertIs(cm.exc_type, SystemExit)
|
||||
|
||||
@requires_forking
|
||||
def test_forking_handled(self):
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ from test import support
|
|||
platforms_to_skip = ('netbsd5', 'hp-ux11')
|
||||
|
||||
|
||||
def restore_default_excepthook(testcase):
|
||||
testcase.addCleanup(setattr, threading, 'excepthook', threading.excepthook)
|
||||
threading.excepthook = threading.__excepthook__
|
||||
|
||||
|
||||
# A trivial mutable counter.
|
||||
class Counter(object):
|
||||
def __init__(self):
|
||||
|
|
@ -427,6 +432,8 @@ class ThreadTests(BaseTestCase):
|
|||
if self.should_raise:
|
||||
raise SystemExit
|
||||
|
||||
restore_default_excepthook(self)
|
||||
|
||||
cyclic_object = RunSelfFunction(should_raise=False)
|
||||
weak_cyclic_object = weakref.ref(cyclic_object)
|
||||
cyclic_object.thread.join()
|
||||
|
|
@ -1331,6 +1338,10 @@ class ThreadRunFail(threading.Thread):
|
|||
|
||||
|
||||
class ExceptHookTests(BaseTestCase):
|
||||
def setUp(self):
|
||||
restore_default_excepthook(self)
|
||||
super().setUp()
|
||||
|
||||
def test_excepthook(self):
|
||||
with support.captured_output("stderr") as stderr:
|
||||
thread = ThreadRunFail(name="excepthook thread")
|
||||
|
|
@ -1501,6 +1512,8 @@ class BarrierTests(lock_tests.BarrierTests):
|
|||
|
||||
class MiscTestCase(unittest.TestCase):
|
||||
def test__all__(self):
|
||||
restore_default_excepthook(self)
|
||||
|
||||
extra = {"ThreadError"}
|
||||
not_exported = {'currentThread', 'activeCount'}
|
||||
support.check__all__(self, threading, ('threading', '_thread'),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
:mod:`test.libregrtest` now marks a test as ENV_CHANGED (altered the execution
|
||||
environment) if a thread raises an exception but does not catch it. It sets a
|
||||
hook on :func:`threading.excepthook`. Use ``--fail-env-changed`` option to mark
|
||||
the test as failed.
|
||||
Patch by Victor Stinner.
|
||||
Loading…
Add table
Add a link
Reference in a new issue