assert elapsed == pytest.approx(1.0, abs=0.05)
+@pytest.mark.slow
+@pytest.mark.timing
+def test_multi_hosts(conn_cls, proxy, dsn, deaf_port, monkeypatch):
+ args = conninfo_to_dict(dsn)
+ args["host"] = f"{proxy.client_host},{proxy.server_host}"
+ args["port"] = f"{deaf_port},{proxy.server_port}"
+ args.pop("hostaddr", None)
+ monkeypatch.setattr(conn_cls, "_DEFAULT_CONNECT_TIMEOUT", 2)
+ t0 = time.time()
+ with conn_cls.connect(**args) as conn:
+ elapsed = time.time() - t0
+ assert 2.0 < elapsed < 2.5
+ assert conn.info.port == int(proxy.server_port)
+ assert conn.info.host == proxy.server_host
+
+
+@pytest.mark.slow
+@pytest.mark.timing
+def test_multi_hosts_timeout(conn_cls, proxy, dsn, deaf_port):
+ args = conninfo_to_dict(dsn)
+ args["host"] = f"{proxy.client_host},{proxy.server_host}"
+ args["port"] = f"{deaf_port},{proxy.server_port}"
+ args.pop("hostaddr", None)
+ args["connect_timeout"] = "1"
+ t0 = time.time()
+ with conn_cls.connect(**args) as conn:
+ elapsed = time.time() - t0
+ assert 1.0 < elapsed < 1.5
+ assert conn.info.port == int(proxy.server_port)
+ assert conn.info.host == proxy.server_host
+
+
def test_close(conn):
assert not conn.closed
assert not conn.broken
def drop_default_args_from_conninfo(conninfo):
- params = conninfo_to_dict(conninfo)
+ if isinstance(conninfo, str):
+ params = conninfo_to_dict(conninfo)
+ else:
+ params = conninfo.copy()
def removeif(key, value):
if params.get(key) == value:
removeif("host", "")
removeif("hostaddr", "")
removeif("port", "5432")
+ if "," in params.get("host", ""):
+ nhosts = len(params["host"].split(","))
+ removeif("port", ",".join(["5432"] * nhosts))
+ removeif("hostaddr", "," * (nhosts - 1))
removeif("connect_timeout", str(DEFAULT_TIMEOUT))
return params
assert elapsed == pytest.approx(1.0, abs=0.05)
+@pytest.mark.slow
+@pytest.mark.timing
+async def test_multi_hosts(aconn_cls, proxy, dsn, deaf_port, monkeypatch):
+ args = conninfo_to_dict(dsn)
+ args["host"] = f"{proxy.client_host},{proxy.server_host}"
+ args["port"] = f"{deaf_port},{proxy.server_port}"
+ args.pop("hostaddr", None)
+ monkeypatch.setattr(aconn_cls, "_DEFAULT_CONNECT_TIMEOUT", 2)
+ t0 = time.time()
+ async with await aconn_cls.connect(**args) as conn:
+ elapsed = time.time() - t0
+ assert 2.0 < elapsed < 2.5
+ assert conn.info.port == int(proxy.server_port)
+ assert conn.info.host == proxy.server_host
+
+
+@pytest.mark.slow
+@pytest.mark.timing
+async def test_multi_hosts_timeout(aconn_cls, proxy, dsn, deaf_port):
+ args = conninfo_to_dict(dsn)
+ args["host"] = f"{proxy.client_host},{proxy.server_host}"
+ args["port"] = f"{deaf_port},{proxy.server_port}"
+ args.pop("hostaddr", None)
+ args["connect_timeout"] = "1"
+ t0 = time.time()
+ async with await aconn_cls.connect(**args) as conn:
+ elapsed = time.time() - t0
+ assert 1.0 < elapsed < 1.5
+ assert conn.info.port == int(proxy.server_port)
+ assert conn.info.host == proxy.server_host
+
+
async def test_close(aconn):
assert not aconn.closed
assert not aconn.broken
import socket
import asyncio
import datetime as dt
+from functools import reduce
import pytest
import psycopg
from psycopg import ProgrammingError
from psycopg.conninfo import make_conninfo, conninfo_to_dict, ConnectionInfo
-from psycopg.conninfo import resolve_hostaddr_async
+from psycopg.conninfo import conninfo_attempts, conninfo_attempts_async
from psycopg._encodings import pg2pyenc
+from .utils import alist
from .fix_crdb import crdb_encoding
+from .test_connection import drop_default_args_from_conninfo
snowman = "\u2603"
assert conn.info.vendor
+@pytest.mark.parametrize(
+ "conninfo, want, env",
+ [
+ ("", "", None),
+ ("host='' user=bar", "host='' user=bar", None),
+ (
+ "host=127.0.0.1 user=bar",
+ "host=127.0.0.1 user=bar",
+ None,
+ ),
+ (
+ "host=1.1.1.1,2.2.2.2 user=bar",
+ "host=1.1.1.1,2.2.2.2 user=bar",
+ None,
+ ),
+ (
+ "host=1.1.1.1,2.2.2.2 port=5432",
+ "host=1.1.1.1,2.2.2.2 port=5432,5432",
+ None,
+ ),
+ (
+ "host=foo.com port=5432",
+ "host=foo.com port=5432 hostaddr=1.2.3.4",
+ {"PGHOSTADDR": "1.2.3.4"},
+ ),
+ ],
+)
+@pytest.mark.anyio
+def test_conninfo_attempts(setpgenv, conninfo, want, env):
+ setpgenv(env)
+ params = conninfo_to_dict(conninfo)
+ attempts = list(conninfo_attempts(params))
+ params = drop_default_args_from_conninfo(reduce(merge_conninfos, attempts))
+ assert drop_default_args_from_conninfo(conninfo_to_dict(want)) == params
+
+
@pytest.mark.parametrize(
"conninfo, want, env",
[
],
)
@pytest.mark.anyio
-async def test_resolve_hostaddr_async_no_resolve(
+async def test_conninfo_attempts_async_no_resolve(
setpgenv, conninfo, want, env, fail_resolve
):
setpgenv(env)
params = conninfo_to_dict(conninfo)
- params = await resolve_hostaddr_async(params)
- assert conninfo_to_dict(want) == params
+ attempts = await alist(conninfo_attempts_async(params))
+ params = drop_default_args_from_conninfo(reduce(merge_conninfos, attempts))
+ assert drop_default_args_from_conninfo(conninfo_to_dict(want)) == params
@pytest.mark.parametrize(
],
)
@pytest.mark.anyio
-async def test_resolve_hostaddr_async(conninfo, want, env, fake_resolve):
+async def test_conninfo_attempts_async(conninfo, want, env, fake_resolve):
params = conninfo_to_dict(conninfo)
- params = await resolve_hostaddr_async(params)
- assert conninfo_to_dict(want) == params
+ attempts = await alist(conninfo_attempts_async(params))
+ params = drop_default_args_from_conninfo(reduce(merge_conninfos, attempts))
+ assert drop_default_args_from_conninfo(conninfo_to_dict(want)) == params
@pytest.mark.parametrize(
],
)
@pytest.mark.anyio
-async def test_resolve_hostaddr_async_bad(setpgenv, conninfo, env, fake_resolve):
+async def test_conninfo_attempts_async_bad(setpgenv, conninfo, env, fake_resolve):
+ setpgenv(env)
+ params = conninfo_to_dict(conninfo)
+ with pytest.raises(psycopg.Error):
+ await alist(conninfo_attempts_async(params))
+
+
+@pytest.mark.parametrize(
+ "conninfo, env",
+ [
+ ("host=foo.com port=1,2", None),
+ ("host=1.1.1.1,2.2.2.2 port=5432,5433,5434", None),
+ ("host=1.1.1.1,2.2.2.2", {"PGPORT": "1,2,3"}),
+ ],
+)
+@pytest.mark.anyio
+def test_conninfo_attempts_bad(setpgenv, conninfo, env):
setpgenv(env)
params = conninfo_to_dict(conninfo)
with pytest.raises(psycopg.Error):
- await resolve_hostaddr_async(params)
+ list(conninfo_attempts(params))
@pytest.fixture
pytest.fail(f"shouldn't try to resolve {host}")
monkeypatch.setattr(asyncio.get_running_loop(), "getaddrinfo", fail_getaddrinfo)
+
+
+def merge_conninfos(a1, a2):
+ """
+ merge conninfo attempts into a multi-host conninfo.
+ """
+ assert set(a1) == set(a2)
+ rv = {}
+ for k in a1:
+ if k in ("host", "hostaddr", "port"):
+ rv[k] = f"{a1[k]},{a2[k]}"
+ else:
+ assert a1[k] == a2[k]
+ rv[k] = a1[k]
+ return rv