aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--Lib/test/libregrtest/setup.py4
-rw-r--r--Lib/test/libregrtest/utils.py33
-rw-r--r--Lib/test/test_regrtest.py33
-rw-r--r--Lib/test/test_socketserver.py7
-rw-r--r--Lib/test/test_threading.py13
-rw-r--r--Misc/NEWS.d/next/Tests/2021-04-14-13-22-44.bpo-43843.ruIQKD.rst5
6 files changed, 91 insertions, 4 deletions
diff --git a/Lib/test/libregrtest/setup.py b/Lib/test/libregrtest/setup.py
index 715d4b96cf9..83ce2f73f4e 100644
--- a/Lib/test/libregrtest/setup.py
+++ b/Lib/test/libregrtest/setup.py
@@ -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
diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py
index 13efdb45818..89d7e7e5354 100644
--- a/Lib/test/libregrtest/utils.py
+++ b/Lib/test/libregrtest/utils.py
@@ -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():
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
index 38321e04b54..054776ccf48 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -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)
diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py
index 954e0331352..211321f3761 100644
--- a/Lib/test/test_socketserver.py
+++ b/Lib/test/test_socketserver.py
@@ -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):
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 49a4af8365a..f44f17f2978 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -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'),
diff --git a/Misc/NEWS.d/next/Tests/2021-04-14-13-22-44.bpo-43843.ruIQKD.rst b/Misc/NEWS.d/next/Tests/2021-04-14-13-22-44.bpo-43843.ruIQKD.rst
new file mode 100644
index 00000000000..d1085ec2395
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2021-04-14-13-22-44.bpo-43843.ruIQKD.rst
@@ -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.