Skip to content

Commit 95f61c8

Browse files
authored
bpo-37069: regrtest uses sys.unraisablehook (GH-13759)
regrtest now uses sys.unraisablehook() to mark a test as "environment altered" (ENV_CHANGED) if it emits an "unraisable exception". Moreover, regrtest logs a warning in this case. Use "python3 -m test --fail-env-changed" to catch unraisable exceptions in tests.
1 parent 913fa1c commit 95f61c8

File tree

4 files changed

+57
-1
lines changed

4 files changed

+57
-1
lines changed

Lib/test/libregrtest/setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
except ImportError:
1111
gc = None
1212

13+
from test.libregrtest.utils import setup_unraisable_hook
14+
1315

1416
def setup_tests(ns):
1517
try:
@@ -93,6 +95,8 @@ def _test_audit_hook(name, args):
9395
pass
9496
sys.addaudithook(_test_audit_hook)
9597

98+
setup_unraisable_hook()
99+
96100

97101
def suppress_msvcrt_asserts(verbose):
98102
try:

Lib/test/libregrtest/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os.path
33
import sys
44
import textwrap
5+
from test import support
56

67

78
def format_duration(seconds):
@@ -59,3 +60,19 @@ def printlist(x, width=70, indent=4, file=None):
5960

6061
def print_warning(msg):
6162
print(f"Warning -- {msg}", file=sys.stderr, flush=True)
63+
64+
65+
orig_unraisablehook = None
66+
67+
68+
def regrtest_unraisable_hook(unraisable):
69+
global orig_unraisablehook
70+
support.environment_altered = True
71+
print_warning("Unraisable exception")
72+
orig_unraisablehook(unraisable)
73+
74+
75+
def setup_unraisable_hook():
76+
global orig_unraisablehook
77+
orig_unraisablehook = sys.unraisablehook
78+
sys.unraisablehook = regrtest_unraisable_hook

Lib/test/test_regrtest.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ def run_command(self, args, input=None, exitcode=0, **kw):
499499
if not input:
500500
input = ''
501501
if 'stderr' not in kw:
502-
kw['stderr'] = subprocess.PIPE
502+
kw['stderr'] = subprocess.STDOUT
503503
proc = subprocess.run(args,
504504
universal_newlines=True,
505505
input=input,
@@ -1124,6 +1124,34 @@ def test_garbage(self):
11241124
env_changed=[testname],
11251125
fail_env_changed=True)
11261126

1127+
def test_unraisable_exc(self):
1128+
# --fail-env-changed must catch unraisable exception
1129+
code = textwrap.dedent(r"""
1130+
import unittest
1131+
import weakref
1132+
1133+
class MyObject:
1134+
pass
1135+
1136+
def weakref_callback(obj):
1137+
raise Exception("weakref callback bug")
1138+
1139+
class Tests(unittest.TestCase):
1140+
def test_unraisable_exc(self):
1141+
obj = MyObject()
1142+
ref = weakref.ref(obj, weakref_callback)
1143+
# call weakref_callback() which logs
1144+
# an unraisable exception
1145+
obj = None
1146+
""")
1147+
testname = self.create_test(code=code)
1148+
1149+
output = self.run_tests("--fail-env-changed", "-v", testname, exitcode=3)
1150+
self.check_executed_tests(output, [testname],
1151+
env_changed=[testname],
1152+
fail_env_changed=True)
1153+
self.assertIn("Warning -- Unraisable exception", output)
1154+
11271155

11281156
class TestUtils(unittest.TestCase):
11291157
def test_format_duration(self):
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
regrtest now uses :func:`sys.unraisablehook` to mark a test as "environment
2+
altered" (ENV_CHANGED) if it emits an "unraisable exception". Moreover,
3+
regrtest logs a warning in this case.
4+
5+
Use ``python3 -m test --fail-env-changed`` to catch unraisable exceptions in
6+
tests.
7+

0 commit comments

Comments
 (0)