Skip to content

Commit 3188548

Browse files
authored
Merge pull request #1898 from graingert/reenable-unraisable-thread-except-hooks
reenable pytest -p unraisableexception and -p threadexception
2 parents b845203 + cb75b22 commit 3188548

File tree

9 files changed

+100
-42
lines changed

9 files changed

+100
-42
lines changed

ci.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ else
143143
cd empty
144144

145145
INSTALLDIR=$(python -c "import os, trio; print(os.path.dirname(trio.__file__))")
146-
cp ../setup.cfg $INSTALLDIR
146+
cp ../pyproject.toml $INSTALLDIR
147147
# We have to copy .coveragerc into this directory, rather than passing
148148
# --cov-config=../.coveragerc to pytest, because codecov.sh will run
149149
# 'coverage xml' to generate the report that it uses, and that will only

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,10 @@ directory = "misc"
5050
name = "Miscellaneous internal changes"
5151
showcontent = true
5252

53+
[tool.pytest.ini_options]
54+
addopts = ["--strict-markers", "--strict-config"]
55+
xfail_strict = true
56+
faulthandler_timeout = 60
57+
markers = ["redistributors_should_skip: tests that should be skipped by downstream redistributors"]
58+
junit_family = "xunit2"
59+
filterwarnings = ["error"]

setup.cfg

Lines changed: 0 additions & 9 deletions
This file was deleted.

trio/_core/tests/test_asyncgen.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from functools import partial
66
from async_generator import aclosing
77
from ... import _core
8-
from .tutil import gc_collect_harder, buggy_pypy_asyncgens
8+
from .tutil import gc_collect_harder, buggy_pypy_asyncgens, restore_unraisablehook
99

1010

1111
def test_asyncgen_basics():
@@ -94,8 +94,9 @@ async def agen():
9494
record.append("crashing")
9595
raise ValueError("oops")
9696

97-
await agen().asend(None)
98-
gc_collect_harder()
97+
with restore_unraisablehook():
98+
await agen().asend(None)
99+
gc_collect_harder()
99100
await _core.wait_all_tasks_blocked()
100101
assert record == ["crashing"]
101102
exc_type, exc_value, exc_traceback = caplog.records[0].exc_info
@@ -170,6 +171,7 @@ async def async_main():
170171
assert record == ["innermost"] + list(range(100))
171172

172173

174+
@restore_unraisablehook()
173175
def test_last_minute_gc_edge_case():
174176
saved = []
175177
record = []
@@ -267,17 +269,18 @@ async def awaits_after_yield():
267269
yield 42
268270
await _core.cancel_shielded_checkpoint()
269271

270-
await step_outside_async_context(well_behaved())
271-
gc_collect_harder()
272-
assert capsys.readouterr().err == ""
272+
with restore_unraisablehook():
273+
await step_outside_async_context(well_behaved())
274+
gc_collect_harder()
275+
assert capsys.readouterr().err == ""
273276

274-
await step_outside_async_context(yields_after_yield())
275-
gc_collect_harder()
276-
assert "ignored GeneratorExit" in capsys.readouterr().err
277+
await step_outside_async_context(yields_after_yield())
278+
gc_collect_harder()
279+
assert "ignored GeneratorExit" in capsys.readouterr().err
277280

278-
await step_outside_async_context(awaits_after_yield())
279-
gc_collect_harder()
280-
assert "awaited something during finalization" in capsys.readouterr().err
281+
await step_outside_async_context(awaits_after_yield())
282+
gc_collect_harder()
283+
assert "awaited something during finalization" in capsys.readouterr().err
281284

282285

283286
@pytest.mark.skipif(buggy_pypy_asyncgens, reason="pypy 7.2.0 is buggy")
@@ -307,10 +310,11 @@ async def async_main():
307310
await _core.wait_all_tasks_blocked()
308311
assert record == ["trio collected ours"]
309312

310-
old_hooks = sys.get_asyncgen_hooks()
311-
sys.set_asyncgen_hooks(my_firstiter, my_finalizer)
312-
try:
313-
_core.run(async_main)
314-
finally:
315-
assert sys.get_asyncgen_hooks() == (my_firstiter, my_finalizer)
316-
sys.set_asyncgen_hooks(*old_hooks)
313+
with restore_unraisablehook():
314+
old_hooks = sys.get_asyncgen_hooks()
315+
sys.set_asyncgen_hooks(my_firstiter, my_finalizer)
316+
try:
317+
_core.run(async_main)
318+
finally:
319+
assert sys.get_asyncgen_hooks() == (my_firstiter, my_finalizer)
320+
sys.set_asyncgen_hooks(*old_hooks)

trio/_core/tests/test_guest_mode.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import trio
1515
import trio.testing
16-
from .tutil import gc_collect_harder, buggy_pypy_asyncgens
16+
from .tutil import gc_collect_harder, buggy_pypy_asyncgens, restore_unraisablehook
1717
from ..._util import signal_raise
1818

1919
# The simplest possible "host" loop.
@@ -277,6 +277,7 @@ def after_io_wait(self, timeout):
277277
assert trivial_guest_run(trio_main) == "ok"
278278

279279

280+
@restore_unraisablehook()
280281
def test_guest_warns_if_abandoned():
281282
# This warning is emitted from the garbage collector. So we have to make
282283
# sure that our abandoned run is garbage. The easiest way to do this is to
@@ -506,6 +507,7 @@ async def trio_main(in_host):
506507
sys.implementation.name == "pypy" and sys.version_info >= (3, 7),
507508
reason="async generator issue under investigation",
508509
)
510+
@restore_unraisablehook()
509511
def test_guest_mode_asyncgens():
510512
import sniffio
511513

trio/_core/tests/test_run.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
gc_collect_harder,
2424
ignore_coroutine_never_awaited_warnings,
2525
buggy_pypy_asyncgens,
26+
restore_unraisablehook,
2627
)
2728

2829
from ... import _core
@@ -844,6 +845,7 @@ async def stubborn_sleeper():
844845
assert record == ["sleep", "woke", "cancelled"]
845846

846847

848+
@restore_unraisablehook()
847849
def test_broken_abort():
848850
async def main():
849851
# These yields are here to work around an annoying warning -- we're
@@ -870,6 +872,7 @@ async def main():
870872
gc_collect_harder()
871873

872874

875+
@restore_unraisablehook()
873876
def test_error_in_run_loop():
874877
# Blow stuff up real good to check we at least get a TrioInternalError
875878
async def main():
@@ -2154,6 +2157,7 @@ def abort_fn(_):
21542157
assert abort_fn_called
21552158

21562159

2160+
@restore_unraisablehook()
21572161
def test_async_function_implemented_in_C():
21582162
# These used to crash because we'd try to mutate the coroutine object's
21592163
# cr_frame, but C functions don't have Python frames.

trio/_core/tests/test_thread_cache.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from queue import Queue
44
import time
55
import sys
6+
from contextlib import contextmanager
67

7-
from .tutil import slow, gc_collect_harder
8+
from .tutil import slow, gc_collect_harder, disable_threading_excepthook
89
from .. import _thread_cache
910
from .._thread_cache import start_thread_soon, ThreadCache
1011

@@ -99,6 +100,17 @@ def test_idle_threads_exit(monkeypatch):
99100
assert not seen_thread.is_alive()
100101

101102

103+
@contextmanager
104+
def _join_started_threads():
105+
before = frozenset(threading.enumerate())
106+
try:
107+
yield
108+
finally:
109+
for thread in threading.enumerate():
110+
if thread not in before:
111+
thread.join()
112+
113+
102114
def test_race_between_idle_exit_and_job_assignment(monkeypatch):
103115
# This is a lock where the first few times you try to acquire it with a
104116
# timeout, it waits until the lock is available and then pretends to time
@@ -138,13 +150,15 @@ def release(self):
138150

139151
monkeypatch.setattr(_thread_cache, "Lock", JankyLock)
140152

141-
tc = ThreadCache()
142-
done = threading.Event()
143-
tc.start_thread_soon(lambda: None, lambda _: done.set())
144-
done.wait()
145-
# Let's kill the thread we started, so it doesn't hang around until the
146-
# test suite finishes. Doesn't really do any harm, but it can be confusing
147-
# to see it in debug output. This is hacky, and leaves our ThreadCache
148-
# object in an inconsistent state... but it doesn't matter, because we're
149-
# not going to use it again anyway.
150-
tc.start_thread_soon(lambda: None, lambda _: sys.exit())
153+
with disable_threading_excepthook(), _join_started_threads():
154+
tc = ThreadCache()
155+
done = threading.Event()
156+
tc.start_thread_soon(lambda: None, lambda _: done.set())
157+
done.wait()
158+
# Let's kill the thread we started, so it doesn't hang around until the
159+
# test suite finishes. Doesn't really do any harm, but it can be confusing
160+
# to see it in debug output. This is hacky, and leaves our ThreadCache
161+
# object in an inconsistent state... but it doesn't matter, because we're
162+
# not going to use it again anyway.
163+
164+
tc.start_thread_soon(lambda: None, lambda _: sys.exit())

trio/_core/tests/test_windows.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# Mark all the tests in this file as being windows-only
99
pytestmark = pytest.mark.skipif(not on_windows, reason="windows only")
1010

11-
from .tutil import slow, gc_collect_harder
11+
from .tutil import slow, gc_collect_harder, restore_unraisablehook
1212
from ... import _core, sleep, move_on_after
1313
from ...testing import wait_all_tasks_blocked
1414

@@ -111,6 +111,7 @@ def pipe_with_overlapped_read():
111111
kernel32.CloseHandle(ffi.cast("HANDLE", write_handle))
112112

113113

114+
@restore_unraisablehook()
114115
def test_forgot_to_register_with_iocp():
115116
with pipe_with_overlapped_read() as (write_fp, read_handle):
116117
with write_fp:

trio/_core/tests/tutil.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Utilities for testing
22
import socket as stdlib_socket
3+
import threading
34
import os
45
import sys
56
from typing import TYPE_CHECKING
@@ -80,6 +81,40 @@ def ignore_coroutine_never_awaited_warnings():
8081
gc_collect_harder()
8182

8283

84+
def _noop(*args, **kwargs):
85+
pass
86+
87+
88+
if sys.version_info >= (3, 8):
89+
90+
@contextmanager
91+
def restore_unraisablehook():
92+
sys.unraisablehook, prev = sys.__unraisablehook__, sys.unraisablehook
93+
try:
94+
yield
95+
finally:
96+
sys.unraisablehook = prev
97+
98+
@contextmanager
99+
def disable_threading_excepthook():
100+
threading.excepthook, prev = _noop, threading.excepthook
101+
try:
102+
yield
103+
finally:
104+
threading.excepthook = prev
105+
106+
107+
else:
108+
109+
@contextmanager
110+
def restore_unraisablehook(): # pragma: no cover
111+
yield
112+
113+
@contextmanager
114+
def disable_threading_excepthook(): # pragma: no cover
115+
yield
116+
117+
83118
# template is like:
84119
# [1, {2.1, 2.2}, 3] -> matches [1, 2.1, 2.2, 3] or [1, 2.2, 2.1, 3]
85120
def check_sequence_matches(seq, template):

0 commit comments

Comments
 (0)