diff --git a/pyproject.toml b/pyproject.toml index b4733d7de9..48c97eccd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,6 +103,7 @@ select = [ "PYI", # flake8-pyi "SIM", # flake8-simplify "TCH", # flake8-type-checking + "PT", # flake8-pytest-style ] extend-ignore = [ 'F403', # undefined-local-with-import-star @@ -131,6 +132,9 @@ extend-exclude = [ [tool.ruff.isort] combine-as-imports = true +[tool.ruff.flake8-pytest-style] +fixture-parentheses = false + [tool.mypy] python_version = "3.8" diff --git a/src/trio/_core/_tests/test_guest_mode.py b/src/trio/_core/_tests/test_guest_mode.py index aa912ab70e..fe1e4bc2ca 100644 --- a/src/trio/_core/_tests/test_guest_mode.py +++ b/src/trio/_core/_tests/test_guest_mode.py @@ -25,7 +25,6 @@ import pytest from outcome import Outcome -from pytest import MonkeyPatch, WarningsRecorder import trio import trio.testing @@ -171,17 +170,16 @@ async def early_task() -> None: assert res == "ok" assert set(record) == {"system task ran", "main task ran", "run_sync_soon cb ran"} + class BadClock: + def start_clock(self) -> NoReturn: + raise ValueError("whoops") + + def after_start_never_runs() -> None: # pragma: no cover + pytest.fail("shouldn't get here") + # Errors during initialization (which can only be TrioInternalErrors) # are raised out of start_guest_run, not out of the done_callback with pytest.raises(trio.TrioInternalError): - - class BadClock: - def start_clock(self) -> NoReturn: - raise ValueError("whoops") - - def after_start_never_runs() -> None: # pragma: no cover - pytest.fail("shouldn't get here") - trivial_guest_run( trio_main, clock=BadClock(), in_host_after_start=after_start_never_runs ) @@ -527,7 +525,7 @@ async def aio_pingpong( def test_guest_mode_internal_errors( - monkeypatch: MonkeyPatch, recwarn: WarningsRecorder + monkeypatch: pytest.MonkeyPatch, recwarn: pytest.WarningsRecorder ) -> None: with monkeypatch.context() as m: diff --git a/src/trio/_core/_tests/test_instrumentation.py b/src/trio/_core/_tests/test_instrumentation.py index b13a852fae..bc61efb519 100644 --- a/src/trio/_core/_tests/test_instrumentation.py +++ b/src/trio/_core/_tests/test_instrumentation.py @@ -255,7 +255,7 @@ def after_run(self) -> NoReturn: raise ValueError("oops") async def main() -> None: - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^oops$"): _core.add_instrument(EvilInstrument()) # Make sure the instrument is fully removed from the per-method lists diff --git a/src/trio/_core/_tests/test_io.py b/src/trio/_core/_tests/test_io.py index 6e88201a11..acecc9d6c6 100644 --- a/src/trio/_core/_tests/test_io.py +++ b/src/trio/_core/_tests/test_io.py @@ -319,7 +319,10 @@ async def test_wait_on_invalid_object() -> None: fileno = s.fileno() # We just closed the socket and don't do anything else in between, so # we can be confident that the fileno hasn't be reassigned. - with pytest.raises(OSError): + with pytest.raises( + OSError, + match=r"^\[\w+ \d+] (Bad file descriptor|An operation was attempted on something that is not a socket)$", + ): await wait(fileno) diff --git a/src/trio/_core/_tests/test_local.py b/src/trio/_core/_tests/test_local.py index 17b10bca35..16f763814e 100644 --- a/src/trio/_core/_tests/test_local.py +++ b/src/trio/_core/_tests/test_local.py @@ -57,13 +57,13 @@ async def reset_check() -> None: t2.reset(token2) assert t2.get() == "dogfish" - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^token has already been used$"): t2.reset(token2) token3 = t3.set("basculin") assert t3.get() == "basculin" - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^token is not for us$"): t1.reset(token3) run(reset_check) diff --git a/src/trio/_core/_tests/test_mock_clock.py b/src/trio/_core/_tests/test_mock_clock.py index 1a0c8b3444..6b0f1ca76b 100644 --- a/src/trio/_core/_tests/test_mock_clock.py +++ b/src/trio/_core/_tests/test_mock_clock.py @@ -20,14 +20,14 @@ def test_mock_clock() -> None: assert c.current_time() == 0 c.jump(1.2) assert c.current_time() == 1.2 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^time can't go backwards$"): c.jump(-1) assert c.current_time() == 1.2 assert c.deadline_to_sleep_time(1.1) == 0 assert c.deadline_to_sleep_time(1.2) == 0 assert c.deadline_to_sleep_time(1.3) > 999999 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^rate must be >= 0$"): c.rate = -1 assert c.rate == 0 diff --git a/src/trio/_core/_tests/test_multierror.py b/src/trio/_core/_tests/test_multierror.py index 505b371f41..f7f5e3d076 100644 --- a/src/trio/_core/_tests/test_multierror.py +++ b/src/trio/_core/_tests/test_multierror.py @@ -136,7 +136,7 @@ async def test_MultiErrorNotHashable() -> None: assert exc1 != exc2 assert exc1 != exc3 - with pytest.raises(MultiError): + with pytest.raises(MultiError): # noqa: PT012 async with open_nursery() as nursery: nursery.start_soon(raise_nothashable, 42) nursery.start_soon(raise_nothashable, 4242) @@ -331,36 +331,35 @@ def simple_filter(exc): assert new_m.__context__ is None # check preservation of __cause__ and __context__ - v = ValueError() + v = ValueError("waffles are great") v.__cause__ = KeyError() - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="^waffles are great$") as excinfo: with pytest.warns(TrioDeprecationWarning), MultiError.catch(lambda exc: exc): raise v assert isinstance(excinfo.value.__cause__, KeyError) - v = ValueError() + v = ValueError("mushroom soup") context = KeyError() v.__context__ = context - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="^mushroom soup$") as excinfo: with pytest.warns(TrioDeprecationWarning), MultiError.catch(lambda exc: exc): raise v assert excinfo.value.__context__ is context assert not excinfo.value.__suppress_context__ for suppress_context in [True, False]: - v = ValueError() + v = ValueError("unique text") context = KeyError() v.__context__ = context v.__suppress_context__ = suppress_context distractor = RuntimeError() - with pytest.raises(ValueError) as excinfo: - def catch_RuntimeError(exc): - if isinstance(exc, RuntimeError): - return None - else: - return exc + def catch_RuntimeError(exc: Exception) -> Exception | None: + if isinstance(exc, RuntimeError): + return None + return exc + with pytest.raises(ValueError, match="^unique text$") as excinfo: # noqa: PT012 with pytest.warns(TrioDeprecationWarning): with MultiError.catch(catch_RuntimeError): raise MultiError([v, distractor]) diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py index 40c55f1f2e..353c1ba45d 100644 --- a/src/trio/_core/_tests/test_parking_lot.py +++ b/src/trio/_core/_tests/test_parking_lot.py @@ -78,7 +78,9 @@ async def waiter(i: int, lot: ParkingLot) -> None: ) lot.unpark_all() - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match=r"^Cannot pop a non-integer number of tasks\.$" + ): lot.unpark(count=1.5) diff --git a/src/trio/_core/_tests/test_run.py b/src/trio/_core/_tests/test_run.py index 212640e78f..23330103f3 100644 --- a/src/trio/_core/_tests/test_run.py +++ b/src/trio/_core/_tests/test_run.py @@ -89,7 +89,7 @@ def test_initial_task_error() -> None: async def main(x: object) -> NoReturn: raise ValueError(x) - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="^17$") as excinfo: _core.run(main, 17) assert excinfo.value.args == (17,) @@ -107,8 +107,8 @@ async def main() -> None: # pragma: no cover async def test_nursery_warn_use_async_with() -> None: + on = _core.open_nursery() with pytest.raises(RuntimeError) as excinfo: - on = _core.open_nursery() with on: # type: ignore pass # pragma: no cover excinfo.match( @@ -123,7 +123,7 @@ async def test_nursery_warn_use_async_with() -> None: async def test_nursery_main_block_error_basic() -> None: exc = ValueError("whoops") - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="^whoops$") as excinfo: async with _core.open_nursery(): raise exc assert excinfo.value is exc @@ -135,12 +135,11 @@ async def test_child_crash_basic() -> None: async def erroring() -> NoReturn: raise exc - try: + with pytest.raises(ValueError, match="^uh oh$") as excinfo: # nursery.__aexit__ propagates exception from child back to parent async with _core.open_nursery() as nursery: nursery.start_soon(erroring) - except ValueError as e: - assert e is exc + assert excinfo.value is exc async def test_basic_interleave() -> None: @@ -178,7 +177,7 @@ async def main() -> None: nursery.start_soon(looper) nursery.start_soon(crasher) - with pytest.raises(ValueError, match="argh"): + with pytest.raises(ValueError, match="^argh$"): _core.run(main) assert looper_record == ["cancelled"] @@ -223,9 +222,9 @@ async def main() -> None: async def test_child_crash_wakes_parent() -> None: async def crasher() -> NoReturn: - raise ValueError + raise ValueError("this is a crash") - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^this is a crash$"): # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(crasher) await sleep_forever() @@ -243,7 +242,7 @@ async def child1() -> None: print("child1 woke") assert x == 0 print("child1 rescheduling t2") - _core.reschedule(not_none(t2), outcome.Error(ValueError())) + _core.reschedule(not_none(t2), outcome.Error(ValueError("error message"))) print("child1 exit") async def child2() -> None: @@ -252,7 +251,7 @@ async def child2() -> None: t2 = _core.current_task() _core.reschedule(not_none(t1), outcome.Value(0)) print("child2 sleep") - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^error message$"): await sleep_forever() print("child2 successful exit") @@ -435,7 +434,7 @@ async def crasher() -> NoReturn: # 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 pytest.raises(KeyError): # noqa: PT012 with _core.CancelScope() as outer: try: async with _core.open_nursery() as nursery: @@ -456,7 +455,8 @@ async def crasher() -> NoReturn: # nursery block exited, all cancellations inside the # nursery block continue propagating to reach the # outer scope. - assert len(multi_exc.exceptions) == 4 + # the noqa is for "Found assertion on exception `multi_exc` in `except` block" + assert len(multi_exc.exceptions) == 4 # noqa: PT017 summary: dict[type, int] = {} for exc in multi_exc.exceptions: summary.setdefault(type(exc), 0) @@ -661,7 +661,8 @@ async def test_unshield_while_cancel_propagating() -> None: await _core.checkpoint() finally: inner.shield = True - assert outer.cancelled_caught and not inner.cancelled_caught + assert outer.cancelled_caught + assert not inner.cancelled_caught async def test_cancel_unbound() -> None: @@ -1003,8 +1004,8 @@ async def main() -> None: async def test_yield_briefly_checks_for_timeout(mock_clock: _core.MockClock) -> None: with _core.CancelScope(deadline=_core.current_time() + 5): await _core.checkpoint() + mock_clock.jump(10) with pytest.raises(_core.Cancelled): - mock_clock.jump(10) await _core.checkpoint() @@ -1019,11 +1020,11 @@ async def test_exc_info() -> None: seq = Sequencer() async def child1() -> None: - with pytest.raises(ValueError) as excinfo: + async with seq(0): + pass # we don't yield until seq(2) below + record.append("child1 raise") + with pytest.raises(ValueError, match="^child1$") as excinfo: # noqa: PT012 try: - async with seq(0): - pass # we don't yield until seq(2) below - record.append("child1 raise") raise ValueError("child1") except ValueError: record.append("child1 sleep") @@ -1036,12 +1037,12 @@ async def child1() -> None: record.append("child1 success") async def child2() -> None: - with pytest.raises(KeyError) as excinfo: - async with seq(1): - pass # we don't yield until seq(3) below - assert "child1 sleep" in record - record.append("child2 wake") - assert sys.exc_info() == (None, None, None) + async with seq(1): + pass # we don't yield until seq(3) below + assert "child1 sleep" in record + record.append("child2 wake") + assert sys.exc_info() == (None, None, None) + with pytest.raises(KeyError) as excinfo: # noqa: PT012 try: raise KeyError("child2") except KeyError: @@ -1096,7 +1097,7 @@ async def child() -> None: await sleep_forever() raise - with pytest.raises(KeyError) as excinfo: + with pytest.raises(KeyError) as excinfo: # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(child) await wait_all_tasks_blocked() @@ -1124,11 +1125,13 @@ async def child() -> None: except KeyError: await sleep_forever() - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="^error text$") as excinfo: # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(child) await wait_all_tasks_blocked() - _core.reschedule(not_none(child_task), outcome.Error(ValueError())) + _core.reschedule( + not_none(child_task), outcome.Error(ValueError("error text")) + ) assert isinstance(excinfo.value.__context__, KeyError) @@ -1156,7 +1159,7 @@ async def inner(exc: BaseException) -> None: await sleep_forever() assert not_none(sys.exc_info()[1]) is exc - with pytest.raises(KeyError) as excinfo: + with pytest.raises(KeyError) as excinfo: # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(child) await wait_all_tasks_blocked() @@ -1188,11 +1191,13 @@ async def inner() -> None: except IndexError: await sleep_forever() - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="^Unique Text$") as excinfo: # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(child) await wait_all_tasks_blocked() - _core.reschedule(not_none(child_task), outcome.Error(ValueError())) + _core.reschedule( + not_none(child_task), outcome.Error(ValueError("Unique Text")) + ) assert isinstance(excinfo.value.__context__, IndexError) assert isinstance(excinfo.value.__context__.__context__, KeyError) @@ -1202,7 +1207,7 @@ async def test_nursery_exception_chaining_doesnt_make_context_loops() -> None: async def crasher() -> NoReturn: raise KeyError - with pytest.raises(MultiError) as excinfo: + with pytest.raises(MultiError) as excinfo: # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(crasher) raise ValueError @@ -1308,9 +1313,9 @@ async def main() -> None: # After main exits but before finally cleaning up, callback processed # normally token.run_sync_soon(lambda: record.append("sync-cb")) - raise ValueError + raise ValueError("error text") - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^error text$"): _core.run(main) assert record == ["sync-cb"] @@ -1455,15 +1460,16 @@ async def main() -> None: async def test_slow_abort_basic() -> None: with _core.CancelScope() as scope: scope.cancel() - with pytest.raises(_core.Cancelled): - task = _core.current_task() - token = _core.current_trio_token() - def slow_abort(raise_cancel: _core.RaiseCancelT) -> _core.Abort: - result = outcome.capture(raise_cancel) - token.run_sync_soon(_core.reschedule, task, result) - return _core.Abort.FAILED + task = _core.current_task() + token = _core.current_trio_token() + + def slow_abort(raise_cancel: _core.RaiseCancelT) -> _core.Abort: + result = outcome.capture(raise_cancel) + token.run_sync_soon(_core.reschedule, task, result) + return _core.Abort.FAILED + with pytest.raises(_core.Cancelled): await _core.wait_task_rescheduled(slow_abort) @@ -1480,8 +1486,8 @@ def slow_abort(raise_cancel: _core.RaiseCancelT) -> _core.Abort: token.run_sync_soon(_core.reschedule, task, result) return _core.Abort.FAILED + record.append("sleeping") with pytest.raises(_core.Cancelled): - record.append("sleeping") await _core.wait_task_rescheduled(slow_abort) record.append("cancelled") # blocking again, this time it's okay, because we're shielded @@ -1663,7 +1669,10 @@ async def main() -> None: async def f() -> None: # pragma: no cover pass - with pytest.raises(TypeError, match="expecting an async function"): + with pytest.raises( + TypeError, + match="^Trio was expecting an async function, but instead it got a coroutine object <.*>", + ): bad_call(f()) # type: ignore[arg-type] async def async_gen(arg: T) -> AsyncGenerator[T, None]: # pragma: no cover @@ -1693,7 +1702,7 @@ 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, match="asyncio"): + with pytest.raises(TypeError, match="asyncio"): # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(sleep_forever) await create_asyncio_future_in_new_loop() @@ -1866,7 +1875,7 @@ async def test_task_nursery_stack() -> None: assert task._child_nurseries == [] async with _core.open_nursery() as nursery1: assert task._child_nurseries == [nursery1] - with pytest.raises(KeyError): + with pytest.raises(KeyError): # noqa: PT012 async with _core.open_nursery() as nursery2: assert task._child_nurseries == [nursery1, nursery2] raise KeyError @@ -1968,7 +1977,7 @@ async def test_nursery_stop_iteration() -> None: async def fail() -> NoReturn: raise ValueError - with pytest.raises(ExceptionGroup) as excinfo: + with pytest.raises(ExceptionGroup) as excinfo: # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(fail) raise StopIteration @@ -2021,7 +2030,7 @@ async def test_traceback_frame_removal() -> None: async def my_child_task() -> NoReturn: raise KeyError() - with pytest.raises(ExceptionGroup) as excinfo: + with pytest.raises(ExceptionGroup) as excinfo: # noqa: PT012 # 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 @@ -2385,7 +2394,7 @@ async def do_a_cancel() -> None: await sleep_forever() async def crasher() -> NoReturn: - raise ValueError + raise ValueError("this is a crash") old_flags = gc.get_debug() try: @@ -2399,7 +2408,7 @@ async def crasher() -> NoReturn: # (See https://github.com/python-trio/trio/pull/1864) await do_a_cancel() - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^this is a crash$"): async with _core.open_nursery() as nursery: # cover NurseryManager.__aexit__ nursery.start_soon(crasher) @@ -2419,11 +2428,13 @@ async def test_cancel_scope_exit_doesnt_create_cyclic_garbage() -> None: gc.collect() async def crasher() -> NoReturn: - raise ValueError + raise ValueError("this is a crash") old_flags = gc.get_debug() try: - with pytest.raises(ValueError), _core.CancelScope() as outer: + with pytest.raises( # noqa: PT012 + ValueError, match="^this is a crash$" + ), _core.CancelScope() as outer: async with _core.open_nursery() as nursery: gc.collect() gc.set_debug(gc.DEBUG_SAVEALL) @@ -2530,7 +2541,7 @@ async def main() -> NoReturn: async with _core.open_nursery(strict_exception_groups=False): raise Exception("foo") - with pytest.raises(Exception, match="foo"): + with pytest.raises(Exception, match="^foo$"): _core.run(main, strict_exception_groups=True) @@ -2554,7 +2565,7 @@ async def test_nursery_collapse_strict() -> None: async def raise_error() -> NoReturn: raise RuntimeError("test error") - with pytest.raises(MultiError) as exc: + with pytest.raises(MultiError) as exc: # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(sleep_forever) nursery.start_soon(raise_error) @@ -2580,7 +2591,7 @@ async def test_nursery_collapse_loose() -> None: async def raise_error() -> NoReturn: raise RuntimeError("test error") - with pytest.raises(MultiError) as exc: + with pytest.raises(MultiError) as exc: # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(sleep_forever) nursery.start_soon(raise_error) @@ -2601,7 +2612,7 @@ async def test_cancel_scope_no_cancellederror() -> None: a Cancelled exception, it will NOT set the ``cancelled_caught`` flag. """ - with pytest.raises(ExceptionGroup): + with pytest.raises(ExceptionGroup): # noqa: PT012 with _core.CancelScope() as scope: scope.cancel() raise ExceptionGroup("test", [RuntimeError(), RuntimeError()]) @@ -2656,7 +2667,7 @@ async def start_raiser() -> None: ) from None raise - with pytest.raises(BaseException) as exc_info: + with pytest.raises(BaseException) as exc_info: # noqa: PT011 # no `match` _core.run(start_raiser, strict_exception_groups=run_strict) if start_raiser_strict or (run_strict and start_raiser_strict is None): diff --git a/src/trio/_core/_tests/test_thread_cache.py b/src/trio/_core/_tests/test_thread_cache.py index a0711b1849..ee301d17fd 100644 --- a/src/trio/_core/_tests/test_thread_cache.py +++ b/src/trio/_core/_tests/test_thread_cache.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Iterator, NoReturn import pytest -from pytest import MonkeyPatch from .. import _thread_cache from .._thread_cache import ThreadCache, start_thread_soon @@ -92,7 +91,7 @@ def deliver(n: int, _: object) -> None: @slow -def test_idle_threads_exit(monkeypatch: MonkeyPatch) -> None: +def test_idle_threads_exit(monkeypatch: pytest.MonkeyPatch) -> None: # Temporarily set the idle timeout to something tiny, to speed up the # test. (But non-zero, so that the worker loop will at least yield the # CPU.) @@ -119,7 +118,9 @@ def _join_started_threads() -> Iterator[None]: assert not thread.is_alive() -def test_race_between_idle_exit_and_job_assignment(monkeypatch: MonkeyPatch) -> None: +def test_race_between_idle_exit_and_job_assignment( + monkeypatch: pytest.MonkeyPatch, +) -> None: # This is a lock where the first few times you try to acquire it with a # timeout, it waits until the lock is available and then pretends to time # out. Using this in our thread cache implementation causes the following diff --git a/src/trio/_core/_tests/test_windows.py b/src/trio/_core/_tests/test_windows.py index 5839f73a5e..f378fd8114 100644 --- a/src/trio/_core/_tests/test_windows.py +++ b/src/trio/_core/_tests/test_windows.py @@ -42,18 +42,20 @@ def test_winerror(monkeypatch: pytest.MonkeyPatch) -> None: # Returning none = no error, should not happen. mock.return_value = None - with pytest.raises(RuntimeError, match="No error set"): + with pytest.raises(RuntimeError, match=r"^No error set\?$"): raise_winerror() mock.assert_called_once_with() mock.reset_mock() - with pytest.raises(RuntimeError, match="No error set"): + with pytest.raises(RuntimeError, match=r"^No error set\?$"): raise_winerror(38) mock.assert_called_once_with(38) mock.reset_mock() mock.return_value = (12, "test error") - with pytest.raises(OSError) as exc: + with pytest.raises( + OSError, match=r"^\[WinError 12\] test error: 'file_1' -> 'file_2'$" + ) as exc: raise_winerror(filename="file_1", filename2="file_2") mock.assert_called_once_with() mock.reset_mock() @@ -63,7 +65,9 @@ def test_winerror(monkeypatch: pytest.MonkeyPatch) -> None: assert exc.value.filename2 == "file_2" # With an explicit number passed in, it overrides what getwinerror() returns. - with pytest.raises(OSError) as exc: + with pytest.raises( + OSError, match=r"^\[WinError 18\] test error: 'a/file' -> 'b/file'$" + ) as exc: raise_winerror(18, filename="a/file", filename2="b/file") mock.assert_called_once_with(18) mock.reset_mock() diff --git a/src/trio/_tests/test_channel.py b/src/trio/_tests/test_channel.py index ae555715cb..4d8d2ada5a 100644 --- a/src/trio/_tests/test_channel.py +++ b/src/trio/_tests/test_channel.py @@ -13,7 +13,7 @@ async def test_channel() -> None: with pytest.raises(TypeError): open_memory_channel(1.0) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^max_buffer_size must be >= 0$"): open_memory_channel(-1) s, r = open_memory_channel[Union[int, str, None]](2) diff --git a/src/trio/_tests/test_dtls.py b/src/trio/_tests/test_dtls.py index b8f085e9a2..62637d8ef6 100644 --- a/src/trio/_tests/test_dtls.py +++ b/src/trio/_tests/test_dtls.py @@ -98,7 +98,9 @@ async def test_smoke(ipv6: bool) -> None: await client_channel.send(b"goodbye") assert await client_channel.receive() == b"goodbye" - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="^openssl doesn't support sending empty DTLS packets$" + ): await client_channel.send(b"") client_channel.set_ciphertext_mtu(1234) @@ -278,18 +280,17 @@ async def test_client_multiplex() -> None: with pytest.raises(trio.ClosedResourceError): client_endpoint.connect(address1, client_ctx) + async def null_handler(_: object) -> None: # pragma: no cover + pass + async with trio.open_nursery() as nursery: with pytest.raises(trio.ClosedResourceError): - - async def null_handler(_: object) -> None: # pragma: no cover - pass - await nursery.start(client_endpoint.serve, server_ctx, null_handler) async def test_dtls_over_dgram_only() -> None: with trio.socket.socket() as s: - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^DTLS requires a SOCK_DGRAM socket$"): DTLSEndpoint(s) diff --git a/src/trio/_tests/test_exports.py b/src/trio/_tests/test_exports.py index 5c97e76f65..22d8ce08e3 100644 --- a/src/trio/_tests/test_exports.py +++ b/src/trio/_tests/test_exports.py @@ -116,7 +116,7 @@ def iter_modules( # they might be using a newer version of Python with additional symbols which # won't be reflected in trio.socket, and this shouldn't cause downstream test # runs to start failing. -@pytest.mark.redistributors_should_skip +@pytest.mark.redistributors_should_skip() # Static analysis tools often have trouble with alpha releases, where Python's # internals are in flux, grammar may not have settled down, etc. @pytest.mark.skipif( @@ -188,7 +188,8 @@ def no_underscores(symbols: Iterable[str]) -> set[str]: else: mod_cache = trio_cache / (modname + ".data.json") - assert mod_cache.exists() and mod_cache.is_file() + assert mod_cache.exists() + assert mod_cache.is_file() with mod_cache.open() as cache_file: cache_json = json.loads(cache_file.read()) static_names = no_underscores( @@ -243,7 +244,7 @@ def no_underscores(symbols: Iterable[str]) -> set[str]: # modules, instead of once per class. @slow # see comment on test_static_tool_sees_all_symbols -@pytest.mark.redistributors_should_skip +@pytest.mark.redistributors_should_skip() # Static analysis tools often have trouble with alpha releases, where Python's # internals are in flux, grammar may not have settled down, etc. @pytest.mark.skipif( @@ -283,7 +284,8 @@ def no_hidden(symbols: Iterable[str]) -> set[str]: else: mod_cache = trio_cache / (modname + ".data.json") - assert mod_cache.exists() and mod_cache.is_file() + assert mod_cache.exists() + assert mod_cache.is_file() with mod_cache.open() as cache_file: cache_json = json.loads(cache_file.read()) diff --git a/src/trio/_tests/test_fakenet.py b/src/trio/_tests/test_fakenet.py index f7f5c16b4d..b6a67022eb 100644 --- a/src/trio/_tests/test_fakenet.py +++ b/src/trio/_tests/test_fakenet.py @@ -10,11 +10,11 @@ # ENOTCONN gives different messages on different platforms if sys.platform == "linux": - ENOTCONN_MSG = "Transport endpoint is not connected" + ENOTCONN_MSG = r"^\[Errno 107\] Transport endpoint is not connected$" elif sys.platform == "darwin": - ENOTCONN_MSG = "Socket is not connected" + ENOTCONN_MSG = r"^\[Errno 57\] Socket is not connected$" else: - ENOTCONN_MSG = "Unknown error" + ENOTCONN_MSG = r"^\[Errno 10057\] Unknown error$" def fn() -> FakeNet: @@ -33,12 +33,16 @@ async def test_basic_udp() -> None: assert ip == "127.0.0.1" assert port != 0 - with pytest.raises(OSError) as exc: # Cannot rebind. + with pytest.raises( + OSError, match=r"^\[\w+ \d+\] Invalid argument$" + ) as exc: # Cannot rebind. await s1.bind(("192.0.2.1", 0)) assert exc.value.errno == errno.EINVAL # Cannot bind multiple sockets to the same address - with pytest.raises(OSError) as exc: + with pytest.raises( + OSError, match=r"^\[\w+ \d+\] (Address already in use|Unknown error)$" + ) as exc: await s2.bind(("127.0.0.1", port)) assert exc.value.errno == errno.EADDRINUSE @@ -91,7 +95,7 @@ async def test_recv_methods() -> None: assert await s1.sendto(b"ghi", s2.getsockname()) == 3 buf = bytearray(10) - with pytest.raises(NotImplementedError, match="partial recvfrom_into"): + with pytest.raises(NotImplementedError, match="^partial recvfrom_into$"): (nbytes, addr) = await s2.recvfrom_into(buf, nbytes=2) (nbytes, addr) = await s2.recvfrom_into(buf) @@ -115,14 +119,14 @@ async def test_recv_methods() -> None: with pytest.raises(OSError, match=ENOTCONN_MSG) as exc: await s2.send(b"mno") assert exc.value.errno == errno.ENOTCONN - with pytest.raises(NotImplementedError, match="FakeNet send flags must be 0, not"): + with pytest.raises(NotImplementedError, match="^FakeNet send flags must be 0, not"): await s2.send(b"mno", flags) # sendto errors # it's successfully used earlier - with pytest.raises(NotImplementedError, match="FakeNet send flags must be 0, not"): + with pytest.raises(NotImplementedError, match="^FakeNet send flags must be 0, not"): await s2.sendto(b"mno", flags, s1.getsockname()) - with pytest.raises(TypeError, match="wrong number of arguments"): + with pytest.raises(TypeError, match="wrong number of arguments$"): await s2.sendto(b"mno", flags, s1.getsockname(), "extra arg") # type: ignore[call-overload] @@ -176,7 +180,7 @@ async def test_nonwindows_functionality() -> None: assert addr == s1.getsockname() with pytest.raises( - AttributeError, match="'FakeSocket' object has no attribute 'share'" + AttributeError, match="^'FakeSocket' object has no attribute 'share'$" ): await s1.share(0) # type: ignore[attr-defined] @@ -192,16 +196,16 @@ async def test_windows_functionality() -> None: s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM) await s1.bind(("127.0.0.1", 0)) with pytest.raises( - AttributeError, match="'FakeSocket' object has no attribute 'sendmsg'" + AttributeError, match="^'FakeSocket' object has no attribute 'sendmsg'$" ): await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) # type: ignore[attr-defined] with pytest.raises( - AttributeError, match="'FakeSocket' object has no attribute 'recvmsg'" + AttributeError, match="^'FakeSocket' object has no attribute 'recvmsg'$" ): s2.recvmsg(0) # type: ignore[attr-defined] with pytest.raises( AttributeError, - match="'FakeSocket' object has no attribute 'recvmsg_into'", + match="^'FakeSocket' object has no attribute 'recvmsg_into'$", ): s2.recvmsg_into([]) # type: ignore[attr-defined] with pytest.raises(NotImplementedError): @@ -219,23 +223,29 @@ async def test_not_implemented_functions() -> None: s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM) # getsockopt - with pytest.raises(OSError, match="FakeNet doesn't implement getsockopt"): + with pytest.raises( + OSError, match=r"^FakeNet doesn't implement getsockopt\(\d, \d\)$" + ): s1.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) # setsockopt with pytest.raises( - NotImplementedError, match="FakeNet always has IPV6_V6ONLY=True" + NotImplementedError, match="^FakeNet always has IPV6_V6ONLY=True$" ): s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False) - with pytest.raises(OSError, match="FakeNet doesn't implement setsockopt"): + with pytest.raises( + OSError, match=r"^FakeNet doesn't implement setsockopt\(\d+, \d+, \.\.\.\)$" + ): s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, True) - with pytest.raises(OSError, match="FakeNet doesn't implement setsockopt"): + with pytest.raises( + OSError, match=r"^FakeNet doesn't implement setsockopt\(\d+, \d+, \.\.\.\)$" + ): s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # set_inheritable s1.set_inheritable(False) with pytest.raises( - NotImplementedError, match="FakeNet can't make inheritable sockets" + NotImplementedError, match="^FakeNet can't make inheritable sockets$" ): s1.set_inheritable(True) @@ -254,7 +264,7 @@ async def test_getpeername() -> None: with pytest.raises( AssertionError, - match="This method seems to assume that self._binding has a remote UDPEndpoint", + match="^This method seems to assume that self._binding has a remote UDPEndpoint$", ): s1.getpeername() diff --git a/src/trio/_tests/test_highlevel_generic.py b/src/trio/_tests/test_highlevel_generic.py index 3e9fc212a8..5750dd7d24 100644 --- a/src/trio/_tests/test_highlevel_generic.py +++ b/src/trio/_tests/test_highlevel_generic.py @@ -81,16 +81,16 @@ async def test_StapledStream_with_erroring_close() -> None: class BrokenSendStream(RecordSendStream): async def aclose(self) -> NoReturn: await super().aclose() - raise ValueError + raise ValueError("send error") class BrokenReceiveStream(RecordReceiveStream): async def aclose(self) -> NoReturn: await super().aclose() - raise ValueError + raise ValueError("recv error") stapled = StapledStream(BrokenSendStream(), BrokenReceiveStream()) - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="^(send|recv) error$") as excinfo: await stapled.aclose() assert isinstance(excinfo.value.__context__, ValueError) diff --git a/src/trio/_tests/test_highlevel_open_tcp_listeners.py b/src/trio/_tests/test_highlevel_open_tcp_listeners.py index 23d6f794e0..3b7cd35c2a 100644 --- a/src/trio/_tests/test_highlevel_open_tcp_listeners.py +++ b/src/trio/_tests/test_highlevel_open_tcp_listeners.py @@ -77,7 +77,10 @@ async def test_open_tcp_listeners_ipv6_v6only() -> None: async with ipv6_listener: _, port, *_ = ipv6_listener.socket.getsockname() - with pytest.raises(OSError): + with pytest.raises( + OSError, + match=r"(Error|all attempts to) connect(ing)* to (\(')*127\.0\.0\.1(', |:)\d+(\): Connection refused| failed)$", + ): await open_tcp_stream("127.0.0.1", port) @@ -89,7 +92,10 @@ async def test_open_tcp_listeners_rebind() -> None: # SO_REUSEADDR set with stdlib_socket.socket() as probe: probe.setsockopt(stdlib_socket.SOL_SOCKET, stdlib_socket.SO_REUSEADDR, 1) - with pytest.raises(OSError): + with pytest.raises( + OSError, + match="(Address already in use|An attempt was made to access a socket in a way forbidden by its access permissions)$", + ): probe.bind(sockaddr1) # Now use the first listener to set up some connections in various states, @@ -323,10 +329,9 @@ async def test_open_tcp_listeners_some_address_families_unavailable( should_succeed = try_families - fail_families if not should_succeed: - with pytest.raises(OSError) as exc_info: + with pytest.raises(OSError, match="This system doesn't support") as exc_info: await open_tcp_listeners(80, host="example.org") - assert "This system doesn't support" in str(exc_info.value) if isinstance(exc_info.value.__cause__, BaseExceptionGroup): for subexc in exc_info.value.__cause__.exceptions: assert "nope" in str(subexc) @@ -353,7 +358,7 @@ async def test_open_tcp_listeners_socket_fails_not_afnosupport() -> None: FakeHostnameResolver([(tsocket.AF_INET, "foo"), (tsocket.AF_INET6, "bar")]) ) - with pytest.raises(OSError) as exc_info: + with pytest.raises(OSError, match="nope") as exc_info: await open_tcp_listeners(80, host="example.org") assert exc_info.value.errno == errno.EINVAL assert exc_info.value.__cause__ is None diff --git a/src/trio/_tests/test_highlevel_open_tcp_stream.py b/src/trio/_tests/test_highlevel_open_tcp_stream.py index 79dd8b0f78..9a71c4d298 100644 --- a/src/trio/_tests/test_highlevel_open_tcp_stream.py +++ b/src/trio/_tests/test_highlevel_open_tcp_stream.py @@ -33,7 +33,7 @@ def close(self) -> None: class CloseKiller(SocketType): def close(self) -> None: - raise OSError + raise OSError("os error text") c: CloseMe = CloseMe() with close_all() as to_close: @@ -41,14 +41,14 @@ def close(self) -> None: assert c.closed c = CloseMe() - with pytest.raises(RuntimeError): + with pytest.raises(RuntimeError): # noqa: PT012 with close_all() as to_close: to_close.add(c) raise RuntimeError assert c.closed c = CloseMe() - with pytest.raises(OSError): + with pytest.raises(OSError, match="os error text"): # noqa: PT012 with close_all() as to_close: to_close.add(CloseKiller()) to_close.add(c) @@ -122,7 +122,7 @@ async def test_open_tcp_stream_real_socket_smoketest() -> None: async def test_open_tcp_stream_input_validation() -> None: - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^host must be str or bytes, not None$"): await open_tcp_stream(None, 80) # type: ignore[arg-type] with pytest.raises(TypeError): await open_tcp_stream("127.0.0.1", b"80") # type: ignore[arg-type] @@ -170,7 +170,9 @@ async def test_local_address_real() -> None: # Trying to connect to an ipv4 address with the ipv6 wildcard # local_address should fail - with pytest.raises(OSError): + with pytest.raises( + OSError, match=r"^all attempts to connect* to *127\.0\.0\.\d:\d+ failed$" + ): await open_tcp_stream(*listener.getsockname(), local_address="::") # But the ipv4 wildcard address should work diff --git a/src/trio/_tests/test_highlevel_serve_listeners.py b/src/trio/_tests/test_highlevel_serve_listeners.py index 5fd8ac72e3..c0ded5e28f 100644 --- a/src/trio/_tests/test_highlevel_serve_listeners.py +++ b/src/trio/_tests/test_highlevel_serve_listeners.py @@ -158,7 +158,7 @@ async def connection_watcher( assert len(nursery.child_tasks) == 10 raise Done - with pytest.raises(Done): + with pytest.raises(Done): # noqa: PT012 async with trio.open_nursery() as nursery: handler_nursery: trio.Nursery = await nursery.start(connection_watcher) await nursery.start( diff --git a/src/trio/_tests/test_highlevel_socket.py b/src/trio/_tests/test_highlevel_socket.py index 61e891e94b..7986d8118e 100644 --- a/src/trio/_tests/test_highlevel_socket.py +++ b/src/trio/_tests/test_highlevel_socket.py @@ -26,7 +26,9 @@ async def test_SocketStream_basics() -> None: # DGRAM socket bad with tsocket.socket(type=tsocket.SOCK_DGRAM) as sock: - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="^SocketStream requires a SOCK_STREAM socket$" + ): # TODO: does not raise an error? SocketStream(sock) @@ -152,7 +154,9 @@ async def test_SocketListener() -> None: # Not a SOCK_STREAM with tsocket.socket(type=tsocket.SOCK_DGRAM) as s: await s.bind(("127.0.0.1", 0)) - with pytest.raises(ValueError) as excinfo: + with pytest.raises( + ValueError, match="^SocketListener requires a SOCK_STREAM socket$" + ) as excinfo: SocketListener(s) excinfo.match(r".*SOCK_STREAM") @@ -161,7 +165,9 @@ async def test_SocketListener() -> None: if sys.platform != "darwin": with tsocket.socket() as s: await s.bind(("127.0.0.1", 0)) - with pytest.raises(ValueError) as excinfo: + with pytest.raises( + ValueError, match="^SocketListener requires a listening socket$" + ) as excinfo: SocketListener(s) excinfo.match(r".*listen") @@ -281,9 +287,13 @@ async def accept(self) -> tuple[SocketType, object]: stream = await listener.accept() assert stream.socket is fake_server_sock - for code in [errno.EMFILE, errno.EFAULT, errno.ENOBUFS]: + for code, match in { + errno.EMFILE: r"\[\w+ \d+\] Out of file descriptors$", + errno.EFAULT: r"\[\w+ \d+\] attempt to write to read-only memory$", + errno.ENOBUFS: r"\[\w+ \d+\] out of buffers$", + }.items(): with assert_checkpoints(): - with pytest.raises(OSError) as excinfo: + with pytest.raises(OSError, match=match) as excinfo: await listener.accept() assert excinfo.value.errno == code diff --git a/src/trio/_tests/test_path.py b/src/trio/_tests/test_path.py index bd2c193f4b..2d994d397e 100644 --- a/src/trio/_tests/test_path.py +++ b/src/trio/_tests/test_path.py @@ -51,7 +51,7 @@ async def test_magic() -> None: ] -@pytest.mark.parametrize("cls_a,cls_b", cls_pairs) +@pytest.mark.parametrize(("cls_a", "cls_b"), cls_pairs) async def test_cmp_magic(cls_a: EitherPathType, cls_b: EitherPathType) -> None: a, b = cls_a(""), cls_b("") assert a == b @@ -78,7 +78,7 @@ async def test_cmp_magic(cls_a: EitherPathType, cls_b: EitherPathType) -> None: ] -@pytest.mark.parametrize("cls_a,cls_b", cls_pairs_str) +@pytest.mark.parametrize(("cls_a", "cls_b"), cls_pairs_str) async def test_div_magic(cls_a: PathOrStrType, cls_b: PathOrStrType) -> None: a, b = cls_a("a"), cls_b("b") @@ -89,7 +89,7 @@ async def test_div_magic(cls_a: PathOrStrType, cls_b: PathOrStrType) -> None: @pytest.mark.parametrize( - "cls_a,cls_b", [(trio.Path, pathlib.Path), (trio.Path, trio.Path)] + ("cls_a", "cls_b"), [(trio.Path, pathlib.Path), (trio.Path, trio.Path)] ) @pytest.mark.parametrize("path", ["foo", "foo/bar/baz", "./foo"]) async def test_hash_magic( diff --git a/src/trio/_tests/test_scheduler_determinism.py b/src/trio/_tests/test_scheduler_determinism.py index 02733226cf..4cc46feb33 100644 --- a/src/trio/_tests/test_scheduler_determinism.py +++ b/src/trio/_tests/test_scheduler_determinism.py @@ -5,7 +5,7 @@ import trio if TYPE_CHECKING: - from pytest import MonkeyPatch + import pytest async def scheduler_trace() -> tuple[tuple[str, int], ...]: @@ -33,7 +33,7 @@ def test_the_trio_scheduler_is_not_deterministic() -> None: def test_the_trio_scheduler_is_deterministic_if_seeded( - monkeypatch: MonkeyPatch, + monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setattr(trio._core._run, "_ALLOW_DETERMINISTIC_SCHEDULING", True) traces = [] diff --git a/src/trio/_tests/test_signals.py b/src/trio/_tests/test_signals.py index 6caadc3f8b..0d38168d57 100644 --- a/src/trio/_tests/test_signals.py +++ b/src/trio/_tests/test_signals.py @@ -41,7 +41,9 @@ async def test_open_signal_receiver() -> None: async def test_open_signal_receiver_restore_handler_after_one_bad_signal() -> None: orig = signal.getsignal(signal.SIGILL) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="(signal number out of range|invalid signal value)$" + ): with open_signal_receiver(signal.SIGILL, 1234567): pass # pragma: no cover # Still restored even if we errored out @@ -72,7 +74,7 @@ async def naughty() -> None: async def test_open_signal_receiver_conflict() -> None: - with pytest.raises(trio.BusyResourceError): + with pytest.raises(trio.BusyResourceError): # noqa: PT012 with open_signal_receiver(signal.SIGILL) as receiver: async with trio.open_nursery() as nursery: nursery.start_soon(receiver.__anext__) @@ -171,7 +173,7 @@ def raise_handler(signum: int, frame: FrameType | None) -> NoReturn: raise RuntimeError(signum) with _signal_handler({signal.SIGILL, signal.SIGFPE}, raise_handler): - with pytest.raises(RuntimeError) as excinfo: + with pytest.raises(RuntimeError) as excinfo: # noqa: PT012 with open_signal_receiver(signal.SIGILL, signal.SIGFPE) as receiver: signal_raise(signal.SIGILL) signal_raise(signal.SIGFPE) diff --git a/src/trio/_tests/test_socket.py b/src/trio/_tests/test_socket.py index bb53146b76..1a55647437 100644 --- a/src/trio/_tests/test_socket.py +++ b/src/trio/_tests/test_socket.py @@ -464,7 +464,7 @@ async def test_SocketType_shutdown() -> None: @pytest.mark.parametrize( - "address, socket_type", + ("address", "socket_type"), [ ("127.0.0.1", tsocket.AF_INET), pytest.param("::1", tsocket.AF_INET6, marks=binds_ipv6), @@ -522,7 +522,7 @@ class Addresses: # Direct thorough tests of the implicit resolver helpers @pytest.mark.parametrize( - "socket_type, addrs", + ("socket_type", "addrs"), [ ( tsocket.AF_INET, @@ -648,11 +648,15 @@ async def res( ) netlink_sock.close() - with pytest.raises(ValueError): + address = r"^address should be a \(host, port(, \[flowinfo, \[scopeid\]\])*\) tuple$" + with pytest.raises(ValueError, match=address): await res("1.2.3.4") # type: ignore[arg-type] - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=address): await res(("1.2.3.4",)) # type: ignore[arg-type] - with pytest.raises(ValueError): + with pytest.raises( # noqa: PT012 + ValueError, + match=address, + ): if v6: await res(("1.2.3.4", 80, 0, 0, 0)) # type: ignore[arg-type] else: @@ -756,7 +760,10 @@ async def t2() -> None: # This tests the complicated paths through connect async def test_SocketType_connect_paths() -> None: with tsocket.socket() as sock: - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match=r"^address should be a \(host, port(, \[flowinfo, \[scopeid\]\])*\) tuple$", + ): # Should be a tuple await sock.connect("localhost") @@ -801,7 +808,10 @@ def connect(self, *args: Any, **kwargs: Any) -> None: # Failed connect (hopefully after raising BlockingIOError) with tsocket.socket() as sock: - with pytest.raises(OSError): + with pytest.raises( + OSError, + match=r"^\[\w+ \d+\] Error connecting to \('127\.0\.0\.\d', \d+\): (Connection refused|Unknown error)$", + ): # TCP port 2 is not assigned. Pretty sure nothing will be # listening there. (We used to bind a port and then *not* call # listen() to ensure nothing was listening there, but it turns @@ -816,10 +826,11 @@ def connect(self, *args: Any, **kwargs: Any) -> None: async def test_address_in_socket_error() -> None: address = "127.0.0.1" with tsocket.socket() as sock: - try: + with pytest.raises( + OSError, + match=rf"^\[\w+ \d+\] Error connecting to \({address!r}, 2\): (Connection refused|Unknown error)$", + ): await sock.connect((address, 2)) - except OSError as e: - assert any(address in str(arg) for arg in e.args) async def test_resolve_address_exception_in_connect_closes_socket() -> None: @@ -1112,19 +1123,23 @@ async def receiver() -> None: async def test_many_sockets() -> None: total = 5000 # Must be more than MAX_AFD_GROUP_SIZE sockets = [] - for _x in range(total // 2): + # Open at most socket pairs + for opened in range(0, total, 2): try: a, b = stdlib_socket.socketpair() - except OSError as e: # pragma: no cover - assert e.errno in (errno.EMFILE, errno.ENFILE) + except OSError as exc: # pragma: no cover + # Semi-expecting following errors (sockets are files): + # EMFILE: "Too many open files" (reached kernel cap) + # ENFILE: "File table overflow" (beyond kernel cap) + assert exc.errno in (errno.EMFILE, errno.ENFILE) # noqa: PT017 + print(f"Unable to open more than {opened} sockets.") + # Stop opening any more sockets if too many are open break sockets += [a, b] async with _core.open_nursery() as nursery: - for s in sockets: - nursery.start_soon(_core.wait_readable, s) + for socket in sockets: + nursery.start_soon(_core.wait_readable, socket) await _core.wait_all_tasks_blocked() nursery.cancel_scope.cancel() - for sock in sockets: - sock.close() - if _x != total // 2 - 1: # pragma: no cover - print(f"Unable to open more than {(_x-1)*2} sockets.") + for socket in sockets: + socket.close() diff --git a/src/trio/_tests/test_ssl.py b/src/trio/_tests/test_ssl.py index 27f93f0cd2..d8ce80df47 100644 --- a/src/trio/_tests/test_ssl.py +++ b/src/trio/_tests/test_ssl.py @@ -356,7 +356,9 @@ async def do_test( func1: str, args1: tuple[object, ...], func2: str, args2: tuple[object, ...] ) -> None: s = PyOpenSSLEchoStream() - with pytest.raises(_core.BusyResourceError, match="simultaneous"): + with pytest.raises( # noqa: PT012 + _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) @@ -743,7 +745,9 @@ 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"): + with pytest.raises( # noqa: PT012 + _core.BusyResourceError, match="another task" + ): async with _core.open_nursery() as nursery: nursery.start_soon(func1, s) nursery.start_soon(func2, s) diff --git a/src/trio/_tests/test_subprocess.py b/src/trio/_tests/test_subprocess.py index 8099ea446d..c2cad33bf9 100644 --- a/src/trio/_tests/test_subprocess.py +++ b/src/trio/_tests/test_subprocess.py @@ -20,7 +20,6 @@ ) import pytest -from pytest import MonkeyPatch, WarningsRecorder from .. import ( ClosedResourceError, @@ -170,7 +169,7 @@ async def test_multi_wait(background_process: BackgroundProcessType) -> None: # Test for deprecated 'async with process:' semantics -async def test_async_with_basics_deprecated(recwarn: WarningsRecorder) -> None: +async def test_async_with_basics_deprecated(recwarn: pytest.WarningsRecorder) -> None: async with await open_process( CAT, stdin=subprocess.PIPE, stdout=subprocess.PIPE ) as proc: @@ -185,7 +184,7 @@ async def test_async_with_basics_deprecated(recwarn: WarningsRecorder) -> None: # Test for deprecated 'async with process:' semantics -async def test_kill_when_context_cancelled(recwarn: WarningsRecorder) -> None: +async def test_kill_when_context_cancelled(recwarn: pytest.WarningsRecorder) -> None: with move_on_after(100) as scope: async with await open_process(SLEEP(10)) as proc: assert proc.poll() is None @@ -346,15 +345,25 @@ async def test_run() -> None: # invalid combinations with pytest.raises(UnicodeError): await run_process(CAT, stdin="oh no, it's text") - with pytest.raises(ValueError): + + pipe_stdout_error = r"^stdout=subprocess\.PIPE is only valid with nursery\.start, since that's the only way to access the pipe(; use nursery\.start or pass the data you want to write directly)*$" + with pytest.raises(ValueError, match=pipe_stdout_error): await run_process(CAT, stdin=subprocess.PIPE) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=pipe_stdout_error): await run_process(CAT, stdout=subprocess.PIPE) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match=pipe_stdout_error.replace("stdout", "stderr", 1) + ): await run_process(CAT, stderr=subprocess.PIPE) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="^can't specify both stdout and capture_stdout$", + ): await run_process(CAT, capture_stdout=True, stdout=subprocess.DEVNULL) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="^can't specify both stderr and capture_stderr$", + ): await run_process(CAT, capture_stderr=True, stderr=None) @@ -583,7 +592,7 @@ async def custom_deliver_cancel(proc: Process) -> None: assert custom_deliver_cancel_called -async def test_warn_on_failed_cancel_terminate(monkeypatch: MonkeyPatch) -> None: +async def test_warn_on_failed_cancel_terminate(monkeypatch: pytest.MonkeyPatch) -> None: original_terminate = Process.terminate def broken_terminate(self: Process) -> NoReturn: @@ -601,7 +610,7 @@ def broken_terminate(self: Process) -> NoReturn: @pytest.mark.skipif(not posix, reason="posix only") async def test_warn_on_cancel_SIGKILL_escalation( - autojump_clock: MockClock, monkeypatch: MonkeyPatch + autojump_clock: MockClock, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setattr(Process, "terminate", lambda *args: None) diff --git a/src/trio/_tests/test_sync.py b/src/trio/_tests/test_sync.py index dd78d2e82f..e4d04202cb 100644 --- a/src/trio/_tests/test_sync.py +++ b/src/trio/_tests/test_sync.py @@ -47,7 +47,7 @@ async def child() -> None: async def test_CapacityLimiter() -> None: with pytest.raises(TypeError): CapacityLimiter(1.0) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^total_tokens must be >= 1$"): CapacityLimiter(-1) c = CapacityLimiter(2) repr(c) # smoke test @@ -135,10 +135,10 @@ async def test_CapacityLimiter_change_total_tokens() -> None: with pytest.raises(TypeError): c.total_tokens = 1.0 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^total_tokens must be >= 1$"): c.total_tokens = 0 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^total_tokens must be >= 1$"): c.total_tokens = -10 assert c.total_tokens == 2 @@ -183,7 +183,7 @@ async def test_CapacityLimiter_memleak_548() -> None: async def test_Semaphore() -> None: with pytest.raises(TypeError): Semaphore(1.0) # type: ignore[arg-type] - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^initial value must be >= 0$"): Semaphore(-1) s = Semaphore(1) repr(s) # smoke test @@ -231,12 +231,12 @@ async def do_acquire(s: Semaphore) -> None: async def test_Semaphore_bounded() -> None: with pytest.raises(TypeError): Semaphore(1, max_value=1.0) # type: ignore[arg-type] - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^max_values must be >= initial_value$"): Semaphore(2, max_value=1) bs = Semaphore(1, max_value=1) assert bs.max_value == 1 repr(bs) # smoke test - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^semaphore released too many times$"): bs.release() assert bs.value == 1 bs.acquire_nowait() diff --git a/src/trio/_tests/test_testing.py b/src/trio/_tests/test_testing.py index f6e137c55e..89d756f360 100644 --- a/src/trio/_tests/test_testing.py +++ b/src/trio/_tests/test_testing.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING import pytest -from pytest import WarningsRecorder from .. import _core, sleep, socket as tsocket from .._core._tests.tutil import can_bind_ipv6 @@ -111,7 +110,7 @@ async def wait_big_cushion() -> None: ################################################################ -async def test_assert_checkpoints(recwarn: WarningsRecorder) -> None: +async def test_assert_checkpoints(recwarn: pytest.WarningsRecorder) -> None: with assert_checkpoints(): await _core.checkpoint() @@ -137,7 +136,7 @@ async def test_assert_checkpoints(recwarn: WarningsRecorder) -> None: await _core.cancel_shielded_checkpoint() -async def test_assert_no_checkpoints(recwarn: WarningsRecorder) -> None: +async def test_assert_no_checkpoints(recwarn: pytest.WarningsRecorder) -> None: with assert_no_checkpoints(): 1 + 1 # noqa: B018 # "useless expression" @@ -158,7 +157,7 @@ async def test_assert_no_checkpoints(recwarn: WarningsRecorder) -> None: await partial_yield() # And both together also count as a checkpoint - with pytest.raises(AssertionError): + with pytest.raises(AssertionError): # noqa: PT012 with assert_no_checkpoints(): await _core.checkpoint_if_cancelled() await _core.cancel_shielded_checkpoint() @@ -293,7 +292,7 @@ async def getter(expect: bytes) -> None: nursery.start_soon(putter, b"xyz") # Two gets at the same time -> BusyResourceError - with pytest.raises(_core.BusyResourceError): + with pytest.raises(_core.BusyResourceError): # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(getter, b"asdf") nursery.start_soon(getter, b"asdf") @@ -427,7 +426,7 @@ async def do_receive_some(max_bytes: int | None) -> bytes: mrs.put_data(b"abc") assert await do_receive_some(None) == b"abc" - with pytest.raises(_core.BusyResourceError): + with pytest.raises(_core.BusyResourceError): # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(do_receive_some, 10) nursery.start_soon(do_receive_some, 10) @@ -649,7 +648,8 @@ async def check(listener: SocketListener) -> None: sock.listen(10) await check(SocketListener(sock)) - if can_bind_ipv6: + # true on all CI systems + if can_bind_ipv6: # pragma: no branch # Listener bound to IPv6 wildcard (needs special handling) sock = tsocket.socket(family=tsocket.AF_INET6) await sock.bind(("::", 0)) diff --git a/src/trio/_tests/test_threads.py b/src/trio/_tests/test_threads.py index adbd5e3bda..5b4eeb6db7 100644 --- a/src/trio/_tests/test_threads.py +++ b/src/trio/_tests/test_threads.py @@ -23,7 +23,6 @@ import pytest import sniffio -from pytest import MonkeyPatch from .. import ( CancelScope, @@ -327,7 +326,9 @@ def f(x: T) -> tuple[T, threading.Thread]: def g() -> NoReturn: raise ValueError(threading.current_thread()) - with pytest.raises(ValueError) as excinfo: + with pytest.raises( + ValueError, match=r"^$" + ) as excinfo: await to_thread_run_sync(g) print(excinfo.value.args) assert excinfo.value.args[0] != trio_thread @@ -394,7 +395,7 @@ async def child(q: stdlib_queue.Queue[None], abandon_on_cancel: bool) -> None: # handled gracefully. (Requires that the thread result machinery be prepared # for call_soon to raise RunFinishedError.) def test_run_in_worker_thread_abandoned( - capfd: pytest.CaptureFixture[str], monkeypatch: MonkeyPatch + capfd: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setattr(_core._thread_cache, "IDLE_TIMEOUT", 0.01) @@ -574,11 +575,11 @@ async def acquire_on_behalf_of(self, borrower: Task) -> None: def release_on_behalf_of(self, borrower: Task) -> NoReturn: record.append("release") - raise ValueError + raise ValueError("release on behalf") bs = BadCapacityLimiter() - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="^release on behalf$") as excinfo: await to_thread_run_sync(lambda: None, limiter=bs) # type: ignore[call-overload] assert excinfo.value.__context__ is None assert record == ["acquire", "release"] @@ -587,13 +588,15 @@ def release_on_behalf_of(self, borrower: Task) -> NoReturn: # If the original function raised an error, then the semaphore error # chains with it d: dict[str, object] = {} - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="^release on behalf$") as excinfo: await to_thread_run_sync(lambda: d["x"], limiter=bs) # type: ignore[call-overload] assert isinstance(excinfo.value.__context__, KeyError) assert record == ["acquire", "release"] -async def test_run_in_worker_thread_fail_to_spawn(monkeypatch: MonkeyPatch) -> None: +async def test_run_in_worker_thread_fail_to_spawn( + monkeypatch: pytest.MonkeyPatch, +) -> None: # Test the unlikely but possible case where trying to spawn a thread fails def bad_start(self: object, *args: object) -> NoReturn: raise RuntimeError("the engines canna take it captain") @@ -1085,10 +1088,16 @@ async def child() -> None: async def test_cancellable_and_abandon_raises() -> None: - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match=r"^Cannot set `cancellable` and `abandon_on_cancel` simultaneously\.$", + ): await to_thread_run_sync(bool, cancellable=True, abandon_on_cancel=False) # type: ignore[call-overload] - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match=r"^Cannot set `cancellable` and `abandon_on_cancel` simultaneously\.$", + ): await to_thread_run_sync(bool, cancellable=True, abandon_on_cancel=True) # type: ignore[call-overload] diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py index c6def0bf9e..98c3d18def 100644 --- a/src/trio/_tests/test_timeouts.py +++ b/src/trio/_tests/test_timeouts.py @@ -109,7 +109,10 @@ async def test_timeouts_raise_value_error() -> None: (sleep, nan), (sleep_until, nan), ): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="^(duration|deadline|timeout) must (not )*be (non-negative|NaN)$", + ): await fun(val) for cm, val in ( @@ -120,6 +123,9 @@ async def test_timeouts_raise_value_error() -> None: (move_on_after, nan), (move_on_at, nan), ): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="^(duration|deadline|timeout) must (not )*be (non-negative|NaN)$", + ): with cm(val): pass # pragma: no cover diff --git a/src/trio/_tests/test_unix_pipes.py b/src/trio/_tests/test_unix_pipes.py index c258cd97cc..6f8fa6e02e 100644 --- a/src/trio/_tests/test_unix_pipes.py +++ b/src/trio/_tests/test_unix_pipes.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING import pytest -from pytest import MonkeyPatch from .. import _core from .._core._tests.tutil import gc_collect_harder, skip_if_fbsd_pipes_broken @@ -108,7 +107,7 @@ async def test_pipe_errors() -> None: r, w = os.pipe() os.close(w) async with FdStream(r) as s: - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^max_bytes must be integer >= 1$"): await s.receive_some(0) @@ -118,11 +117,11 @@ async def test_del() -> None: del w, r gc_collect_harder() - with pytest.raises(OSError) as excinfo: + with pytest.raises(OSError, match="Bad file descriptor$") as excinfo: os.close(f1) assert excinfo.value.errno == errno.EBADF - with pytest.raises(OSError) as excinfo: + with pytest.raises(OSError, match="Bad file descriptor$") as excinfo: os.close(f2) assert excinfo.value.errno == errno.EBADF @@ -135,11 +134,11 @@ async def test_async_with() -> None: assert w.fileno() == -1 assert r.fileno() == -1 - with pytest.raises(OSError) as excinfo: + with pytest.raises(OSError, match="Bad file descriptor$") as excinfo: os.close(w.fileno()) assert excinfo.value.errno == errno.EBADF - with pytest.raises(OSError) as excinfo: + with pytest.raises(OSError, match="Bad file descriptor$") as excinfo: os.close(r.fileno()) assert excinfo.value.errno == errno.EBADF @@ -182,7 +181,9 @@ async def expect_eof() -> None: os.close(w2_fd) -async def test_close_at_bad_time_for_receive_some(monkeypatch: MonkeyPatch) -> None: +async def test_close_at_bad_time_for_receive_some( + monkeypatch: pytest.MonkeyPatch, +) -> None: # We used to have race conditions where if one task was using the pipe, # and another closed it at *just* the wrong moment, it would give an # unexpected error instead of ClosedResourceError: @@ -210,7 +211,7 @@ async def patched_wait_readable(*args, **kwargs) -> None: await s.send_all(b"x") -async def test_close_at_bad_time_for_send_all(monkeypatch: MonkeyPatch) -> None: +async def test_close_at_bad_time_for_send_all(monkeypatch: pytest.MonkeyPatch) -> None: # We used to have race conditions where if one task was using the pipe, # and another closed it at *just* the wrong moment, it would give an # unexpected error instead of ClosedResourceError: diff --git a/src/trio/_tests/test_util.py b/src/trio/_tests/test_util.py index a1f5fac537..dd44765ba1 100644 --- a/src/trio/_tests/test_util.py +++ b/src/trio/_tests/test_util.py @@ -49,7 +49,7 @@ async def test_ConflictDetector() -> None: with ul2: print("ok") - with pytest.raises(_core.BusyResourceError, match="ul1"): + with pytest.raises(_core.BusyResourceError, match="ul1"): # noqa: PT012 with ul1: with ul1: pass # pragma: no cover @@ -58,7 +58,7 @@ async def wait_with_ul1() -> None: with ul1: await wait_all_tasks_blocked() - with pytest.raises(_core.BusyResourceError, match="ul1"): + with pytest.raises(_core.BusyResourceError, match="ul1"): # noqa: PT012 async with _core.open_nursery() as nursery: nursery.start_soon(wait_with_ul1) nursery.start_soon(wait_with_ul1) diff --git a/src/trio/_tests/test_wait_for_object.py b/src/trio/_tests/test_wait_for_object.py index b41bcba3a5..54bbb77567 100644 --- a/src/trio/_tests/test_wait_for_object.py +++ b/src/trio/_tests/test_wait_for_object.py @@ -54,7 +54,7 @@ async def test_WaitForMultipleObjects_sync() -> None: handle1 = kernel32.CreateEventA(ffi.NULL, True, False, ffi.NULL) handle2 = kernel32.CreateEventA(ffi.NULL, True, False, ffi.NULL) kernel32.CloseHandle(handle1) - with pytest.raises(OSError): + with pytest.raises(OSError, match=r"^\[WinError 6\] The handle is invalid$"): WaitForMultipleObjects_sync(handle1, handle2) kernel32.CloseHandle(handle2) print("test_WaitForMultipleObjects_sync close first OK") @@ -63,7 +63,7 @@ async def test_WaitForMultipleObjects_sync() -> None: handle1 = kernel32.CreateEventA(ffi.NULL, True, False, ffi.NULL) handle2 = kernel32.CreateEventA(ffi.NULL, True, False, ffi.NULL) kernel32.CloseHandle(handle2) - with pytest.raises(OSError): + with pytest.raises(OSError, match=r"^\[WinError 6\] The handle is invalid$"): WaitForMultipleObjects_sync(handle1, handle2) kernel32.CloseHandle(handle1) print("test_WaitForMultipleObjects_sync close second OK") @@ -147,7 +147,7 @@ async def test_WaitForSingleObject() -> None: # Test already closed handle = kernel32.CreateEventA(ffi.NULL, True, False, ffi.NULL) kernel32.CloseHandle(handle) - with pytest.raises(OSError): + with pytest.raises(OSError, match=r"^\[WinError 6\] The handle is invalid$"): await WaitForSingleObject(handle) # should return at once print("test_WaitForSingleObject already closed OK") diff --git a/src/trio/_tests/test_windows_pipes.py b/src/trio/_tests/test_windows_pipes.py index f0783b7b06..0f968e26ce 100644 --- a/src/trio/_tests/test_windows_pipes.py +++ b/src/trio/_tests/test_windows_pipes.py @@ -45,9 +45,9 @@ async def test_pipe_error_on_close() -> None: assert kernel32.CloseHandle(_handle(r)) assert kernel32.CloseHandle(_handle(w)) - with pytest.raises(OSError): + with pytest.raises(OSError, match=r"^\[WinError 6\] The handle is invalid$"): await send_stream.aclose() - with pytest.raises(OSError): + with pytest.raises(OSError, match=r"^\[WinError 6\] The handle is invalid$"): await receive_stream.aclose() @@ -96,7 +96,7 @@ async def test_close_during_write() -> None: async with _core.open_nursery() as nursery: async def write_forever() -> None: - with pytest.raises(_core.ClosedResourceError) as excinfo: + with pytest.raises(_core.ClosedResourceError) as excinfo: # noqa: PT012 while True: await w.send_all(b"x" * 4096) assert "another task" in str(excinfo.value) diff --git a/src/trio/_tests/tools/test_mypy_annotate.py b/src/trio/_tests/tools/test_mypy_annotate.py index a83116dd78..0ff4babb99 100644 --- a/src/trio/_tests/tools/test_mypy_annotate.py +++ b/src/trio/_tests/tools/test_mypy_annotate.py @@ -13,7 +13,7 @@ @pytest.mark.parametrize( - "src, expected", + ("src", "expected"), [ ("", None), ("a regular line\n", None),