Skip to content

Commit 266b0a3

Browse files
committed
Convert test_client to pytest
1 parent 0296c20 commit 266b0a3

File tree

6 files changed

+839
-20
lines changed

6 files changed

+839
-20
lines changed

pymongo/asynchronous/mongo_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,7 @@ def __next__(self) -> NoReturn:
14181418
raise TypeError("'AsyncMongoClient' object is not iterable")
14191419

14201420
next = __next__
1421+
anext = next
14211422

14221423
async def _server_property(self, attr_name: str) -> Any:
14231424
"""An attribute of the current server's description.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ zstd = ["requirements/zstd.txt"]
9494

9595
[tool.pytest.ini_options]
9696
minversion = "7"
97-
addopts = ["-ra", "--strict-config", "--strict-markers", "--junitxml=xunit-results/TEST-results.xml", "-m default or default_async"]
97+
addopts = ["-ra", "--strict-config", "--strict-markers", "--junitxml=xunit-results/TEST-results.xml", "-m default or default_async or asyncio"]
9898
testpaths = ["test"]
9999
log_cli_level = "INFO"
100100
faulthandler_timeout = 1500

test/asynchronous/__init__.py

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
import unittest
3131
import warnings
3232
from asyncio import iscoroutinefunction
33+
34+
import pytest_asyncio
35+
from pymongo.lock import _create_lock, _async_create_lock
36+
3337
from test.helpers import (
3438
COMPRESSORS,
3539
IS_SRV,
@@ -116,7 +120,7 @@ def __init__(self):
116120
self.default_client_options: Dict = {}
117121
self.sessions_enabled = False
118122
self.client = None # type: ignore
119-
self.conn_lock = threading.Lock()
123+
self.conn_lock = _async_create_lock()
120124
self.is_data_lake = False
121125
self.load_balancer = TEST_LOADBALANCER
122126
self.serverless = TEST_SERVERLESS
@@ -337,7 +341,7 @@ async def _init_client(self):
337341
await mongos_client.close()
338342

339343
async def init(self):
340-
with self.conn_lock:
344+
async with self.conn_lock:
341345
if not self.client and not self.connection_attempts:
342346
await self._init_client()
343347

@@ -520,6 +524,12 @@ def require_data_lake(self, func):
520524
func=func,
521525
)
522526

527+
@property
528+
def is_not_mmap(self):
529+
if self.is_mongos:
530+
return True
531+
return self.storage_engine != "mmapv1"
532+
523533
def require_no_mmap(self, func):
524534
"""Run a test only if the server is not using the MMAPv1 storage
525535
engine. Only works for standalone and replica sets; tests are
@@ -573,6 +583,10 @@ def require_replica_set(self, func):
573583
"""Run a test only if the client is connected to a replica set."""
574584
return self._require(lambda: self.is_rs, "Not connected to a replica set", func=func)
575585

586+
@property
587+
async def secondaries_count(self):
588+
return 0 if not self.client else len(await self.client.secondaries)
589+
576590
def require_secondaries_count(self, count):
577591
"""Run a test only if the client is connected to a replica set that has
578592
`count` secondaries.
@@ -588,7 +602,7 @@ async def check():
588602

589603
@property
590604
async def supports_secondary_read_pref(self):
591-
if self.has_secondaries:
605+
if await self.has_secondaries:
592606
return True
593607
if self.is_mongos:
594608
shard = await self.client.config.shards.find_one()["host"] # type:ignore[index]
@@ -692,7 +706,7 @@ async def is_topology_type(self, topologies):
692706
if "sharded" in topologies and self.is_mongos:
693707
return True
694708
if "sharded-replicaset" in topologies and self.is_mongos:
695-
shards = await async_client_context.client.config.shards.find().to_list()
709+
shards = await self.client.config.shards.find().to_list()
696710
for shard in shards:
697711
# For a 3-member RS-backed sharded cluster, shard['host']
698712
# will be 'replicaName/ip1:port1,ip2:port2,ip3:port3'
@@ -1191,8 +1205,39 @@ async def asyncTearDown(self) -> None:
11911205
await super().asyncTearDown()
11921206

11931207

1208+
async def _get_environment():
1209+
client = AsyncClientContext()
1210+
await client.init()
1211+
requirements = {}
1212+
requirements["SUPPORT_TRANSACTIONS"] = client.supports_transactions()
1213+
requirements["IS_DATA_LAKE"] = client.is_data_lake
1214+
requirements["IS_SYNC"] = _IS_SYNC
1215+
requirements["IS_SYNC"] = _IS_SYNC
1216+
requirements["REQUIRE_API_VERSION"] = MONGODB_API_VERSION
1217+
requirements["SUPPORTS_FAILCOMMAND_FAIL_POINT"] = client.supports_failCommand_fail_point
1218+
requirements["IS_NOT_MMAP"] = client.is_not_mmap
1219+
requirements["SERVER_VERSION"] = client.version
1220+
requirements["AUTH_ENABLED"] = client.auth_enabled
1221+
requirements["FIPS_ENABLED"] = client.fips_enabled
1222+
requirements["IS_RS"] = client.is_rs
1223+
requirements["MONGOSES"] = len(client.mongoses)
1224+
requirements["SECONDARIES_COUNT"] = await client.secondaries_count
1225+
requirements["SECONDARY_READ_PREF"] = await client.supports_secondary_read_pref
1226+
requirements["HAS_IPV6"] = client.has_ipv6
1227+
requirements["IS_SERVERLESS"] = client.serverless
1228+
requirements["IS_LOAD_BALANCER"] = client.load_balancer
1229+
requirements["TEST_COMMANDS_ENABLED"] = client.test_commands_enabled
1230+
requirements["IS_TLS"] = client.tls
1231+
requirements["IS_TLS_CERT"] = client.tlsCertificateKeyFile
1232+
requirements["SERVER_IS_RESOLVEABLE"] = client.server_is_resolvable
1233+
requirements["SESSIONS_ENABLED"] = client.sessions_enabled
1234+
requirements["SUPPORTS_RETRYABLE_WRITES"] = client.supports_retryable_writes()
1235+
await client.client.close()
1236+
1237+
return requirements
1238+
11941239
async def async_setup():
1195-
await async_client_context.init()
1240+
await _get_environment()
11961241
warnings.resetwarnings()
11971242
warnings.simplefilter("always")
11981243
global_knobs.enable()
@@ -1207,16 +1252,16 @@ async def async_teardown():
12071252
garbage.append(f" gc.get_referrers: {gc.get_referrers(g)!r}")
12081253
if garbage:
12091254
raise AssertionError("\n".join(garbage))
1210-
c = async_client_context.client
1211-
if c:
1212-
if not async_client_context.is_data_lake:
1213-
await c.drop_database("pymongo-pooling-tests")
1214-
await c.drop_database("pymongo_test")
1215-
await c.drop_database("pymongo_test1")
1216-
await c.drop_database("pymongo_test2")
1217-
await c.drop_database("pymongo_test_mike")
1218-
await c.drop_database("pymongo_test_bernie")
1219-
await c.close()
1255+
# c = async_client_context.client
1256+
# if c:
1257+
# if not async_client_context.is_data_lake:
1258+
# await c.drop_database("pymongo-pooling-tests")
1259+
# await c.drop_database("pymongo_test")
1260+
# await c.drop_database("pymongo_test1")
1261+
# await c.drop_database("pymongo_test2")
1262+
# await c.drop_database("pymongo_test_mike")
1263+
# await c.drop_database("pymongo_test_bernie")
1264+
# await c.close()
12201265
print_running_clients()
12211266

12221267

test/asynchronous/conftest.py

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
import asyncio
44
import sys
5-
from test import pytest_conf
6-
from test.asynchronous import async_setup, async_teardown
5+
6+
from typing_extensions import Any
7+
8+
from pymongo import AsyncMongoClient
9+
from pymongo.uri_parser import parse_uri
10+
11+
from test import pytest_conf, db_user, db_pwd
12+
from test.asynchronous import async_setup, async_teardown, _connection_string, AsyncClientContext
713

814
import pytest
915
import pytest_asyncio
@@ -21,12 +27,103 @@ def event_loop_policy():
2127

2228
return asyncio.get_event_loop_policy()
2329

30+
@pytest_asyncio.fixture(loop_scope="session")
31+
async def async_client_context_fixture():
32+
client = AsyncClientContext()
33+
await client.init()
34+
yield client
35+
await client.client.close()
2436

25-
@pytest_asyncio.fixture(scope="package", autouse=True)
37+
@pytest_asyncio.fixture(loop_scope="session", autouse=True)
2638
async def test_setup_and_teardown():
2739
await async_setup()
2840
yield
2941
await async_teardown()
3042

43+
async def _async_mongo_client(
44+
async_client_context, host, port, authenticate=True, directConnection=None, **kwargs
45+
):
46+
"""Create a new client over SSL/TLS if necessary."""
47+
host = host or await async_client_context.host
48+
port = port or await async_client_context.port
49+
client_options: dict = async_client_context.default_client_options.copy()
50+
if async_client_context.replica_set_name and not directConnection:
51+
client_options["replicaSet"] = async_client_context.replica_set_name
52+
if directConnection is not None:
53+
client_options["directConnection"] = directConnection
54+
client_options.update(kwargs)
55+
56+
uri = _connection_string(host)
57+
auth_mech = kwargs.get("authMechanism", "")
58+
if async_client_context.auth_enabled and authenticate and auth_mech != "MONGODB-OIDC":
59+
# Only add the default username or password if one is not provided.
60+
res = parse_uri(uri)
61+
if (
62+
not res["username"]
63+
and not res["password"]
64+
and "username" not in client_options
65+
and "password" not in client_options
66+
):
67+
client_options["username"] = db_user
68+
client_options["password"] = db_pwd
69+
client = AsyncMongoClient(uri, port, **client_options)
70+
if client._options.connect:
71+
await client.aconnect()
72+
return client
73+
74+
75+
async def async_single_client_noauth(
76+
async_client_context, h: Any = None, p: Any = None, **kwargs: Any
77+
) -> AsyncMongoClient[dict]:
78+
"""Make a direct connection. Don't authenticate."""
79+
return await _async_mongo_client(async_client_context, h, p, authenticate=False, directConnection=True, **kwargs)
80+
#
81+
async def async_single_client(
82+
async_client_context, h: Any = None, p: Any = None, **kwargs: Any
83+
) -> AsyncMongoClient[dict]:
84+
"""Make a direct connection, and authenticate if necessary."""
85+
return await _async_mongo_client(async_client_context, h, p, directConnection=True, **kwargs)
86+
87+
# @pytest_asyncio.fixture(loop_scope="function")
88+
# async def async_rs_client_noauth(
89+
# async_client_context, h: Any = None, p: Any = None, **kwargs: Any
90+
# ) -> AsyncMongoClient[dict]:
91+
# """Connect to the replica set. Don't authenticate."""
92+
# return await _async_mongo_client(async_client_context, h, p, authenticate=False, **kwargs)
93+
#
94+
# @pytest_asyncio.fixture(loop_scope="function")
95+
# async def async_rs_client(
96+
# async_client_context, h: Any = None, p: Any = None, **kwargs: Any
97+
# ) -> AsyncMongoClient[dict]:
98+
# """Connect to the replica set and authenticate if necessary."""
99+
# return await _async_mongo_client(async_client_context, h, p, **kwargs)
100+
#
101+
# @pytest_asyncio.fixture(loop_scope="function")
102+
# async def async_rs_or_single_client_noauth(
103+
# async_client_context, h: Any = None, p: Any = None, **kwargs: Any
104+
# ) -> AsyncMongoClient[dict]:
105+
# """Connect to the replica set if there is one, otherwise the standalone.
106+
#
107+
# Like rs_or_single_client, but does not authenticate.
108+
# """
109+
# return await _async_mongo_client(async_client_context, h, p, authenticate=False, **kwargs)
110+
111+
async def async_rs_or_single_client(
112+
async_client_context, h: Any = None, p: Any = None, **kwargs: Any
113+
) -> AsyncMongoClient[Any]:
114+
"""Connect to the replica set if there is one, otherwise the standalone.
115+
116+
Authenticates if necessary.
117+
"""
118+
return await _async_mongo_client(async_client_context, h, p, **kwargs)
119+
120+
def simple_client(h: Any = None, p: Any = None, **kwargs: Any) -> AsyncMongoClient:
121+
if not h and not p:
122+
client = AsyncMongoClient(**kwargs)
123+
else:
124+
client = AsyncMongoClient(h, p, **kwargs)
125+
return client
126+
127+
31128

32129
pytest_collection_modifyitems = pytest_conf.pytest_collection_modifyitems

0 commit comments

Comments
 (0)