Skip to content

Commit b035c9c

Browse files
authored
PYTHON-4520 Add test-async tox command and automated async testing (mongodb#1699)
1 parent 3617b5c commit b035c9c

File tree

8 files changed

+60
-15
lines changed

8 files changed

+60
-15
lines changed

.evergreen/run-tests.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ if [ -z "$GREEN_FRAMEWORK" ]; then
258258
# Use --capture=tee-sys so pytest prints test output inline:
259259
# https://docs.pytest.org/en/stable/how-to/capture-stdout-stderr.html
260260
python -m pytest -v --capture=tee-sys --durations=5 --maxfail=10 $TEST_ARGS
261+
if [ -z "$TEST_ARGS" ]; then # TODO: remove this in PYTHON-4528
262+
python -m pytest -v --capture=tee-sys --durations=5 --maxfail=10 test/synchronous/ $TEST_ARGS
263+
fi
264+
python -m pytest -v --capture=tee-sys --durations=5 --maxfail=10 test/asynchronous/ $TEST_ARGS
261265
else
262266
python green_framework_test.py $GREEN_FRAMEWORK -v $TEST_ARGS
263267
fi

.github/workflows/test-python.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ jobs:
7171
- name: Run tests
7272
run: |
7373
tox -m test
74+
- name: Run async tests
75+
run: |
76+
tox -m test-async
7477
7578
doctest:
7679
runs-on: ubuntu-latest
@@ -203,3 +206,5 @@ jobs:
203206
which python
204207
pip install -e ".[test]"
205208
pytest -v
209+
pytest -v test/synchronous/
210+
pytest -v test/asynchronous/

test/asynchronous/test_collection.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
sys.path[0:0] = [""]
2929

3030
from test import unittest
31-
from test.asynchronous import AsyncIntegrationTest, async_client_context
31+
from test.asynchronous import ( # TODO: fix sync imports in PYTHON-4528
32+
AsyncIntegrationTest,
33+
async_client_context,
34+
)
3235
from test.utils import (
3336
IMPOSSIBLE_WRITE_CONCERN,
3437
EventListener,
@@ -472,8 +475,8 @@ async def test_index_geo2d(self):
472475
async def test_index_haystack(self):
473476
db = self.db
474477
await db.test.drop()
475-
_id = await db.test.insert_one(
476-
{"pos": {"long": 34.2, "lat": 33.3}, "type": "restaurant"}
478+
_id = (
479+
await db.test.insert_one({"pos": {"long": 34.2, "lat": 33.3}, "type": "restaurant"})
477480
).inserted_id
478481
await db.test.insert_one({"pos": {"long": 34.2, "lat": 37.3}, "type": "restaurant"})
479482
await db.test.insert_one({"pos": {"long": 59.1, "lat": 87.2}, "type": "office"})

test/synchronous/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from test.synchronous import setup, teardown
3+
from test import setup, teardown
44

55
import pytest
66

test/synchronous/test_collection.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@
2727

2828
sys.path[0:0] = [""]
2929

30-
from test import unittest
31-
from test.synchronous import IntegrationTest, client_context
30+
from test import ( # TODO: fix sync imports in PYTHON-4528
31+
IntegrationTest,
32+
client_context,
33+
unittest,
34+
)
3235
from test.utils import (
3336
IMPOSSIBLE_WRITE_CONCERN,
3437
EventListener,
@@ -461,8 +464,8 @@ def test_index_geo2d(self):
461464
def test_index_haystack(self):
462465
db = self.db
463466
db.test.drop()
464-
_id = db.test.insert_one(
465-
{"pos": {"long": 34.2, "lat": 33.3}, "type": "restaurant"}
467+
_id = (
468+
db.test.insert_one({"pos": {"long": 34.2, "lat": 33.3}, "type": "restaurant"})
466469
).inserted_id
467470
db.test.insert_one({"pos": {"long": 34.2, "lat": 37.3}, "type": "restaurant"})
468471
db.test.insert_one({"pos": {"long": 59.1, "lat": 87.2}, "type": "office"})

tools/synchro.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@
9292

9393
type_replacements = {"_Condition": "threading.Condition"}
9494

95+
import_replacements = {"test.synchronous": "test"}
96+
9597
_pymongo_base = "./pymongo/asynchronous/"
9698
_gridfs_base = "./gridfs/asynchronous/"
9799
_test_base = "./test/asynchronous/"
@@ -136,23 +138,31 @@ def process_files(files: list[str]) -> None:
136138
if "__init__" not in file or "__init__" and "test" in file:
137139
with open(file, "r+") as f:
138140
lines = f.readlines()
139-
lines = apply_is_sync(lines)
141+
lines = apply_is_sync(lines, file)
140142
lines = translate_coroutine_types(lines)
141143
lines = translate_async_sleeps(lines)
142144
if file in docstring_translate_files:
143145
lines = translate_docstrings(lines)
144146
translate_locks(lines)
145147
translate_types(lines)
148+
if file in sync_test_files:
149+
translate_imports(lines)
146150
f.seek(0)
147151
f.writelines(lines)
148152
f.truncate()
149153

150154

151-
def apply_is_sync(lines: list[str]) -> list[str]:
152-
is_sync = next(iter([line for line in lines if line.startswith("_IS_SYNC = ")]))
153-
index = lines.index(is_sync)
154-
is_sync = is_sync.replace("False", "True")
155-
lines[index] = is_sync
155+
def apply_is_sync(lines: list[str], file: str) -> list[str]:
156+
try:
157+
is_sync = next(iter([line for line in lines if line.startswith("_IS_SYNC = ")]))
158+
index = lines.index(is_sync)
159+
is_sync = is_sync.replace("False", "True")
160+
lines[index] = is_sync
161+
except StopIteration as e:
162+
print(
163+
f"Missing _IS_SYNC at top of async file {file.replace('synchronous', 'asynchronous')}"
164+
)
165+
raise e
156166
return lines
157167

158168

@@ -196,6 +206,15 @@ def translate_types(lines: list[str]) -> list[str]:
196206
return lines
197207

198208

209+
def translate_imports(lines: list[str]) -> list[str]:
210+
for k, v in import_replacements.items():
211+
matches = [line for line in lines if k in line and "import" in line]
212+
for line in matches:
213+
index = lines.index(line)
214+
lines[index] = line.replace(k, v)
215+
return lines
216+
217+
199218
def translate_async_sleeps(lines: list[str]) -> list[str]:
200219
blocking_sleeps = [line for line in lines if "asyncio.sleep(0)" in line]
201220
lines = [line for line in lines if line not in blocking_sleeps]

tools/synchro.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/bin/bash -eu
22

33
python ./tools/synchro.py
44
python -m ruff check pymongo/synchronous/ gridfs/synchronous/ test/synchronous --fix --silent

tox.ini

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ requires =
55
envlist =
66
# Test using the system Python.
77
test,
8+
# Test async tests using the system Python.
9+
test-async,
810
# Test using the run-tests Evergreen script.
911
test-eg,
1012
# Set up encryption files and services.
@@ -34,6 +36,7 @@ envlist =
3436

3537
labels = # Use labels and -m instead of -e so that tox -m <label> fails instantly if the label does not exist
3638
test = test
39+
test-async = test-async
3740
test-eg = test-eg
3841
setup-encryption = setup-encryption
3942
teardown-encryption = teardown-encryption
@@ -63,6 +66,14 @@ extras =
6366
test
6467
commands =
6568
pytest -v --durations=5 --maxfail=10 {posargs}
69+
pytest -v --durations=5 --maxfail=10 test/synchronous/ {posargs}
70+
71+
[testenv:test-async]
72+
description = run base set of async unit tests with no extra functionality
73+
extras =
74+
test
75+
commands =
76+
pytest -v --durations=5 --maxfail=10 test/asynchronous/ {posargs}
6677

6778
[testenv:test-eg]
6879
description = run tests using run-tests.sh Evergreen script

0 commit comments

Comments
 (0)