diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0d29bdb3e..c02fe59d0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,28 +26,29 @@ jobs: matrix: include: # Test different combinations of Python, Postgres, libpq. - - {impl: python, python: "3.7", postgres: "postgres:10", libpq: newest} - - {impl: python, python: "3.8", postgres: "postgres:12"} - - {impl: python, python: "3.9", postgres: "postgres:13"} - - {impl: python, python: "3.10", postgres: "postgres:14"} - - {impl: python, python: "3.11", postgres: "postgres:15", libpq: oldest} + - {impl: python, python: "3.7", postgres: "postgres:10", libpq: newest, pytest_opts: "--anyio=asyncio"} + - {impl: python, python: "3.8", postgres: "postgres:12", pytest_opts: ""} + - {impl: python, python: "3.9", postgres: "postgres:13", pytest_opts: "--anyio=trio"} + - {impl: python, python: "3.10", postgres: "postgres:14", pytest_opts: ""} + - {impl: python, python: "3.11", postgres: "postgres:15", libpq: oldest, pytest_opts: ""} - - {impl: c, python: "3.7", postgres: "postgres:15", libpq: newest} - - {impl: c, python: "3.8", postgres: "postgres:13"} - - {impl: c, python: "3.9", postgres: "postgres:14"} - - {impl: c, python: "3.10", postgres: "postgres:11", libpq: oldest} - - {impl: c, python: "3.11", postgres: "postgres:10", libpq: newest} + - {impl: c, python: "3.7", postgres: "postgres:15", libpq: newest, pytest_opts: ""} + - {impl: c, python: "3.8", postgres: "postgres:13", pytest_opts: "--anyio=asyncio"} + - {impl: c, python: "3.9", postgres: "postgres:14", pytest_opts: ""} + - {impl: c, python: "3.10", postgres: "postgres:11", libpq: oldest, pytest_opts: "--anyio=trio"} + - {impl: c, python: "3.11", postgres: "postgres:10", libpq: newest, pytest_opts: ""} - - {impl: python, python: "3.9", ext: dns, postgres: "postgres:14"} - - {impl: python, python: "3.9", ext: postgis, postgres: "postgis/postgis"} + - {impl: python, python: "3.9", ext: dns, postgres: "postgres:14", pytest_opts: ""} + - {impl: python, python: "3.9", ext: postgis, postgres: "postgis/postgis", pytest_opts: ""} # Test with minimum dependencies versions - - {impl: c, python: "3.7", ext: min, postgres: "postgres:15"} + - {impl: c, python: "3.7", ext: min, postgres: "postgres:15", pytest_opts: ""} env: PSYCOPG_IMPL: ${{ matrix.impl }} DEPS: ./psycopg[test] ./psycopg_pool PSYCOPG_TEST_DSN: "host=127.0.0.1 user=postgres password=password" + PYTEST_ADDOPTS: ${{ matrix.pytest_opts }} MARKERS: "" steps: @@ -110,21 +111,22 @@ jobs: fail-fast: false matrix: include: - - {impl: python, python: "3.7"} - - {impl: python, python: "3.8"} - - {impl: python, python: "3.9"} - - {impl: python, python: "3.10"} - - {impl: python, python: "3.11"} - - {impl: c, python: "3.7"} - - {impl: c, python: "3.8"} - - {impl: c, python: "3.9"} - - {impl: c, python: "3.10"} - - {impl: c, python: "3.11"} + - {impl: python, python: "3.7", pytest_opts: ""} + - {impl: python, python: "3.8", pytest_opts: "--anyio=asyncio"} + - {impl: python, python: "3.9", pytest_opts: ""} + - {impl: python, python: "3.10", pytest_opts: "--anyio=trio"} + - {impl: python, python: "3.11", pytest_opts: ""} + - {impl: c, python: "3.7", pytest_opts: "--anyio=trio"} + - {impl: c, python: "3.8", pytest_opts: ""} + - {impl: c, python: "3.9", pytest_opts: "--anyio=asyncio"} + - {impl: c, python: "3.10", pytest_opts: ""} + - {impl: c, python: "3.11", pytest_opts: ""} env: PSYCOPG_IMPL: ${{ matrix.impl }} DEPS: ./psycopg[test] ./psycopg_pool PSYCOPG_TEST_DSN: "host=127.0.0.1 user=runner dbname=postgres" + PYTEST_ADDOPTS: ${{ matrix.pytest_opts }} # MacOS on GitHub Actions seems particularly slow. # Don't run timing-based tests as they regularly fail. # pproxy-based tests fail too, with the proxy not coming up in 2s. @@ -165,21 +167,22 @@ jobs: fail-fast: false matrix: include: - - {impl: python, python: "3.7"} - - {impl: python, python: "3.8"} - - {impl: python, python: "3.9"} - - {impl: python, python: "3.10"} - - {impl: python, python: "3.11"} - - {impl: c, python: "3.7"} - - {impl: c, python: "3.8"} - - {impl: c, python: "3.9"} - - {impl: c, python: "3.10"} - - {impl: c, python: "3.11"} + - {impl: python, python: "3.7", pytest_opts: "--anyio=asyncio"} + - {impl: python, python: "3.8", pytest_opts: ""} + - {impl: python, python: "3.9", pytest_opts: "--anyio=trio"} + - {impl: python, python: "3.10", pytest_opts: ""} + - {impl: python, python: "3.11", pytest_opts: ""} + - {impl: c, python: "3.7", pytest_opts: ""} + - {impl: c, python: "3.8", pytest_opts: "--anyio=trio"} + - {impl: c, python: "3.9", pytest_opts: ""} + - {impl: c, python: "3.10", pytest_opts: "--anyio=asyncio"} + - {impl: c, python: "3.11", pytest_opts: ""} env: PSYCOPG_IMPL: ${{ matrix.impl }} DEPS: ./psycopg[test] ./psycopg_pool PSYCOPG_TEST_DSN: "host=127.0.0.1 dbname=postgres" + PYTEST_ADDOPTS: ${{ matrix.pytest_opts }} # On windows pproxy doesn't seem very happy. Also a few timing test fail. NOT_MARKERS: "timing proxy mypy" @@ -235,12 +238,13 @@ jobs: fail-fast: false matrix: include: - - {impl: c, crdb: "latest-v22.1", python: "3.10", libpq: newest} - - {impl: python, crdb: "latest-v22.2", python: "3.11"} + - {impl: c, crdb: "latest-v22.1", python: "3.10", libpq: newest, pytest_opts: "--anyio=trio"} + - {impl: python, crdb: "latest-v22.2", python: "3.11", pytest_opts: ""} env: PSYCOPG_IMPL: ${{ matrix.impl }} DEPS: ./psycopg[test] ./psycopg_pool PSYCOPG_TEST_DSN: "host=127.0.0.1 port=26257 user=root dbname=defaultdb" + PYTEST_ADDOPTS: ${{ matrix.pytest_opts }} steps: - uses: actions/checkout@v3 diff --git a/tests/conftest.py b/tests/conftest.py index 9dadf1168..1fa272552 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,6 +34,15 @@ def pytest_configure(config): def pytest_addoption(parser): + parser.addoption( + "--anyio", + choices=["asyncio", "trio"], + help=( + "Use AnyIO implementation of the async API, run tests with " + "specified backend. If unset, use plain asyncio implementation, " + "and run with asyncio backend." + ), + ) parser.addoption( "--loop", choices=["default", "uvloop"], @@ -46,8 +55,15 @@ def pytest_report_header(config): rv = [] rv.append(f"default selector: {selectors.DefaultSelector.__name__}") + backend = config.getoption("--anyio") + if backend: + rv.append(f"AnyIO backend: {backend}") loop = config.getoption("--loop") if loop != "default": + if backend not in (None, "asyncio"): + raise pytest.UsageError( + f"--loop={loop} is incompatible with --anyio={backend}" + ) rv.append(f"asyncio loop: {loop}") return rv @@ -67,23 +83,28 @@ def pytest_sessionstart(session): cache.set("segfault", True) +@pytest.fixture(scope="session") +def use_anyio(pytestconfig, anyio_backend): + """True if AnyIO-based implementations of Psycopg API should be used.""" + return pytestconfig.option.anyio is not None + + asyncio_options: Dict[str, Any] = {} if sys.platform == "win32" and sys.version_info >= (3, 8): asyncio_options["policy"] = asyncio.WindowsSelectorEventLoopPolicy() -@pytest.fixture( - params=[ - pytest.param(("asyncio", asyncio_options.copy()), id="asyncio"), - pytest.param(("trio", {}), id="trio"), - ], - scope="session", -) -def anyio_backend(request): - backend, options = request.param - if request.config.option.loop == "uvloop": - options["use_uvloop"] = True - return backend, options +@pytest.fixture(scope="session") +def anyio_backend(pytestconfig): + opt = pytestconfig.option.anyio + if opt in (None, "asyncio"): + options = asyncio_options.copy() + if pytestconfig.option.loop == "uvloop": + options["use_uvloop"] = True + return "asyncio", options + else: + assert opt == "trio" + return "trio", {} @pytest.fixture(scope="session") diff --git a/tests/fix_db.py b/tests/fix_db.py index 7542ae8ae..7d524ed5a 100644 --- a/tests/fix_db.py +++ b/tests/fix_db.py @@ -239,21 +239,15 @@ def conn_cls(session_dsn): @pytest.fixture(scope="session") -def aconn_cls(session_dsn, anyio_backend_name): - cls_by_backend = { - "asyncio": psycopg.AsyncConnection, - "trio": psycopg.AnyIOConnection, - } +def aconn_cls(session_dsn, use_anyio): + cls = psycopg.AnyIOConnection if use_anyio else psycopg.AsyncConnection if crdb_version: from psycopg.crdb import AsyncCrdbConnection, AnyIOCrdbConnection - cls_by_backend = { - "asyncio": AsyncCrdbConnection, - "trio": AnyIOCrdbConnection, - } + cls = AnyIOCrdbConnection if use_anyio else AsyncCrdbConnection - return cls_by_backend[anyio_backend_name] + return cls @pytest.fixture(scope="session") diff --git a/tests/pool/conftest.py b/tests/pool/conftest.py index a4d1f3564..052794f3c 100644 --- a/tests/pool/conftest.py +++ b/tests/pool/conftest.py @@ -3,12 +3,9 @@ from ..conftest import asyncio_options -@pytest.fixture( - params=[pytest.param(("asyncio", asyncio_options.copy()), id="asyncio")], - scope="session", -) +@pytest.fixture(scope="session") def anyio_backend(request): - backend, options = request.param + options = asyncio_options.copy() if request.config.option.loop == "uvloop": options["use_uvloop"] = True - return backend, options + return "asyncio", options diff --git a/tests/test_connection_async.py b/tests/test_connection_async.py index 34d2ec3b7..076899493 100644 --- a/tests/test_connection_async.py +++ b/tests/test_connection_async.py @@ -737,7 +737,7 @@ async def test_cancel_closed(aconn): @pytest.fixture -async def fake_resolve(monkeypatch, anyio_backend_name): +async def fake_resolve(monkeypatch, use_anyio): fake_hosts = {"foo.com": "1.1.1.1"} async def fake_getaddrinfo(host, port, **kwargs): @@ -748,7 +748,7 @@ async def fake_getaddrinfo(host, port, **kwargs): else: return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (addr, 432))] - if anyio_backend_name == "asyncio": + if not use_anyio: monkeypatch.setattr(asyncio.get_running_loop(), "getaddrinfo", fake_getaddrinfo) else: monkeypatch.setattr(anyio, "getaddrinfo", fake_getaddrinfo) diff --git a/tests/test_copy_async.py b/tests/test_copy_async.py index 25d9abb1e..bd7b46cd6 100644 --- a/tests/test_copy_async.py +++ b/tests/test_copy_async.py @@ -645,11 +645,8 @@ async def test_description(aconn): @pytest.fixture -def writercls(anyio_backend_name): - if anyio_backend_name == "asyncio": - return AsyncQueuedLibpqWriter - else: - return AnyIOLibpqWriter +def writercls(use_anyio): + return AnyIOLibpqWriter if use_anyio else AsyncQueuedLibpqWriter @pytest.mark.parametrize( diff --git a/tests/test_waiting.py b/tests/test_waiting.py index f020d4815..a071471d4 100644 --- a/tests/test_waiting.py +++ b/tests/test_waiting.py @@ -116,19 +116,13 @@ def test_wait_large_fd(dsn, waitfn): @pytest.fixture -def wait_async(anyio_backend_name): - return { - "asyncio": waiting.wait_asyncio, - "trio": waiting_anyio.wait, - }[anyio_backend_name] +def wait_async(use_anyio): + return waiting_anyio.wait if use_anyio else waiting.wait_asyncio @pytest.fixture -def wait_conn_async(anyio_backend_name): - return { - "asyncio": waiting.wait_conn_asyncio, - "trio": waiting_anyio.wait_conn, - }[anyio_backend_name] +def wait_conn_async(use_anyio): + return waiting_anyio.wait_conn if use_anyio else waiting.wait_conn_asyncio @pytest.mark.parametrize("timeout", timeouts)