Uploaded image for project: 'Python Driver'
  1. Python Driver
  2. PYTHON-5071

Async tests should all share a single event loop

    • Type: Icon: Task Task
    • Resolution: Fixed
    • Priority: Icon: Unknown Unknown
    • 4.12
    • Affects Version/s: None
    • Component/s: None
    • None
    • Python Drivers
    • Not Needed
    • Hide

      1. What would you like to communicate to the user about this feature?
      2. Would you like the user to see examples of the syntax and/or executable code and its output?
      3. Which versions of the driver/connector does this apply to?

      Show
      1. What would you like to communicate to the user about this feature? 2. Would you like the user to see examples of the syntax and/or executable code and its output? 3. Which versions of the driver/connector does this apply to?

      Ideally, our async tests should all share a single event loop. This would simplify the async test migration story because we would no longer be blocked on migrating to pytest (PYTHON-5036).

      For example:

      class AsyncPyMongoTestCase(unittest.TestCase):
          if not _IS_SYNC:
              # An async TestCase that uses a single event loop for all tests. Inspired by IsolatedAsyncioTestCase. 
              def __init__(self, methodName="runTest"):
                  super().__init__(methodName)
                  try:
                      self.loop = asyncio.get_event_loop()
                  except RuntimeError:
                      self.loop = asyncio.new_event_loop()
                      asyncio.set_event_loop(self.loop)
      
              async def asyncSetUp(self):
                  pass
      
              async def asyncTearDown(self):
                  pass
      
              def addAsyncCleanup(self, func, /, *args, **kwargs):
                  self.addCleanup(*(func, *args), **kwargs)
      
              def _callSetUp(self):
                  self._callAsync(self.asyncSetUp)
      
              def _callTestMethod(self, method):
                  if self._callMaybeAsync(method) is not None:
                      warnings.warn(
                          f"It is deprecated to return a value that is not None from a "
                          f"test case ({method})",
                          DeprecationWarning,
                          stacklevel=4,
                      )
      
              def _callTearDown(self):
                  self._callAsync(self.asyncTearDown)
                  self.tearDown()
      
              def _callCleanup(self, function, *args, **kwargs):
                  self._callMaybeAsync(function, *args, **kwargs)
      
              def _callAsync(self, func, /, *args, **kwargs):
                  assert inspect.iscoroutinefunction(func), f"{func!r} is not an async function"
                  return self.loop.run_until_complete(func(*args, **kwargs))
      
              def _callMaybeAsync(self, func, /, *args, **kwargs):
                  if inspect.iscoroutinefunction(func):
                      return self.loop.run_until_complete(func(*args, **kwargs))
                  else:
                      return func(*args, **kwargs)
      

      The only downside to this approach is that we'll be using some private unittest apis. I would be willing to accept this because it's only in the test suite and these private apis haven't changed since they were introduced in Python 3.8.

      This would let us start

            Assignee:
            shane.harvey@mongodb.com Shane Harvey
            Reporter:
            shane.harvey@mongodb.com Shane Harvey
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: