# Utilities for testing import asyncio import socket as stdlib_socket import threading import os import sys from typing import TYPE_CHECKING import pytest import warnings from contextlib import contextmanager, closing import gc # See trio/tests/conftest.py for the other half of this from trio.tests.conftest import RUN_SLOW slow = pytest.mark.skipif(not RUN_SLOW, reason="use --run-slow to run slow tests") # PyPy 7.2 was released with a bug that just never called the async # generator 'firstiter' hook at all. This impacts tests of end-of-run # finalization (nothing gets added to runner.asyncgens) and tests of # "foreign" async generator behavior (since the firstiter hook is what # marks the asyncgen as foreign), but most tests of GC-mediated # finalization still work. buggy_pypy_asyncgens = ( not TYPE_CHECKING and sys.implementation.name == "pypy" and sys.pypy_version_info < (7, 3) ) try: s = stdlib_socket.socket(stdlib_socket.AF_INET6, stdlib_socket.SOCK_STREAM, 0) except OSError: # pragma: no cover # Some systems don't even support creating an IPv6 socket, let alone # binding it. (ex: Linux with 'ipv6.disable=1' in the kernel command line) # We don't have any of those in our CI, and there's nothing that gets # tested _only_ if can_create_ipv6 = False, so we'll just no-cover this. can_create_ipv6 = False can_bind_ipv6 = False else: can_create_ipv6 = True with s: try: s.bind(("::1", 0)) except OSError: can_bind_ipv6 = False else: can_bind_ipv6 = True creates_ipv6 = pytest.mark.skipif(not can_create_ipv6, reason="need IPv6") binds_ipv6 = pytest.mark.skipif(not can_bind_ipv6, reason="need IPv6") def gc_collect_harder(): # In the test suite we sometimes want to call gc.collect() to make sure # that any objects with noisy __del__ methods (e.g. unawaited coroutines) # get collected before we continue, so their noise doesn't leak into # unrelated tests. # # On PyPy, coroutine objects (for example) can survive at least 1 round of # garbage collection, because executing their __del__ method to print the # warning can cause them to be resurrected. So we call collect a few times # to make sure. for _ in range(4): gc.collect() # Some of our tests need to leak coroutines, and thus trigger the # "RuntimeWarning: coroutine '...' was never awaited" message. This context # manager should be used anywhere this happens to hide those messages, because # when expected they're clutter. @contextmanager def ignore_coroutine_never_awaited_warnings(): with warnings.catch_warnings(): warnings.filterwarnings("ignore", message="coroutine '.*' was never awaited") try: yield finally: # Make sure to trigger any coroutine __del__ methods now, before # we leave the context manager. gc_collect_harder() def _noop(*args, **kwargs): pass if sys.version_info >= (3, 8): @contextmanager def restore_unraisablehook(): sys.unraisablehook, prev = sys.__unraisablehook__, sys.unraisablehook try: yield finally: sys.unraisablehook = prev @contextmanager def disable_threading_excepthook(): if sys.version_info >= (3, 10): threading.excepthook, prev = threading.__excepthook__, threading.excepthook else: threading.excepthook, prev = _noop, threading.excepthook try: yield finally: threading.excepthook = prev else: @contextmanager def restore_unraisablehook(): # pragma: no cover yield @contextmanager def disable_threading_excepthook(): # pragma: no cover yield # template is like: # [1, {2.1, 2.2}, 3] -> matches [1, 2.1, 2.2, 3] or [1, 2.2, 2.1, 3] def check_sequence_matches(seq, template): i = 0 for pattern in template: if not isinstance(pattern, set): pattern = {pattern} got = set(seq[i : i + len(pattern)]) assert got == pattern i += len(got) # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=246350 skip_if_fbsd_pipes_broken = pytest.mark.skipif( sys.platform != "win32" # prevent mypy from complaining about missing uname and hasattr(os, "uname") and os.uname().sysname == "FreeBSD" and os.uname().release[:4] < "12.2", reason="hangs on FreeBSD 12.1 and earlier, due to FreeBSD bug #246350", ) def create_asyncio_future_in_new_loop(): with closing(asyncio.new_event_loop()) as loop: return loop.create_future()