diff --git a/test/asynchronous/test_srv_polling.py b/test/asynchronous/test_srv_polling.py index 520d520c7b..74e6ce969e 100644 --- a/test/asynchronous/test_srv_polling.py +++ b/test/asynchronous/test_srv_polling.py @@ -21,7 +21,6 @@ import time from typing import Any -from test.asynchronous.utils import flaky from test.utils_shared import FunctionCallRecorder sys.path[0:0] = [""] @@ -226,7 +225,6 @@ def response_callback(*args): await self.run_scenario(response_callback, False) - @flaky(reason="PYTHON-5500", max_runs=3) async def test_dns_failures_logging(self): from dns import exception @@ -237,8 +235,10 @@ def response_callback(*args): await self.run_scenario(response_callback, False) - srv_failure_logs = [r for r in cm.records if "SRV monitor check failed" in r.getMessage()] - self.assertEqual(len(srv_failure_logs), 1) + await async_wait_until( + lambda: any("SRV monitor check failed" in r.getMessage() for r in cm.records), + "log the SRV monitor check failure", + ) async def test_dns_record_lookup_empty(self): response: list = [] @@ -270,14 +270,12 @@ def final_callback(): # Nodelist should reflect new valid DNS resolver response. await self.assert_nodelist_change(response_final, client) - @flaky(reason="PYTHON-5315") async def test_recover_from_initially_empty_seedlist(self): def empty_seedlist(): return [] await self._test_recover_from_initial(empty_seedlist) - @flaky(reason="PYTHON-5315") async def test_recover_from_initially_erroring_seedlist(self): def erroring_seedlist(): raise ConfigurationError diff --git a/test/test_srv_polling.py b/test/test_srv_polling.py index f3d0c3f160..4927b08f3d 100644 --- a/test/test_srv_polling.py +++ b/test/test_srv_polling.py @@ -21,7 +21,6 @@ import time from typing import Any -from test.utils import flaky from test.utils_shared import FunctionCallRecorder sys.path[0:0] = [""] @@ -226,7 +225,6 @@ def response_callback(*args): self.run_scenario(response_callback, False) - @flaky(reason="PYTHON-5500", max_runs=3) def test_dns_failures_logging(self): from dns import exception @@ -237,8 +235,10 @@ def response_callback(*args): self.run_scenario(response_callback, False) - srv_failure_logs = [r for r in cm.records if "SRV monitor check failed" in r.getMessage()] - self.assertEqual(len(srv_failure_logs), 1) + wait_until( + lambda: any("SRV monitor check failed" in r.getMessage() for r in cm.records), + "log the SRV monitor check failure", + ) def test_dns_record_lookup_empty(self): response: list = [] @@ -270,14 +270,12 @@ def final_callback(): # Nodelist should reflect new valid DNS resolver response. self.assert_nodelist_change(response_final, client) - @flaky(reason="PYTHON-5315") def test_recover_from_initially_empty_seedlist(self): def empty_seedlist(): return [] self._test_recover_from_initial(empty_seedlist) - @flaky(reason="PYTHON-5315") def test_recover_from_initially_erroring_seedlist(self): def erroring_seedlist(): raise ConfigurationError diff --git a/test/utils_shared.py b/test/utils_shared.py index c0c811a5a7..fa1e044048 100644 --- a/test/utils_shared.py +++ b/test/utils_shared.py @@ -338,19 +338,38 @@ def __eq__(self, other): class FunctionCallRecorder: - """Utility class to wrap a callable and record its invocations.""" + """Utility class to wrap a callable and record its invocations. + + A synchronous callable is recorded immediately, before it runs. A + coroutine function is recorded only once the returned coroutine + finishes running, whether it returns, raises, or is cancelled, so + callers can rely on a recorded call meaning the wrapped coroutine + actually ran rather than merely having been scheduled. + """ def __init__(self, function): self._function = function self._call_list = [] def __call__(self, *args, **kwargs): - self._call_list.append((args, kwargs)) if iscoroutinefunction(self._function): - return self._function(*args, **kwargs) + + async def _run_and_record(): + try: + return await self._function(*args, **kwargs) + finally: + self._call_list.append((args, kwargs)) + + return _run_and_record() else: + self._call_list.append((args, kwargs)) return self._function(*args, **kwargs) + def __get__(self, obj, objtype=None): + if obj is None: + return self + return functools.partial(self, obj) + def reset(self): """Wipes the call list.""" self._call_list = []