From 153d7004c9f2330c97ad492cce9df7ea958fdeb1 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 27 Nov 2023 14:59:28 +0100
Subject: [PATCH 1/4] remove _assert_raises
---
src/trio/_tests/test_testing.py | 14 ------
src/trio/testing/_check_streams.py | 68 +++++++++++++-----------------
2 files changed, 29 insertions(+), 53 deletions(-)
diff --git a/src/trio/_tests/test_testing.py b/src/trio/_tests/test_testing.py
index f6e137c55e..235b4fc1a1 100644
--- a/src/trio/_tests/test_testing.py
+++ b/src/trio/_tests/test_testing.py
@@ -12,7 +12,6 @@
from .._highlevel_generic import StapledStream, aclose_forcefully
from .._highlevel_socket import SocketListener
from ..testing import *
-from ..testing._check_streams import _assert_raises
from ..testing._memory_streams import _UnboundedByteQueue
if TYPE_CHECKING:
@@ -237,19 +236,6 @@ async def child(i: int) -> None:
################################################################
-async def test__assert_raises() -> None:
- with pytest.raises(AssertionError):
- with _assert_raises(RuntimeError):
- 1 + 1 # noqa: B018 # "useless expression"
-
- with pytest.raises(TypeError):
- with _assert_raises(RuntimeError):
- "foo" + 1 # type: ignore[operator] # noqa: B018 # "useless expression"
-
- with _assert_raises(RuntimeError):
- raise RuntimeError
-
-
# This is a private implementation detail, but it's complex enough to be worth
# testing directly
async def test__UnboundeByteQueue() -> None:
diff --git a/src/trio/testing/_check_streams.py b/src/trio/testing/_check_streams.py
index 0b9c904275..b6e4f931c8 100644
--- a/src/trio/testing/_check_streams.py
+++ b/src/trio/testing/_check_streams.py
@@ -2,16 +2,17 @@
from __future__ import annotations
import random
-from contextlib import contextmanager, suppress
+from contextlib import suppress
from typing import TYPE_CHECKING, Awaitable, Callable, Generic, Tuple, TypeVar
+import pytest
+
from .. import CancelScope, _core
from .._abc import AsyncResource, HalfCloseableStream, ReceiveStream, SendStream, Stream
from .._highlevel_generic import aclose_forcefully
from ._checkpoints import assert_checkpoints
if TYPE_CHECKING:
- from collections.abc import Generator
from types import TracebackType
from typing_extensions import ParamSpec, TypeAlias
@@ -42,17 +43,6 @@ async def __aexit__(
await aclose_forcefully(self._second)
-@contextmanager
-def _assert_raises(exc: type[BaseException]) -> Generator[None, None, None]:
- __tracebackhide__ = True
- try:
- yield
- except exc:
- pass
- else:
- raise AssertionError(f"expected exception: {exc}")
-
-
async def check_one_way_stream(
stream_maker: StreamMaker[SendStream, ReceiveStream],
clogged_stream_maker: StreamMaker[SendStream, ReceiveStream] | None,
@@ -121,11 +111,11 @@ async def send_empty_then_y() -> None:
nursery.start_soon(checked_receive_1, b"2")
# max_bytes must be a positive integer
- with _assert_raises(ValueError):
+ with pytest.raises(ValueError):
await r.receive_some(-1)
- with _assert_raises(ValueError):
+ with pytest.raises(ValueError):
await r.receive_some(0)
- with _assert_raises(TypeError):
+ with pytest.raises(TypeError):
await r.receive_some(1.5) # type: ignore[arg-type]
# it can also be missing or None
async with _core.open_nursery() as nursery:
@@ -135,7 +125,7 @@ async def send_empty_then_y() -> None:
nursery.start_soon(do_send_all, b"x")
assert await do_receive_some(None) == b"x"
- with _assert_raises(_core.BusyResourceError):
+ with pytest.raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(do_receive_some, 1)
nursery.start_soon(do_receive_some, 1)
@@ -161,7 +151,7 @@ async def simple_check_wait_send_all_might_not_block(
# closing the r side leads to BrokenResourceError on the s side
# (eventually)
async def expect_broken_stream_on_send() -> None:
- with _assert_raises(_core.BrokenResourceError):
+ with pytest.raises(_core.BrokenResourceError):
while True:
await do_send_all(b"x" * 100)
@@ -170,11 +160,11 @@ async def expect_broken_stream_on_send() -> None:
nursery.start_soon(do_aclose, r)
# once detected, the stream stays broken
- with _assert_raises(_core.BrokenResourceError):
+ with pytest.raises(_core.BrokenResourceError):
await do_send_all(b"x" * 100)
# r closed -> ClosedResourceError on the receive side
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await do_receive_some(4096)
# we can close the same stream repeatedly, it's fine
@@ -185,15 +175,15 @@ async def expect_broken_stream_on_send() -> None:
await do_aclose(s)
# now trying to send raises ClosedResourceError
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await do_send_all(b"x" * 100)
# even if it's an empty send
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await do_send_all(b"")
# ditto for wait_send_all_might_not_block
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
with assert_checkpoints():
await s.wait_send_all_might_not_block()
@@ -224,17 +214,17 @@ async def receive_send_then_close() -> None:
async with _ForceCloseBoth(await stream_maker()) as (s, r):
await aclose_forcefully(r)
- with _assert_raises(_core.BrokenResourceError):
+ with pytest.raises(_core.BrokenResourceError):
while True:
await do_send_all(b"x" * 100)
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await do_receive_some(4096)
async with _ForceCloseBoth(await stream_maker()) as (s, r):
await aclose_forcefully(s)
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await do_send_all(b"123")
# after the sender does a forceful close, the receiver might either
@@ -253,10 +243,10 @@ async def receive_send_then_close() -> None:
scope.cancel()
await s.aclose()
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await do_send_all(b"123")
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await do_receive_some(4096)
# Check that we can still gracefully close a stream after an operation has
@@ -275,7 +265,7 @@ async def expect_cancelled(
*args: ArgsT.args,
**kwargs: ArgsT.kwargs,
) -> None:
- with _assert_raises(_core.Cancelled):
+ with pytest.raises(_core.Cancelled):
await afn(*args, **kwargs)
with _core.CancelScope() as scope:
@@ -292,8 +282,8 @@ async def expect_cancelled(
# receive stream causes it to wake up.
async with _ForceCloseBoth(await stream_maker()) as (s, r):
- async def receive_expecting_closed():
- with _assert_raises(_core.ClosedResourceError):
+ async def receive_expecting_closed() -> None:
+ with pytest.raises(_core.ClosedResourceError):
await r.receive_some(10)
async with _core.open_nursery() as nursery:
@@ -333,7 +323,7 @@ async def receiver() -> None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s, r):
# simultaneous wait_send_all_might_not_block fails
- with _assert_raises(_core.BusyResourceError):
+ with pytest.raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s.wait_send_all_might_not_block)
nursery.start_soon(s.wait_send_all_might_not_block)
@@ -342,7 +332,7 @@ async def receiver() -> None:
# this test might destroy the stream b/c we end up cancelling
# send_all and e.g. SSLStream can't handle that, so we have to
# recreate afterwards)
- with _assert_raises(_core.BusyResourceError):
+ with pytest.raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s.wait_send_all_might_not_block)
nursery.start_soon(s.send_all, b"123")
@@ -350,7 +340,7 @@ async def receiver() -> None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s, r):
# send_all and send_all blocked simultaneously should also raise
# (but again this might destroy the stream)
- with _assert_raises(_core.BusyResourceError):
+ with pytest.raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s.send_all, b"123")
nursery.start_soon(s.send_all, b"123")
@@ -392,13 +382,13 @@ async def close_soon(s: SendStream) -> None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s, r):
async with _core.open_nursery() as nursery:
nursery.start_soon(close_soon, s)
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await s.send_all(b"xyzzy")
async with _ForceCloseBoth(await clogged_stream_maker()) as (s, r):
async with _core.open_nursery() as nursery:
nursery.start_soon(close_soon, s)
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await s.wait_send_all_might_not_block()
@@ -517,7 +507,7 @@ async def expect_x_then_eof(r: HalfCloseableStream) -> None:
nursery.start_soon(expect_x_then_eof, s2)
# now sending is disallowed
- with _assert_raises(_core.ClosedResourceError):
+ with pytest.raises(_core.ClosedResourceError):
await s1.send_all(b"y")
# but we can do send_eof again
@@ -532,7 +522,7 @@ async def expect_x_then_eof(r: HalfCloseableStream) -> None:
if clogged_stream_maker is not None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s1, s2):
# send_all and send_eof simultaneously is not ok
- with _assert_raises(_core.BusyResourceError):
+ with pytest.raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s1.send_all, b"x")
await _core.wait_all_tasks_blocked()
@@ -541,7 +531,7 @@ async def expect_x_then_eof(r: HalfCloseableStream) -> None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s1, s2):
# wait_send_all_might_not_block and send_eof simultaneously is not
# ok either
- with _assert_raises(_core.BusyResourceError):
+ with pytest.raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s1.wait_send_all_might_not_block)
await _core.wait_all_tasks_blocked()
From 639505329137cd50181161c8e2b13097966d1ca1 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 27 Nov 2023 15:39:22 +0100
Subject: [PATCH 2/4] cleanup of various tests
---
src/trio/_core/_tests/test_run.py | 56 ++++++++----------
src/trio/_tests/test_ssl.py | 98 +++++++++++++------------------
src/trio/_tests/test_util.py | 6 +-
3 files changed, 68 insertions(+), 92 deletions(-)
diff --git a/src/trio/_core/_tests/test_run.py b/src/trio/_core/_tests/test_run.py
index 310e9a67e5..70ef0c8221 100644
--- a/src/trio/_core/_tests/test_run.py
+++ b/src/trio/_core/_tests/test_run.py
@@ -178,11 +178,10 @@ async def main() -> None:
nursery.start_soon(looper)
nursery.start_soon(crasher)
- with pytest.raises(ValueError) as excinfo:
+ with pytest.raises(ValueError, match="argh"):
_core.run(main)
assert looper_record == ["cancelled"]
- assert excinfo.value.args == ("argh",)
def test_main_and_task_both_crash() -> None:
@@ -433,7 +432,10 @@ async def test_cancel_scope_multierror_filtering() -> None:
async def crasher() -> NoReturn:
raise KeyError
- try:
+ # This is outside the outer scope, so all the Cancelled
+ # exceptions should have been absorbed, leaving just a regular
+ # KeyError from crasher()
+ with pytest.raises(KeyError):
with _core.CancelScope() as outer:
try:
async with _core.open_nursery() as nursery:
@@ -461,15 +463,8 @@ async def crasher() -> NoReturn:
summary[type(exc)] += 1
assert summary == {_core.Cancelled: 3, KeyError: 1}
raise
- except AssertionError: # pragma: no cover
- raise
- except BaseException as exc:
- # This is outside the outer scope, so all the Cancelled
- # exceptions should have been absorbed, leaving just a regular
- # KeyError from crasher()
- assert type(exc) is KeyError
- else: # pragma: no cover
- raise AssertionError()
+ else:
+ raise AssertionError("No ExceptionGroup")
async def test_precancelled_task() -> None:
@@ -785,9 +780,10 @@ async def task2() -> None:
await wait_all_tasks_blocked()
nursery.cancel_scope.__exit__(None, None, None)
finally:
- with pytest.raises(RuntimeError) as exc_info:
+ with pytest.raises(
+ RuntimeError, match="which had already been exited"
+ ) as exc_info:
await nursery_mgr.__aexit__(*sys.exc_info())
- assert "which had already been exited" in str(exc_info.value)
assert type(exc_info.value.__context__) is NonBaseMultiError
assert len(exc_info.value.__context__.exceptions) == 3
cancelled_in_context = False
@@ -1606,10 +1602,9 @@ async def child_xyzzy() -> None:
async def misguided() -> None:
await child_xyzzy()
- with pytest.raises(TypeError) as excinfo:
+ with pytest.raises(TypeError, match="asyncio") as excinfo:
_core.run(misguided)
- assert "asyncio" in str(excinfo.value)
# The traceback should point to the location of the foreign await
assert any( # pragma: no branch
entry.name == "child_xyzzy" for entry in excinfo.traceback
@@ -1618,11 +1613,10 @@ async def misguided() -> None:
async def test_asyncio_function_inside_nursery_does_not_explode() -> None:
# Regression test for https://github.com/python-trio/trio/issues/552
- with pytest.raises(TypeError) as excinfo:
+ with pytest.raises(TypeError, match="asyncio"):
async with _core.open_nursery() as nursery:
nursery.start_soon(sleep_forever)
await create_asyncio_future_in_new_loop()
- assert "asyncio" in str(excinfo.value)
async def test_trivial_yields() -> None:
@@ -1890,12 +1884,11 @@ async def test_nursery_stop_iteration() -> None:
async def fail() -> NoReturn:
raise ValueError
- try:
+ with pytest.raises(ExceptionGroup) as excinfo:
async with _core.open_nursery() as nursery:
nursery.start_soon(fail)
raise StopIteration
- except MultiError as e:
- assert tuple(map(type, e.exceptions)) == (StopIteration, ValueError)
+ assert tuple(map(type, excinfo.value.exceptions)) == (StopIteration, ValueError)
async def test_nursery_stop_async_iteration() -> None:
@@ -1944,7 +1937,7 @@ async def test_traceback_frame_removal() -> None:
async def my_child_task() -> NoReturn:
raise KeyError()
- try:
+ with pytest.raises(ExceptionGroup) as excinfo:
# Trick: For now cancel/nursery scopes still leave a bunch of tb gunk
# behind. But if there's a MultiError, they leave it on the MultiError,
# which lets us get a clean look at the KeyError itself. Someday I
@@ -1953,16 +1946,15 @@ async def my_child_task() -> NoReturn:
async with _core.open_nursery() as nursery:
nursery.start_soon(my_child_task)
nursery.start_soon(my_child_task)
- except MultiError as exc:
- first_exc = exc.exceptions[0]
- assert isinstance(first_exc, KeyError)
- # The top frame in the exception traceback should be inside the child
- # task, not trio/contextvars internals. And there's only one frame
- # inside the child task, so this will also detect if our frame-removal
- # is too eager.
- tb = first_exc.__traceback__
- assert tb is not None
- assert tb.tb_frame.f_code is my_child_task.__code__
+ first_exc = excinfo.value.exceptions[0]
+ assert isinstance(first_exc, KeyError)
+ # The top frame in the exception traceback should be inside the child
+ # task, not trio/contextvars internals. And there's only one frame
+ # inside the child task, so this will also detect if our frame-removal
+ # is too eager.
+ tb = first_exc.__traceback__
+ assert tb is not None
+ assert tb.tb_frame.f_code is my_child_task.__code__
def test_contextvar_support() -> None:
diff --git a/src/trio/_tests/test_ssl.py b/src/trio/_tests/test_ssl.py
index 94e0356f06..27f93f0cd2 100644
--- a/src/trio/_tests/test_ssl.py
+++ b/src/trio/_tests/test_ssl.py
@@ -8,7 +8,15 @@
from contextlib import asynccontextmanager, contextmanager, suppress
from functools import partial
from ssl import SSLContext
-from typing import TYPE_CHECKING, Any, AsyncIterator, Iterator, NoReturn
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncIterator,
+ Awaitable,
+ Callable,
+ Iterator,
+ NoReturn,
+)
import pytest
@@ -344,33 +352,21 @@ async def test_PyOpenSSLEchoStream_gives_resource_busy_errors() -> None:
# PyOpenSSLEchoStream, so this makes sure that if we do have a bug then
# PyOpenSSLEchoStream will notice and complain.
- s = PyOpenSSLEchoStream()
- with pytest.raises(_core.BusyResourceError) as excinfo:
- async with _core.open_nursery() as nursery:
- nursery.start_soon(s.send_all, b"x")
- nursery.start_soon(s.send_all, b"x")
- assert "simultaneous" in str(excinfo.value)
-
- s = PyOpenSSLEchoStream()
- with pytest.raises(_core.BusyResourceError) as excinfo:
- async with _core.open_nursery() as nursery:
- nursery.start_soon(s.send_all, b"x")
- nursery.start_soon(s.wait_send_all_might_not_block)
- assert "simultaneous" in str(excinfo.value)
-
- s = PyOpenSSLEchoStream()
- with pytest.raises(_core.BusyResourceError) as excinfo:
- async with _core.open_nursery() as nursery:
- nursery.start_soon(s.wait_send_all_might_not_block)
- nursery.start_soon(s.wait_send_all_might_not_block)
- assert "simultaneous" in str(excinfo.value)
+ async def do_test(
+ func1: str, args1: tuple[object, ...], func2: str, args2: tuple[object, ...]
+ ) -> None:
+ s = PyOpenSSLEchoStream()
+ with pytest.raises(_core.BusyResourceError, match="simultaneous"):
+ async with _core.open_nursery() as nursery:
+ nursery.start_soon(getattr(s, func1), *args1)
+ nursery.start_soon(getattr(s, func2), *args2)
- s = PyOpenSSLEchoStream()
- with pytest.raises(_core.BusyResourceError) as excinfo:
- async with _core.open_nursery() as nursery:
- nursery.start_soon(s.receive_some, 1)
- nursery.start_soon(s.receive_some, 1)
- assert "simultaneous" in str(excinfo.value)
+ await do_test("send_all", (b"x",), "send_all", (b"x",))
+ await do_test("send_all", (b"x",), "wait_send_all_might_not_block", ())
+ await do_test(
+ "wait_send_all_might_not_block", (), "wait_send_all_might_not_block", ()
+ )
+ await do_test("receive_some", (1,), "receive_some", (1,))
@contextmanager # type: ignore[misc] # decorated contains Any
@@ -727,45 +723,35 @@ async def sleeper_with_slow_wait_writable_and_expect(method: str) -> None:
async def test_resource_busy_errors(client_ctx: SSLContext) -> None:
- async def do_send_all() -> None:
+ S: TypeAlias = trio.SSLStream[
+ trio.StapledStream[trio.abc.SendStream, trio.abc.ReceiveStream]
+ ]
+
+ async def do_send_all(s: S) -> None:
with assert_checkpoints():
await s.send_all(b"x")
- async def do_receive_some() -> None:
+ async def do_receive_some(s: S) -> None:
with assert_checkpoints():
await s.receive_some(1)
- async def do_wait_send_all_might_not_block() -> None:
+ async def do_wait_send_all_might_not_block(s: S) -> None:
with assert_checkpoints():
await s.wait_send_all_might_not_block()
- s, _ = ssl_lockstep_stream_pair(client_ctx)
- with pytest.raises(_core.BusyResourceError) as excinfo:
- async with _core.open_nursery() as nursery:
- nursery.start_soon(do_send_all)
- nursery.start_soon(do_send_all)
- assert "another task" in str(excinfo.value)
-
- s, _ = ssl_lockstep_stream_pair(client_ctx)
- with pytest.raises(_core.BusyResourceError) as excinfo:
- async with _core.open_nursery() as nursery:
- nursery.start_soon(do_receive_some)
- nursery.start_soon(do_receive_some)
- assert "another task" in str(excinfo.value)
-
- s, _ = ssl_lockstep_stream_pair(client_ctx)
- with pytest.raises(_core.BusyResourceError) as excinfo:
- async with _core.open_nursery() as nursery:
- nursery.start_soon(do_send_all)
- nursery.start_soon(do_wait_send_all_might_not_block)
- assert "another task" in str(excinfo.value)
+ async def do_test(
+ func1: Callable[[S], Awaitable[None]], func2: Callable[[S], Awaitable[None]]
+ ) -> None:
+ s, _ = ssl_lockstep_stream_pair(client_ctx)
+ with pytest.raises(_core.BusyResourceError, match="another task"):
+ async with _core.open_nursery() as nursery:
+ nursery.start_soon(func1, s)
+ nursery.start_soon(func2, s)
- s, _ = ssl_lockstep_stream_pair(client_ctx)
- with pytest.raises(_core.BusyResourceError) as excinfo:
- async with _core.open_nursery() as nursery:
- nursery.start_soon(do_wait_send_all_might_not_block)
- nursery.start_soon(do_wait_send_all_might_not_block)
- assert "another task" in str(excinfo.value)
+ await do_test(do_send_all, do_send_all)
+ await do_test(do_receive_some, do_receive_some)
+ await do_test(do_send_all, do_wait_send_all_might_not_block)
+ await do_test(do_wait_send_all_might_not_block, do_wait_send_all_might_not_block)
async def test_wait_writable_calls_underlying_wait_writable() -> None:
diff --git a/src/trio/_tests/test_util.py b/src/trio/_tests/test_util.py
index 40c2fd11bb..7c2cc95d61 100644
--- a/src/trio/_tests/test_util.py
+++ b/src/trio/_tests/test_util.py
@@ -49,21 +49,19 @@ async def test_ConflictDetector() -> None:
with ul2:
print("ok")
- with pytest.raises(_core.BusyResourceError) as excinfo:
+ with pytest.raises(_core.BusyResourceError, match="ul1"):
with ul1:
with ul1:
pass # pragma: no cover
- assert "ul1" in str(excinfo.value)
async def wait_with_ul1() -> None:
with ul1:
await wait_all_tasks_blocked()
- with pytest.raises(_core.BusyResourceError) as excinfo:
+ with pytest.raises(_core.BusyResourceError, match="ul1"):
async with _core.open_nursery() as nursery:
nursery.start_soon(wait_with_ul1)
nursery.start_soon(wait_with_ul1)
- assert "ul1" in str(excinfo.value)
def test_module_metadata_is_fixed_up() -> None:
From 72466cbca06fdb1e444322911d66b192cf484811 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 28 Nov 2023 15:09:37 +0100
Subject: [PATCH 3/4] Revert "remove _assert_raises"
This reverts commit 153d7004c9f2330c97ad492cce9df7ea958fdeb1.
---
src/trio/_tests/test_testing.py | 14 ++++++
src/trio/testing/_check_streams.py | 68 +++++++++++++++++-------------
2 files changed, 53 insertions(+), 29 deletions(-)
diff --git a/src/trio/_tests/test_testing.py b/src/trio/_tests/test_testing.py
index 235b4fc1a1..f6e137c55e 100644
--- a/src/trio/_tests/test_testing.py
+++ b/src/trio/_tests/test_testing.py
@@ -12,6 +12,7 @@
from .._highlevel_generic import StapledStream, aclose_forcefully
from .._highlevel_socket import SocketListener
from ..testing import *
+from ..testing._check_streams import _assert_raises
from ..testing._memory_streams import _UnboundedByteQueue
if TYPE_CHECKING:
@@ -236,6 +237,19 @@ async def child(i: int) -> None:
################################################################
+async def test__assert_raises() -> None:
+ with pytest.raises(AssertionError):
+ with _assert_raises(RuntimeError):
+ 1 + 1 # noqa: B018 # "useless expression"
+
+ with pytest.raises(TypeError):
+ with _assert_raises(RuntimeError):
+ "foo" + 1 # type: ignore[operator] # noqa: B018 # "useless expression"
+
+ with _assert_raises(RuntimeError):
+ raise RuntimeError
+
+
# This is a private implementation detail, but it's complex enough to be worth
# testing directly
async def test__UnboundeByteQueue() -> None:
diff --git a/src/trio/testing/_check_streams.py b/src/trio/testing/_check_streams.py
index b6e4f931c8..0b9c904275 100644
--- a/src/trio/testing/_check_streams.py
+++ b/src/trio/testing/_check_streams.py
@@ -2,17 +2,16 @@
from __future__ import annotations
import random
-from contextlib import suppress
+from contextlib import contextmanager, suppress
from typing import TYPE_CHECKING, Awaitable, Callable, Generic, Tuple, TypeVar
-import pytest
-
from .. import CancelScope, _core
from .._abc import AsyncResource, HalfCloseableStream, ReceiveStream, SendStream, Stream
from .._highlevel_generic import aclose_forcefully
from ._checkpoints import assert_checkpoints
if TYPE_CHECKING:
+ from collections.abc import Generator
from types import TracebackType
from typing_extensions import ParamSpec, TypeAlias
@@ -43,6 +42,17 @@ async def __aexit__(
await aclose_forcefully(self._second)
+@contextmanager
+def _assert_raises(exc: type[BaseException]) -> Generator[None, None, None]:
+ __tracebackhide__ = True
+ try:
+ yield
+ except exc:
+ pass
+ else:
+ raise AssertionError(f"expected exception: {exc}")
+
+
async def check_one_way_stream(
stream_maker: StreamMaker[SendStream, ReceiveStream],
clogged_stream_maker: StreamMaker[SendStream, ReceiveStream] | None,
@@ -111,11 +121,11 @@ async def send_empty_then_y() -> None:
nursery.start_soon(checked_receive_1, b"2")
# max_bytes must be a positive integer
- with pytest.raises(ValueError):
+ with _assert_raises(ValueError):
await r.receive_some(-1)
- with pytest.raises(ValueError):
+ with _assert_raises(ValueError):
await r.receive_some(0)
- with pytest.raises(TypeError):
+ with _assert_raises(TypeError):
await r.receive_some(1.5) # type: ignore[arg-type]
# it can also be missing or None
async with _core.open_nursery() as nursery:
@@ -125,7 +135,7 @@ async def send_empty_then_y() -> None:
nursery.start_soon(do_send_all, b"x")
assert await do_receive_some(None) == b"x"
- with pytest.raises(_core.BusyResourceError):
+ with _assert_raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(do_receive_some, 1)
nursery.start_soon(do_receive_some, 1)
@@ -151,7 +161,7 @@ async def simple_check_wait_send_all_might_not_block(
# closing the r side leads to BrokenResourceError on the s side
# (eventually)
async def expect_broken_stream_on_send() -> None:
- with pytest.raises(_core.BrokenResourceError):
+ with _assert_raises(_core.BrokenResourceError):
while True:
await do_send_all(b"x" * 100)
@@ -160,11 +170,11 @@ async def expect_broken_stream_on_send() -> None:
nursery.start_soon(do_aclose, r)
# once detected, the stream stays broken
- with pytest.raises(_core.BrokenResourceError):
+ with _assert_raises(_core.BrokenResourceError):
await do_send_all(b"x" * 100)
# r closed -> ClosedResourceError on the receive side
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await do_receive_some(4096)
# we can close the same stream repeatedly, it's fine
@@ -175,15 +185,15 @@ async def expect_broken_stream_on_send() -> None:
await do_aclose(s)
# now trying to send raises ClosedResourceError
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await do_send_all(b"x" * 100)
# even if it's an empty send
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await do_send_all(b"")
# ditto for wait_send_all_might_not_block
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
with assert_checkpoints():
await s.wait_send_all_might_not_block()
@@ -214,17 +224,17 @@ async def receive_send_then_close() -> None:
async with _ForceCloseBoth(await stream_maker()) as (s, r):
await aclose_forcefully(r)
- with pytest.raises(_core.BrokenResourceError):
+ with _assert_raises(_core.BrokenResourceError):
while True:
await do_send_all(b"x" * 100)
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await do_receive_some(4096)
async with _ForceCloseBoth(await stream_maker()) as (s, r):
await aclose_forcefully(s)
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await do_send_all(b"123")
# after the sender does a forceful close, the receiver might either
@@ -243,10 +253,10 @@ async def receive_send_then_close() -> None:
scope.cancel()
await s.aclose()
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await do_send_all(b"123")
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await do_receive_some(4096)
# Check that we can still gracefully close a stream after an operation has
@@ -265,7 +275,7 @@ async def expect_cancelled(
*args: ArgsT.args,
**kwargs: ArgsT.kwargs,
) -> None:
- with pytest.raises(_core.Cancelled):
+ with _assert_raises(_core.Cancelled):
await afn(*args, **kwargs)
with _core.CancelScope() as scope:
@@ -282,8 +292,8 @@ async def expect_cancelled(
# receive stream causes it to wake up.
async with _ForceCloseBoth(await stream_maker()) as (s, r):
- async def receive_expecting_closed() -> None:
- with pytest.raises(_core.ClosedResourceError):
+ async def receive_expecting_closed():
+ with _assert_raises(_core.ClosedResourceError):
await r.receive_some(10)
async with _core.open_nursery() as nursery:
@@ -323,7 +333,7 @@ async def receiver() -> None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s, r):
# simultaneous wait_send_all_might_not_block fails
- with pytest.raises(_core.BusyResourceError):
+ with _assert_raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s.wait_send_all_might_not_block)
nursery.start_soon(s.wait_send_all_might_not_block)
@@ -332,7 +342,7 @@ async def receiver() -> None:
# this test might destroy the stream b/c we end up cancelling
# send_all and e.g. SSLStream can't handle that, so we have to
# recreate afterwards)
- with pytest.raises(_core.BusyResourceError):
+ with _assert_raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s.wait_send_all_might_not_block)
nursery.start_soon(s.send_all, b"123")
@@ -340,7 +350,7 @@ async def receiver() -> None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s, r):
# send_all and send_all blocked simultaneously should also raise
# (but again this might destroy the stream)
- with pytest.raises(_core.BusyResourceError):
+ with _assert_raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s.send_all, b"123")
nursery.start_soon(s.send_all, b"123")
@@ -382,13 +392,13 @@ async def close_soon(s: SendStream) -> None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s, r):
async with _core.open_nursery() as nursery:
nursery.start_soon(close_soon, s)
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await s.send_all(b"xyzzy")
async with _ForceCloseBoth(await clogged_stream_maker()) as (s, r):
async with _core.open_nursery() as nursery:
nursery.start_soon(close_soon, s)
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await s.wait_send_all_might_not_block()
@@ -507,7 +517,7 @@ async def expect_x_then_eof(r: HalfCloseableStream) -> None:
nursery.start_soon(expect_x_then_eof, s2)
# now sending is disallowed
- with pytest.raises(_core.ClosedResourceError):
+ with _assert_raises(_core.ClosedResourceError):
await s1.send_all(b"y")
# but we can do send_eof again
@@ -522,7 +532,7 @@ async def expect_x_then_eof(r: HalfCloseableStream) -> None:
if clogged_stream_maker is not None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s1, s2):
# send_all and send_eof simultaneously is not ok
- with pytest.raises(_core.BusyResourceError):
+ with _assert_raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s1.send_all, b"x")
await _core.wait_all_tasks_blocked()
@@ -531,7 +541,7 @@ async def expect_x_then_eof(r: HalfCloseableStream) -> None:
async with _ForceCloseBoth(await clogged_stream_maker()) as (s1, s2):
# wait_send_all_might_not_block and send_eof simultaneously is not
# ok either
- with pytest.raises(_core.BusyResourceError):
+ with _assert_raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(s1.wait_send_all_might_not_block)
await _core.wait_all_tasks_blocked()
From b9acf48b8676492cb42fafc67fbdc3f19ca7fc02 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 28 Nov 2023 15:11:03 +0100
Subject: [PATCH 4/4] add comment on why _assert_raises is used
---
src/trio/testing/_check_streams.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/trio/testing/_check_streams.py b/src/trio/testing/_check_streams.py
index 0b9c904275..d50e8d864d 100644
--- a/src/trio/testing/_check_streams.py
+++ b/src/trio/testing/_check_streams.py
@@ -42,6 +42,8 @@ async def __aexit__(
await aclose_forcefully(self._second)
+# This is used in this file instead of pytest.raises in order to avoid a dependency
+# on pytest, as the check_* functions are publicly exported.
@contextmanager
def _assert_raises(exc: type[BaseException]) -> Generator[None, None, None]:
__tracebackhide__ = True