]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
test: use anyio instead of pytest-asyncio 352/head
authorDenis Laxalde <denis.laxalde@dalibo.com>
Wed, 17 Aug 2022 13:59:31 +0000 (15:59 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 4 Feb 2023 11:47:43 +0000 (12:47 +0100)
This is in preparation for adding support for other async libraries,
through anyio. AnyIO pytest plugin is used in replacement for
pytest-asyncio:

- either the pytest.mark.asyncio is replaced by pytest.mark.anyio, or,
- we rely on the 'anyio_backend' fixture that is pulled in 'aconn_cls'
  fixture (and hence 'aconn') providing automatic detection for test
  functions using it.

The 'anyio_backend' fixture is parametrized to only use asyncio and
selects the event loop policy we need on Windows platform as previously
done in pytest_sessionstart(), but only for Python version 3.8 or
higher.

This fixture is defined in main conftest.py, as well as in
pool/conftest.py since we'll change the former to support more async
backend while keeping the later asyncio-only for now.

Function test_concurrency_async.py::test_ctrl_c is no longer 'async'
because its code does not directly use asyncio (it's done through a
subprocess); but the 'async def' was needed before in order for
pytest-asyncio to run it since the test module had a global
pytest.mark.asyncio (and we were using the "auto" mode).

32 files changed:
psycopg/setup.cfg
pyproject.toml
tests/conftest.py
tests/constraints.txt
tests/crdb/test_connection_async.py
tests/crdb/test_copy_async.py
tests/crdb/test_cursor_async.py
tests/fix_db.py
tests/pool/conftest.py [new file with mode: 0644]
tests/pool/test_null_pool_async.py
tests/pool/test_pool_async.py
tests/pool/test_sched_async.py
tests/test_client_cursor_async.py
tests/test_concurrency_async.py
tests/test_connection_async.py
tests/test_conninfo.py
tests/test_copy_async.py
tests/test_cursor_async.py
tests/test_dns.py
tests/test_dns_srv.py
tests/test_errors.py
tests/test_pipeline_async.py
tests/test_prepared_async.py
tests/test_server_cursor_async.py
tests/test_tpc_async.py
tests/test_transaction_async.py
tests/test_typeinfo.py
tests/test_waiting.py
tests/types/test_composite.py
tests/types/test_enum.py
tests/types/test_multirange.py
tests/types/test_range.py

index 089fa4ee276f8cd0948a53614f59a54d4a0da69b..63abf750bc161a31c57898465ab44cf0f85b39ea 100644 (file)
@@ -65,10 +65,10 @@ binary =
 pool =
     psycopg-pool
 test =
+    anyio >= 3.6.2
     mypy >= 0.990
     pproxy >= 2.7
     pytest >= 6.2.5
-    pytest-asyncio >= 0.17
     pytest-cov >= 3.0
     pytest-randomly >= 3.5
 dev =
index 14f3c9e0d4f4ed6ede6b50611d0a81f4b5bb2783..950bccb4a7bbf60e42b759c6c93a575ddd582083 100644 (file)
@@ -3,7 +3,6 @@ requires = ["setuptools>=49.2.0", "wheel>=0.37"]
 build-backend = "setuptools.build_meta"
 
 [tool.pytest.ini_options]
-asyncio_mode = "auto"
 filterwarnings = [
     "error",
 ]
index 15bcf4099ee11dd0ffc39a0f9c86e8022fcf9f50..1ec997bf9872e291bb1e50d9eb9834c147c5e31e 100644 (file)
@@ -1,7 +1,9 @@
 import sys
 import asyncio
 import selectors
-from typing import List
+from typing import Any, Dict, List
+
+import pytest
 
 pytest_plugins = (
     "tests.fix_db",
@@ -64,17 +66,21 @@ def pytest_sessionstart(session):
         raise session.Failed
     cache.set("segfault", True)
 
-    # Configure the async loop.
-    loop = session.config.getoption("--loop")
-    if loop == "uvloop":
-        import uvloop
 
-        uvloop.install()
-    else:
-        assert loop == "default"
+asyncio_options: Dict[str, Any] = {}
+if sys.platform == "win32" and sys.version_info >= (3, 8):
+    asyncio_options["policy"] = asyncio.WindowsSelectorEventLoopPolicy()
+
 
-    if sys.platform == "win32":
-        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+@pytest.fixture(
+    params=[pytest.param(("asyncio", asyncio_options.copy()), id="asyncio")],
+    scope="session",
+)
+def anyio_backend(request):
+    backend, options = request.param
+    if request.config.option.loop == "uvloop":
+        options["use_uvloop"] = True
+    return backend, options
 
 
 allow_fail_messages: List[str] = []
index a254f29076c10651ca07b94eff14e36534796d1c..d4b9ffc061edf6e8e4c57b7506aecdc043e0fcab 100644 (file)
@@ -9,10 +9,10 @@ typing-extensions == 4.1.0
 importlib-metadata == 1.4
 
 # From the 'test' extra
+anyio == 3.6.2
 mypy == 0.990
 pproxy == 2.7.0
 pytest == 6.2.5
-pytest-asyncio == 0.17.0
 pytest-cov == 3.0.0
 pytest-randomly == 3.5.0
 
index b568e426e3c187121eece2054fa463e1bd28dc1f..9cd73ddd7926a0da45baa4ea9a6cd0bf5d373525 100644 (file)
@@ -8,7 +8,7 @@ from psycopg._compat import create_task
 
 import pytest
 
-pytestmark = [pytest.mark.crdb, pytest.mark.asyncio]
+pytestmark = [pytest.mark.crdb, pytest.mark.anyio]
 
 
 async def test_is_crdb(aconn):
index d5fbf50d0b35c20ed57c553cceac1ccf52314ee9..45ee5eca08c44a05f773aedfaddfc77ac3fbf815 100644 (file)
@@ -13,7 +13,7 @@ from ..test_copy import sample_records
 from ..test_copy_async import ensure_table
 from .test_copy import sample_tabledef, copyopt
 
-pytestmark = [pytest.mark.crdb, pytest.mark.asyncio]
+pytestmark = [pytest.mark.crdb, pytest.mark.anyio]
 
 
 @pytest.mark.parametrize(
index fcc7760a94915af18387a5e5ca8ffccb976b86f6..2472c37f20a97bc2ba591eef67a8f42c3ac2ef4f 100644 (file)
@@ -12,7 +12,7 @@ from .test_cursor import testfeed
 
 testfeed  # fixture
 
-pytestmark = [pytest.mark.crdb, pytest.mark.asyncio]
+pytestmark = [pytest.mark.crdb, pytest.mark.anyio]
 
 
 @pytest.mark.slow
index 3a37aa10a2a81dd231c9f2a4516662ca9f8659a5..890e4ed5a167c050cb0b3a864253d2a6f235fc01 100644 (file)
@@ -239,7 +239,7 @@ def conn_cls(session_dsn):
 
 
 @pytest.fixture(scope="session")
-def aconn_cls(session_dsn):
+def aconn_cls(session_dsn, anyio_backend):
     cls = psycopg.AsyncConnection
     if crdb_version:
         from psycopg.crdb import AsyncCrdbConnection
diff --git a/tests/pool/conftest.py b/tests/pool/conftest.py
new file mode 100644 (file)
index 0000000..a4d1f35
--- /dev/null
@@ -0,0 +1,14 @@
+import pytest
+
+from ..conftest import asyncio_options
+
+
+@pytest.fixture(
+    params=[pytest.param(("asyncio", asyncio_options.copy()), id="asyncio")],
+    scope="session",
+)
+def anyio_backend(request):
+    backend, options = request.param
+    if request.config.option.loop == "uvloop":
+        options["use_uvloop"] = True
+    return backend, options
index d33eecb4d10844661e7edda9184376c98ee6a1aa..fea47fbf45af566cfd1c27793ccfb8bf9129f6d7 100644 (file)
@@ -11,7 +11,7 @@ from psycopg.pq import TransactionStatus
 from psycopg._compat import create_task
 from .test_pool_async import delay_connection, ensure_waiting
 
-pytestmark = [pytest.mark.asyncio]
+pytestmark = [pytest.mark.anyio]
 
 try:
     from psycopg_pool import AsyncNullConnectionPool  # noqa: F401
index 27f9f452c40b3dc00fa09a1a26ff96414d01deb2..1f16ae2f3d73c57c347db3ca4842107c116a9ba0 100644 (file)
@@ -15,7 +15,7 @@ except ImportError:
     # Tests should have been skipped if the package is not available
     pass
 
-pytestmark = [pytest.mark.asyncio]
+pytestmark = [pytest.mark.anyio]
 
 
 async def test_defaults(dsn):
index 492d620597be4a1b76fe81a2ae7c44462a0c19f6..28df510c20ee0acb539880fcc3aa94285160f11b 100644 (file)
@@ -13,7 +13,7 @@ except ImportError:
     # Tests should have been skipped if the package is not available
     pass
 
-pytestmark = [pytest.mark.asyncio, pytest.mark.timing]
+pytestmark = [pytest.mark.anyio, pytest.mark.timing]
 
 
 @pytest.mark.slow
index 50f08f7ef318d94ec81da81af6e1f8ae51d512fb..ec6504d07906881ede7a36639ee9a0fea478047c 100644 (file)
@@ -14,7 +14,7 @@ from .test_cursor import execmany, _execmany  # noqa: F401
 from .fix_crdb import crdb_encoding
 
 execmany = execmany  # avoid F811 underneath
-pytestmark = pytest.mark.asyncio
+pytestmark = pytest.mark.anyio
 
 
 @pytest.fixture
index 29b08cf1b9c9d3e9e4fb8eb07631dcc9cf7aecc1..67bb6afbd5fdc75bd2b0672f223b1d1985c2d394 100644 (file)
@@ -12,8 +12,6 @@ import psycopg
 from psycopg import errors as e
 from psycopg._compat import create_task
 
-pytestmark = pytest.mark.asyncio
-
 
 @pytest.mark.slow
 async def test_commit_concurrency(aconn):
@@ -194,7 +192,7 @@ async def test_identify_closure(aconn_cls, dsn):
     sys.platform == "win32", reason="don't know how to Ctrl-C on Windows"
 )
 @pytest.mark.crdb_skip("cancel")
-async def test_ctrl_c(dsn):
+def test_ctrl_c(dsn):
     script = f"""\
 import signal
 import asyncio
index 1288a6c817a4081136f5f6919ae877517989d8a5..fe1a403f1700f49f91dd6bb11fdccda0343e2fbb 100644 (file)
@@ -17,7 +17,7 @@ from .test_connection import testctx  # noqa: F401  # fixture
 from .test_adapt import make_bin_dumper, make_dumper
 from .test_conninfo import fake_resolve  # noqa: F401
 
-pytestmark = pytest.mark.asyncio
+pytestmark = pytest.mark.anyio
 
 
 async def test_connect(aconn_cls, dsn):
index e2c2c01b93c23fb5169413bb77dde2dddaeaefb2..56a944ff17b754160fd3eb383f2ce53c29144a53 100644 (file)
@@ -348,7 +348,7 @@ class TestConnectionInfo:
         ),
     ],
 )
-@pytest.mark.asyncio
+@pytest.mark.anyio
 async def test_resolve_hostaddr_async_no_resolve(
     setpgenv, conninfo, want, env, fail_resolve
 ):
@@ -398,7 +398,7 @@ async def test_resolve_hostaddr_async_no_resolve(
         ),
     ],
 )
-@pytest.mark.asyncio
+@pytest.mark.anyio
 async def test_resolve_hostaddr_async(conninfo, want, env, fake_resolve):
     params = conninfo_to_dict(conninfo)
     params = await resolve_hostaddr_async(params)
@@ -414,7 +414,7 @@ async def test_resolve_hostaddr_async(conninfo, want, env, fake_resolve):
         ("host=1.1.1.1,2.2.2.2", {"PGPORT": "1,2,3"}),
     ],
 )
-@pytest.mark.asyncio
+@pytest.mark.anyio
 async def test_resolve_hostaddr_async_bad(setpgenv, conninfo, env, fake_resolve):
     setpgenv(env)
     params = conninfo_to_dict(conninfo)
index 59389dd7368cf43925567a24a3ae837748d44252..79e97c0c6dbdbe40e80b675ec6dc9ff783767869 100644 (file)
@@ -24,7 +24,6 @@ from .test_copy import sample_values, sample_records, sample_tabledef
 from .test_copy import py_to_raw, special_chars
 
 pytestmark = [
-    pytest.mark.asyncio,
     pytest.mark.crdb_skip("copy"),
 ]
 
index fdc3d898f4d985f21f0d270b67c5c094e04aa134..e2081535523655f759030d6cef479548feca1195 100644 (file)
@@ -13,7 +13,6 @@ from .test_cursor import execmany, _execmany  # noqa: F401
 from .fix_crdb import crdb_encoding
 
 execmany = execmany  # avoid F811 underneath
-pytestmark = pytest.mark.asyncio
 
 
 async def test_init(aconn):
index f50092ffbe5809dbe581a4e17c5401b3a536ece6..2eb5569df944309ac2ae0814145365ab11adfa56 100644 (file)
@@ -6,7 +6,7 @@ from psycopg.conninfo import conninfo_to_dict
 pytestmark = [pytest.mark.dns]
 
 
-@pytest.mark.asyncio
+@pytest.mark.anyio
 async def test_resolve_hostaddr_async_warning(recwarn):
     import_dnspython()
     conninfo = "dbname=foo"
index 857491ccd1b4afd0c3d4f375760b9632eb505974..5244271314da21c9163a982b75146d30b8e3725d 100644 (file)
@@ -52,7 +52,7 @@ def test_srv(conninfo, want, env, fake_srv, setpgenv):
     assert conninfo_to_dict(want) == params
 
 
-@pytest.mark.asyncio
+@pytest.mark.anyio
 @pytest.mark.parametrize("conninfo, want, env", samples_ok)
 async def test_srv_async(conninfo, want, env, afake_srv, setpgenv):
     setpgenv(env)
@@ -75,7 +75,7 @@ def test_srv_bad(conninfo, env, fake_srv, setpgenv):
         psycopg._dns.resolve_srv(params)  # type: ignore[attr-defined]
 
 
-@pytest.mark.asyncio
+@pytest.mark.anyio
 @pytest.mark.parametrize("conninfo,  env", samples_bad)
 async def test_srv_bad_async(conninfo, env, afake_srv, setpgenv):
     setpgenv(env)
index 23ad314fdb47b9b846f0c432d78e57d518b621a5..b2ca66c1273b0c41de98239d96eef95f5ab1de3a 100644 (file)
@@ -216,7 +216,6 @@ def test_diag_from_commit(conn):
     assert exc.value.diag.sqlstate == "23503"
 
 
-@pytest.mark.asyncio
 @pytest.mark.crdb_skip("deferrable")
 async def test_diag_from_commit_async(aconn):
     cur = aconn.cursor()
index 1dc6110858db0bb183427b23d0a624a514977277..bdaf5a5ebffffd8868a887cbdc4999704640e593 100644 (file)
@@ -13,7 +13,6 @@ from psycopg import errors as e
 from .test_pipeline import pipeline_aborted
 
 pytestmark = [
-    pytest.mark.asyncio,
     pytest.mark.pipeline,
     pytest.mark.skipif("not psycopg.AsyncPipeline.is_supported()"),
 ]
index 84d948f6553d7f13b7642577fa78edfe77727bb3..afe7d4db52cb898f3549946c4548e6483030ef86 100644 (file)
@@ -9,8 +9,6 @@ import pytest
 
 from psycopg.rows import namedtuple_row
 
-pytestmark = pytest.mark.asyncio
-
 
 @pytest.mark.parametrize("value", [None, 0, 3])
 async def test_prepare_threshold_init(aconn_cls, dsn, value):
index 21b434503d9ecdda550c7dae521ef7ad9f1004f1..8325639c1d8ec040513708df40ec7201aecf718d 100644 (file)
@@ -5,7 +5,6 @@ from psycopg import rows, errors as e
 from psycopg.pq import Format
 
 pytestmark = [
-    pytest.mark.asyncio,
     pytest.mark.crdb_skip("server-side cursor"),
 ]
 
index a829c4c12dd5d70b4685574ede73306f7ac73628..05a547c5dc632af1a049266fccf7b83e4d496ded 100644 (file)
@@ -4,7 +4,6 @@ import psycopg
 from psycopg.pq import TransactionStatus
 
 pytestmark = [
-    pytest.mark.asyncio,
     pytest.mark.crdb_skip("2-phase commit"),
 ]
 
index 55e1c9c790ff4746a7f491498626a8f26b671a36..921ea5a1458676f13ec4962c6f6a76608a576e3e 100644 (file)
@@ -11,11 +11,9 @@ from .test_transaction import in_transaction, insert_row, inserted, get_exc_info
 from .test_transaction import ExpectedException, crdb_skip_external_observer
 from .test_transaction import create_test_table  # noqa  # autouse fixture
 
-pytestmark = pytest.mark.asyncio
-
 
 @pytest.fixture
-async def aconn(aconn, apipeline):
+async def aconn(aconn, apipeline, anyio_backend):
     return aconn
 
 
index 1d74ef2bffea19f829ae9036c38cf4b092364231..aaed8d0488b1d10ecce250f003be250c127a8b16 100644 (file)
@@ -42,7 +42,6 @@ def test_fetch(conn, name, status, encoding):
     assert info.regtype == "text"
 
 
-@pytest.mark.asyncio
 @pytest.mark.parametrize("name", ["text", sql.Identifier("text")])
 @pytest.mark.parametrize("status", ["IDLE", "INTRANS", None])
 @pytest.mark.parametrize(
@@ -111,7 +110,6 @@ def test_fetch_not_found(conn, name, status, info_cls, monkeypatch):
     assert info is None
 
 
-@pytest.mark.asyncio
 @_name
 @_status
 @_info_cls
index 63237e8792e14e1a103d155cefdd0b45f87bd2ab..f5ece29e5c7e447ea614caa80b81aa8a9dad366b 100644 (file)
@@ -114,21 +114,21 @@ def test_wait_large_fd(dsn, waitfn):
 
 
 @pytest.mark.parametrize("timeout", timeouts)
-@pytest.mark.asyncio
+@pytest.mark.anyio
 async def test_wait_conn_async(dsn, timeout):
     gen = generators.connect(dsn)
     conn = await waiting.wait_conn_async(gen, **timeout)
     assert conn.status == ConnStatus.OK
 
 
-@pytest.mark.asyncio
+@pytest.mark.anyio
 async def test_wait_conn_async_bad(dsn):
     gen = generators.connect("dbname=nosuchdb")
     with pytest.raises(psycopg.OperationalError):
         await waiting.wait_conn_async(gen)
 
 
-@pytest.mark.asyncio
+@pytest.mark.anyio
 @pytest.mark.parametrize("wait, ready", zip(waiting.Wait, waiting.Ready))
 @skip_if_not_linux
 async def test_wait_ready_async(wait, ready):
@@ -141,7 +141,7 @@ async def test_wait_ready_async(wait, ready):
     assert r & ready
 
 
-@pytest.mark.asyncio
+@pytest.mark.anyio
 async def test_wait_async(pgconn):
     pgconn.send_query(b"select 1")
     gen = generators.execute(pgconn)
@@ -149,7 +149,7 @@ async def test_wait_async(pgconn):
     assert res.status == ExecStatus.TUPLES_OK
 
 
-@pytest.mark.asyncio
+@pytest.mark.anyio
 async def test_wait_async_bad(pgconn):
     pgconn.send_query(b"select 1")
     gen = generators.execute(pgconn)
index 47beecf352158b973ce72589bd8c093c84a9efa5..49e734bf44bef432cbeb91cffe54953e01ff7a19 100644 (file)
@@ -166,7 +166,6 @@ def test_fetch_info(conn, testcomp, name, fields):
         assert info.field_types[i] == builtins[t].oid
 
 
-@pytest.mark.asyncio
 @pytest.mark.parametrize("name, fields", fetch_cases)
 async def test_fetch_info_async(aconn, testcomp, name, fields):
     info = await CompositeInfo.fetch(aconn, name)
index 8dfb6d4f0d381f26083d677b6ebe9418e970248d..e40c610257d3c99b78be2288576b78b4a35238b3 100644 (file)
@@ -68,7 +68,6 @@ def test_fetch_info(conn):
     assert info.labels == list(StrTestEnum.__members__)
 
 
-@pytest.mark.asyncio
 async def test_fetch_info_async(aconn):
     info = await EnumInfo.fetch(aconn, "PureTestEnum")
     assert info.name == "puretestenum"
index 2ab51524e3eef0bcf50b3a86b33687694920474e..65ff0fe0e08513b3c458c60897c3bed0cfddc3fc 100644 (file)
@@ -387,7 +387,6 @@ def test_fetch_info_not_found(conn):
     assert MultirangeInfo.fetch(conn, "nosuchrange") is None
 
 
-@pytest.mark.asyncio
 @pytest.mark.parametrize("name, subtype", fetch_cases)
 async def test_fetch_info_async(aconn, testmr, name, subtype):  # noqa: F811
     info = await MultirangeInfo.fetch(aconn, name)
@@ -397,7 +396,6 @@ async def test_fetch_info_async(aconn, testmr, name, subtype):  # noqa: F811
     assert info.subtype_oid == aconn.adapters.types[subtype].oid
 
 
-@pytest.mark.asyncio
 async def test_fetch_info_not_found_async(aconn):
     assert await MultirangeInfo.fetch(aconn, "nosuchrange") is None
 
index 1efd398bb80dd6361ed8634da64d41db3b70b3d4..b1026d3027c6f108d554340616184261e3a45ec4 100644 (file)
@@ -303,7 +303,6 @@ def test_fetch_info_not_found(conn):
     assert RangeInfo.fetch(conn, "nosuchrange") is None
 
 
-@pytest.mark.asyncio
 @pytest.mark.parametrize("name, subtype", fetch_cases)
 async def test_fetch_info_async(aconn, testrange, name, subtype):
     info = await RangeInfo.fetch(aconn, name)
@@ -313,7 +312,6 @@ async def test_fetch_info_async(aconn, testrange, name, subtype):
     assert info.subtype_oid == aconn.adapters.types[subtype].oid
 
 
-@pytest.mark.asyncio
 async def test_fetch_info_not_found_async(aconn):
     assert await RangeInfo.fetch(aconn, "nosuchrange") is None