diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 969e460054..a1e0cd46c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.6', '3.7', '3.8', '3.9'] + python: ['3.6', '3.7', '3.8', '3.9', '3.10'] arch: ['x86', 'x64'] lsp: [''] lsp_extract_file: [''] @@ -41,7 +41,15 @@ jobs: - name: Setup python uses: actions/setup-python@v2 with: - python-version: '${{ matrix.python }}' + # This allows the matrix to specify just the major.minor version while still + # expanding it to get the latest patch version including alpha releases. + # This avoids the need to update for each new alpha, beta, release candidate, + # and then finally an actual release version. actions/setup-python doesn't + # support this for PyPy presently so we get no help there. + # + # CPython -> 3.9.0-alpha - 3.9.X + # PyPy -> pypy-3.7 + python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python), matrix.python))[startsWith(matrix.python, 'pypy')] }} architecture: '${{ matrix.arch }}' - name: Run tests run: ./ci.sh @@ -59,7 +67,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['pypy-3.6', 'pypy-3.7', '3.6', '3.7', '3.8', '3.9', '3.8-dev', '3.9-dev'] + python: ['pypy-3.6', 'pypy-3.7', '3.6', '3.7', '3.8', '3.9', '3.10', '3.8-dev', '3.9-dev', '3.10-dev'] check_formatting: ['0'] pypy_nightly_branch: [''] extra_name: [''] @@ -77,7 +85,7 @@ jobs: uses: actions/setup-python@v2 if: "!endsWith(matrix.python, '-dev')" with: - python-version: '${{ matrix.python }}' + python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python), matrix.python))[startsWith(matrix.python, 'pypy')] }} - name: Setup python (dev) uses: deadsnakes/action@v2.0.2 if: endsWith(matrix.python, '-dev') @@ -98,14 +106,14 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.6', '3.7', '3.8', '3.9'] + python: ['3.6', '3.7', '3.8', '3.9', '3.10'] steps: - name: Checkout uses: actions/checkout@v2 - name: Setup python uses: actions/setup-python@v2 with: - python-version: '${{ matrix.python }}' + python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python), matrix.python))[startsWith(matrix.python, 'pypy')] }} - name: Run tests run: ./ci.sh env: diff --git a/docs/source/reference-lowlevel.rst b/docs/source/reference-lowlevel.rst index ebd0f57c03..2fe773cbb7 100644 --- a/docs/source/reference-lowlevel.rst +++ b/docs/source/reference-lowlevel.rst @@ -785,7 +785,7 @@ Here's how we'd extend our asyncio example to implement this pattern: asyncio_loop.call_soon_threadsafe(fn) # Revised 'done' callback: set a Future - done_fut = asyncio.Future() + done_fut = asyncio_loop.create_future() def done_callback(trio_main_outcome): done_fut.set_result(trio_main_outcome) diff --git a/notes-to-self/aio-guest-test.py b/notes-to-self/aio-guest-test.py index 5e9b398132..b64a11bd04 100644 --- a/notes-to-self/aio-guest-test.py +++ b/notes-to-self/aio-guest-test.py @@ -4,7 +4,7 @@ async def aio_main(): loop = asyncio.get_running_loop() - trio_done_fut = asyncio.Future() + trio_done_fut = loop.create_future() def trio_done_callback(main_outcome): print(f"trio_main finished: {main_outcome!r}") trio_done_fut.set_result(main_outcome) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index 13fc3f3d0f..ed926c029f 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -381,10 +381,13 @@ def traceback_exception_init( limit=None, lookup_lines=True, capture_locals=False, + compact=False, _seen=None, ): - if _seen is None: - _seen = set() + if sys.version_info >= (3, 10): + kwargs = {"compact": compact} + else: + kwargs = {} # Capture the original exception and its cause and context as TracebackExceptions traceback_exception_original_init( @@ -396,8 +399,14 @@ def traceback_exception_init( lookup_lines=lookup_lines, capture_locals=capture_locals, _seen=_seen, + **kwargs, ) + seen_was_none = _seen is None + + if _seen is None: + _seen = set() + # Capture each of the exceptions in the MultiError along with each of their causes and contexts if isinstance(exc_value, MultiError): embedded = [] @@ -411,7 +420,7 @@ def traceback_exception_init( capture_locals=capture_locals, # copy the set of _seen exceptions so that duplicates # shared between sub-exceptions are not omitted - _seen=set(_seen), + _seen=None if seen_was_none else set(_seen), ) ) self.embedded = embedded diff --git a/trio/_core/tests/test_guest_mode.py b/trio/_core/tests/test_guest_mode.py index 0184ff3103..b06849a836 100644 --- a/trio/_core/tests/test_guest_mode.py +++ b/trio/_core/tests/test_guest_mode.py @@ -323,7 +323,7 @@ def aiotrio_run(trio_fn, *, pass_not_threadsafe=True, **start_guest_run_kwargs): loop = asyncio.new_event_loop() async def aio_main(): - trio_done_fut = asyncio.Future() + trio_done_fut = loop.create_future() def trio_done_callback(main_outcome): print(f"trio_fn finished: {main_outcome!r}") diff --git a/trio/_core/tests/test_run.py b/trio/_core/tests/test_run.py index 02f5954e60..c56aaaa092 100644 --- a/trio/_core/tests/test_run.py +++ b/trio/_core/tests/test_run.py @@ -24,6 +24,7 @@ ignore_coroutine_never_awaited_warnings, buggy_pypy_asyncgens, restore_unraisablehook, + create_asyncio_future_in_new_loop, ) from ... import _core @@ -1574,9 +1575,7 @@ async def async_gen(arg): # pragma: no cover def test_calling_asyncio_function_gives_nice_error(): async def child_xyzzy(): - import asyncio - - await asyncio.Future() + await create_asyncio_future_in_new_loop() async def misguided(): await child_xyzzy() @@ -1598,7 +1597,7 @@ async def test_asyncio_function_inside_nursery_does_not_explode(): import asyncio nursery.start_soon(sleep_forever) - await asyncio.Future() + await create_asyncio_future_in_new_loop() assert "asyncio" in str(excinfo.value) diff --git a/trio/_core/tests/tutil.py b/trio/_core/tests/tutil.py index 7f51750869..3e8053f71f 100644 --- a/trio/_core/tests/tutil.py +++ b/trio/_core/tests/tutil.py @@ -1,4 +1,5 @@ # Utilities for testing +import asyncio import socket as stdlib_socket import threading import os @@ -7,7 +8,7 @@ import pytest import warnings -from contextlib import contextmanager +from contextlib import contextmanager, closing import gc @@ -97,7 +98,11 @@ def restore_unraisablehook(): @contextmanager def disable_threading_excepthook(): - threading.excepthook, prev = _noop, 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: @@ -135,3 +140,8 @@ def check_sequence_matches(seq, template): 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() diff --git a/trio/tests/test_exports.py b/trio/tests/test_exports.py index 374ce8c044..ef7edb4ccb 100644 --- a/trio/tests/test_exports.py +++ b/trio/tests/test_exports.py @@ -1,3 +1,4 @@ +import re import sys import importlib import types @@ -69,6 +70,17 @@ def public_modules(module): ) @pytest.mark.parametrize("modname", PUBLIC_MODULE_NAMES) @pytest.mark.parametrize("tool", ["pylint", "jedi"]) +@pytest.mark.filterwarnings( + "ignore:" + + re.escape( + "The distutils package is deprecated and slated for removal in Python 3.12. " + "Use setuptools or check PEP 632 for potential alternatives" + ) + + ":DeprecationWarning", + "ignore:" + + re.escape("The distutils.sysconfig module is deprecated, use sysconfig instead") + + ":DeprecationWarning", +) def test_static_tool_sees_all_symbols(tool, modname): module = importlib.import_module(modname) diff --git a/trio/tests/test_ssl.py b/trio/tests/test_ssl.py index 1a2e66212d..75f99df137 100644 --- a/trio/tests/test_ssl.py +++ b/trio/tests/test_ssl.py @@ -1,3 +1,4 @@ +import re import sys import pytest @@ -1198,6 +1199,9 @@ async def test_selected_npn_protocol_before_handshake(client_ctx): server.selected_npn_protocol() +@pytest.mark.filterwarnings( + r"ignore: ssl module. NPN is deprecated, use ALPN instead:UserWarning" +) async def test_selected_npn_protocol_when_not_set(client_ctx): # NPN protocol still returns None when it's not set, # instead of raising an exception diff --git a/trio/tests/test_util.py b/trio/tests/test_util.py index 2ea0a1e287..2d57e0ebfc 100644 --- a/trio/tests/test_util.py +++ b/trio/tests/test_util.py @@ -3,7 +3,10 @@ import trio from .. import _core -from .._core.tests.tutil import ignore_coroutine_never_awaited_warnings +from .._core.tests.tutil import ( + ignore_coroutine_never_awaited_warnings, + create_asyncio_future_in_new_loop, +) from .._util import ( signal_raise, ConflictDetector, @@ -114,11 +117,11 @@ def generator_based_coro(): # pragma: no cover assert "asyncio" in str(excinfo.value) with pytest.raises(TypeError) as excinfo: - coroutine_or_error(asyncio.Future()) + coroutine_or_error(create_asyncio_future_in_new_loop()) assert "asyncio" in str(excinfo.value) with pytest.raises(TypeError) as excinfo: - coroutine_or_error(lambda: asyncio.Future()) + coroutine_or_error(create_asyncio_future_in_new_loop) assert "asyncio" in str(excinfo.value) with pytest.raises(TypeError) as excinfo: