Skip to content

Commit c30c898

Browse files
committed
clean up flaky test handling
1 parent 82a6392 commit c30c898

File tree

4 files changed

+68
-74
lines changed

4 files changed

+68
-74
lines changed

test/asynchronous/unified_format.py

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
client_knobs,
3636
unittest,
3737
)
38-
from test.asynchronous.utils import async_get_pool
38+
from test.asynchronous.utils import async_get_pool, flaky
3939
from test.asynchronous.utils_spec_runner import SpecRunnerTask
4040
from test.unified_format_shared import (
4141
KMS_TLS_OPTS,
@@ -529,20 +529,10 @@ def maybe_skip_test(self, spec):
529529
self.skipTest("Implement PYTHON-1894")
530530
if "timeoutMS applied to entire download" in spec["description"]:
531531
self.skipTest("PyMongo's open_download_stream does not cap the stream's lifetime")
532-
if (
533-
"Error returned from connection pool clear with interruptInUseConnections=true is retryable"
534-
in spec["description"]
535-
and not _IS_SYNC
536-
):
537-
self.skipTest("PYTHON-5170 tests are flakey")
538-
if "Driver extends timeout while streaming" in spec["description"] and not _IS_SYNC:
539-
self.skipTest("PYTHON-5174 tests are flakey")
540532

541533
class_name = self.__class__.__name__.lower()
542534
description = spec["description"].lower()
543535
if "csot" in class_name:
544-
if "gridfs" in class_name and sys.platform == "win32":
545-
self.skipTest("PYTHON-3522 CSOT GridFS tests are flaky on Windows")
546536
if async_client_context.storage_engine == "mmapv1":
547537
self.skipTest(
548538
"MMAPv1 does not support retryable writes which is required for CSOT tests"
@@ -1378,30 +1368,21 @@ async def run_scenario(self, spec, uri=None):
13781368
# operations during test set up and tear down.
13791369
await self.kill_all_sessions()
13801370

1381-
if "csot" in self.id().lower():
1382-
# Retry CSOT tests up to 2 times to deal with flakey tests.
1383-
attempts = 3
1384-
for i in range(attempts):
1385-
try:
1386-
return await self._run_scenario(spec, uri)
1387-
except (AssertionError, OperationFailure) as exc:
1388-
if isinstance(exc, OperationFailure) and (
1389-
_IS_SYNC or "failpoint" not in exc._message
1390-
):
1391-
raise
1392-
if i < attempts - 1:
1393-
print(
1394-
f"Retrying after attempt {i+1} of {self.id()} failed with:\n"
1395-
f"{traceback.format_exc()}",
1396-
file=sys.stderr,
1397-
)
1398-
await self.asyncSetUp()
1399-
continue
1400-
raise
1401-
return None
1402-
else:
1403-
await self._run_scenario(spec, uri)
1404-
return None
1371+
# Handle flaky tests.
1372+
func = functools.partial(self._run_scenario, spec, uri)
1373+
flaky_tests = [
1374+
# PYTHON-5170
1375+
".*test_discovery_and_monitoring.TestUnifiedInterruptInUsePoolClear.*",
1376+
# PYTHON-5174
1377+
".*Driver_extends_timeout_while_streaming",
1378+
".*csot.*",
1379+
]
1380+
for flaky_test in flaky_tests:
1381+
if re.match(flaky_test, self.id()) is not None:
1382+
decorator = flaky(reset_func=self.asyncSetUp, func_name=self.id())
1383+
func = decorator(func)
1384+
break
1385+
await func()
14051386

14061387
async def _run_scenario(self, spec, uri=None):
14071388
# maybe skip test manually

test/asynchronous/utils.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import sys
2222
import threading # Used in the synchronized version of this file
2323
import time
24+
import traceback
2425
from asyncio import iscoroutinefunction
2526
from functools import wraps
2627

@@ -156,7 +157,16 @@ async def async_joinall(tasks):
156157
await asyncio.wait([t.task for t in tasks if t is not None], timeout=300)
157158

158159

159-
def flaky(func=None, *, max_runs=2, min_passes=1, delay=1, affects_cpython_linux=False):
160+
def flaky(
161+
func=None,
162+
*,
163+
max_runs=2,
164+
min_passes=1,
165+
delay=1,
166+
affects_cpython_linux=False,
167+
func_name=None,
168+
reset_func=None,
169+
):
160170
is_cpython_linux = sys.platform == "linux" and sys.implementation.name == "cpython"
161171
if is_cpython_linux and not affects_cpython_linux:
162172
max_runs = 1
@@ -177,7 +187,13 @@ async def wrapper(*args, **kwargs):
177187
failure = e
178188
await asyncio.sleep(delay)
179189
if failure is not None and i < max_runs - 1:
180-
print("Flaky failure:", failure)
190+
print(
191+
f"Retrying after attempt {i+1} of {func_name or target_func.__name__} failed with:\n"
192+
f"{traceback.format_exc()}",
193+
file=sys.stderr,
194+
)
195+
if reset_func:
196+
await reset_func()
181197
if failure:
182198
raise failure
183199
raise RuntimeError(f"Only passed {passes} of {min_passes} times")

test/unified_format.py

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
parse_collection_or_database_options,
4848
with_metaclass,
4949
)
50-
from test.utils import get_pool
50+
from test.utils import flaky, get_pool
5151
from test.utils_shared import (
5252
camel_to_snake,
5353
camel_to_snake_args,
@@ -528,20 +528,10 @@ def maybe_skip_test(self, spec):
528528
self.skipTest("Implement PYTHON-1894")
529529
if "timeoutMS applied to entire download" in spec["description"]:
530530
self.skipTest("PyMongo's open_download_stream does not cap the stream's lifetime")
531-
if (
532-
"Error returned from connection pool clear with interruptInUseConnections=true is retryable"
533-
in spec["description"]
534-
and not _IS_SYNC
535-
):
536-
self.skipTest("PYTHON-5170 tests are flakey")
537-
if "Driver extends timeout while streaming" in spec["description"] and not _IS_SYNC:
538-
self.skipTest("PYTHON-5174 tests are flakey")
539531

540532
class_name = self.__class__.__name__.lower()
541533
description = spec["description"].lower()
542534
if "csot" in class_name:
543-
if "gridfs" in class_name and sys.platform == "win32":
544-
self.skipTest("PYTHON-3522 CSOT GridFS tests are flaky on Windows")
545535
if client_context.storage_engine == "mmapv1":
546536
self.skipTest(
547537
"MMAPv1 does not support retryable writes which is required for CSOT tests"
@@ -1365,30 +1355,21 @@ def run_scenario(self, spec, uri=None):
13651355
# operations during test set up and tear down.
13661356
self.kill_all_sessions()
13671357

1368-
if "csot" in self.id().lower():
1369-
# Retry CSOT tests up to 2 times to deal with flakey tests.
1370-
attempts = 3
1371-
for i in range(attempts):
1372-
try:
1373-
return self._run_scenario(spec, uri)
1374-
except (AssertionError, OperationFailure) as exc:
1375-
if isinstance(exc, OperationFailure) and (
1376-
_IS_SYNC or "failpoint" not in exc._message
1377-
):
1378-
raise
1379-
if i < attempts - 1:
1380-
print(
1381-
f"Retrying after attempt {i+1} of {self.id()} failed with:\n"
1382-
f"{traceback.format_exc()}",
1383-
file=sys.stderr,
1384-
)
1385-
self.setUp()
1386-
continue
1387-
raise
1388-
return None
1389-
else:
1390-
self._run_scenario(spec, uri)
1391-
return None
1358+
# Handle flaky tests.
1359+
func = functools.partial(self._run_scenario, spec, uri)
1360+
flaky_tests = [
1361+
# PYTHON-5170
1362+
".*test_discovery_and_monitoring.TestUnifiedInterruptInUsePoolClear.*",
1363+
# PYTHON-5174
1364+
".*Driver_extends_timeout_while_streaming",
1365+
".*csot.*",
1366+
]
1367+
for flaky_test in flaky_tests:
1368+
if re.match(flaky_test, self.id()) is not None:
1369+
decorator = flaky(reset_func=self.setUp, func_name=self.id())
1370+
func = decorator(func)
1371+
break
1372+
func()
13921373

13931374
def _run_scenario(self, spec, uri=None):
13941375
# maybe skip test manually

test/utils.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import sys
2222
import threading # Used in the synchronized version of this file
2323
import time
24+
import traceback
2425
from asyncio import iscoroutinefunction
2526
from functools import wraps
2627

@@ -154,7 +155,16 @@ def joinall(tasks):
154155
asyncio.wait([t.task for t in tasks if t is not None], timeout=300)
155156

156157

157-
def flaky(func=None, *, max_runs=2, min_passes=1, delay=1, affects_cpython_linux=False):
158+
def flaky(
159+
func=None,
160+
*,
161+
max_runs=2,
162+
min_passes=1,
163+
delay=1,
164+
affects_cpython_linux=False,
165+
func_name=None,
166+
reset_func=None,
167+
):
158168
is_cpython_linux = sys.platform == "linux" and sys.implementation.name == "cpython"
159169
if is_cpython_linux and not affects_cpython_linux:
160170
max_runs = 1
@@ -175,7 +185,13 @@ def wrapper(*args, **kwargs):
175185
failure = e
176186
time.sleep(delay)
177187
if failure is not None and i < max_runs - 1:
178-
print("Flaky failure:", failure)
188+
print(
189+
f"Retrying after attempt {i+1} of {func_name or target_func.__name__} failed with:\n"
190+
f"{traceback.format_exc()}",
191+
file=sys.stderr,
192+
)
193+
if reset_func:
194+
reset_func()
179195
if failure:
180196
raise failure
181197
raise RuntimeError(f"Only passed {passes} of {min_passes} times")

0 commit comments

Comments
 (0)