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