Skip to content

Commit

Permalink
test: allow to select async backend and implementation
Browse files Browse the repository at this point in the history
We add a --anyio={asyncio,trio} option to pytest to select the AnyIO
backend to run async tests. When this option is set, we'll use the AnyIO
implementations of psycopg API (i.e. AnyIOConnection, resp. waiting
functions, etc.); otherwise (the default), we'll use plain asyncio
implementations (i.e. AsyncConnection).

This way, we can now run the test suite with:
- plain asyncio implementations (previously achieved through the asyncio
  backend for AnyIO pytest plugin),
- AnyIO implementations, with asyncio backend (new from this commit),
- AnyIO implementations, with trio backend (previously achieved through
  the Trio backend for AnyIO pytest plugin).

Accordingly, the anyio_backend fixture is no longer parametrized and its
value depends on the --anyio option.

Selection of whether to use AnyIO or plain asyncio implementations is
done through the new 'use_anyio' fixture, which simply detects if no
--anyio option got specified.

This new fixture is used everywhere we used anyio_backend_name fixture
previously to select either plain asyncio or anyio variants of test
cases. The fixture pulls (but does not use) anyio_backend so that all
tests using it will be detected as async.

In CI, we add a new pytest_opts axis to the matrix to cover all
configurations in various environments.
  • Loading branch information
dlax committed Jan 27, 2023
1 parent ee3c3d6 commit 8ceaa18
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 80 deletions.
74 changes: 39 additions & 35 deletions .github/workflows/tests.yml
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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
Expand Down
45 changes: 33 additions & 12 deletions tests/conftest.py
Expand Up @@ -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"],
Expand All @@ -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
Expand All @@ -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")
Expand Down
14 changes: 4 additions & 10 deletions tests/fix_db.py
Expand Up @@ -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")
Expand Down
9 changes: 3 additions & 6 deletions tests/pool/conftest.py
Expand Up @@ -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
4 changes: 2 additions & 2 deletions tests/test_connection_async.py
Expand Up @@ -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):
Expand All @@ -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)
Expand Down
7 changes: 2 additions & 5 deletions tests/test_copy_async.py
Expand Up @@ -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(
Expand Down
14 changes: 4 additions & 10 deletions tests/test_waiting.py
Expand Up @@ -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)
Expand Down

0 comments on commit 8ceaa18

Please sign in to comment.