1*cda5da8dSAndroid Build Coastguard Workerimport asyncio 2*cda5da8dSAndroid Build Coastguard Workerimport contextvars 3*cda5da8dSAndroid Build Coastguard Workerimport inspect 4*cda5da8dSAndroid Build Coastguard Workerimport warnings 5*cda5da8dSAndroid Build Coastguard Worker 6*cda5da8dSAndroid Build Coastguard Workerfrom .case import TestCase 7*cda5da8dSAndroid Build Coastguard Worker 8*cda5da8dSAndroid Build Coastguard Worker 9*cda5da8dSAndroid Build Coastguard Workerclass IsolatedAsyncioTestCase(TestCase): 10*cda5da8dSAndroid Build Coastguard Worker # Names intentionally have a long prefix 11*cda5da8dSAndroid Build Coastguard Worker # to reduce a chance of clashing with user-defined attributes 12*cda5da8dSAndroid Build Coastguard Worker # from inherited test case 13*cda5da8dSAndroid Build Coastguard Worker # 14*cda5da8dSAndroid Build Coastguard Worker # The class doesn't call loop.run_until_complete(self.setUp()) and family 15*cda5da8dSAndroid Build Coastguard Worker # but uses a different approach: 16*cda5da8dSAndroid Build Coastguard Worker # 1. create a long-running task that reads self.setUp() 17*cda5da8dSAndroid Build Coastguard Worker # awaitable from queue along with a future 18*cda5da8dSAndroid Build Coastguard Worker # 2. await the awaitable object passing in and set the result 19*cda5da8dSAndroid Build Coastguard Worker # into the future object 20*cda5da8dSAndroid Build Coastguard Worker # 3. Outer code puts the awaitable and the future object into a queue 21*cda5da8dSAndroid Build Coastguard Worker # with waiting for the future 22*cda5da8dSAndroid Build Coastguard Worker # The trick is necessary because every run_until_complete() call 23*cda5da8dSAndroid Build Coastguard Worker # creates a new task with embedded ContextVar context. 24*cda5da8dSAndroid Build Coastguard Worker # To share contextvars between setUp(), test and tearDown() we need to execute 25*cda5da8dSAndroid Build Coastguard Worker # them inside the same task. 26*cda5da8dSAndroid Build Coastguard Worker 27*cda5da8dSAndroid Build Coastguard Worker # Note: the test case modifies event loop policy if the policy was not instantiated 28*cda5da8dSAndroid Build Coastguard Worker # yet. 29*cda5da8dSAndroid Build Coastguard Worker # asyncio.get_event_loop_policy() creates a default policy on demand but never 30*cda5da8dSAndroid Build Coastguard Worker # returns None 31*cda5da8dSAndroid Build Coastguard Worker # I believe this is not an issue in user level tests but python itself for testing 32*cda5da8dSAndroid Build Coastguard Worker # should reset a policy in every test module 33*cda5da8dSAndroid Build Coastguard Worker # by calling asyncio.set_event_loop_policy(None) in tearDownModule() 34*cda5da8dSAndroid Build Coastguard Worker 35*cda5da8dSAndroid Build Coastguard Worker def __init__(self, methodName='runTest'): 36*cda5da8dSAndroid Build Coastguard Worker super().__init__(methodName) 37*cda5da8dSAndroid Build Coastguard Worker self._asyncioRunner = None 38*cda5da8dSAndroid Build Coastguard Worker self._asyncioTestContext = contextvars.copy_context() 39*cda5da8dSAndroid Build Coastguard Worker 40*cda5da8dSAndroid Build Coastguard Worker async def asyncSetUp(self): 41*cda5da8dSAndroid Build Coastguard Worker pass 42*cda5da8dSAndroid Build Coastguard Worker 43*cda5da8dSAndroid Build Coastguard Worker async def asyncTearDown(self): 44*cda5da8dSAndroid Build Coastguard Worker pass 45*cda5da8dSAndroid Build Coastguard Worker 46*cda5da8dSAndroid Build Coastguard Worker def addAsyncCleanup(self, func, /, *args, **kwargs): 47*cda5da8dSAndroid Build Coastguard Worker # A trivial trampoline to addCleanup() 48*cda5da8dSAndroid Build Coastguard Worker # the function exists because it has a different semantics 49*cda5da8dSAndroid Build Coastguard Worker # and signature: 50*cda5da8dSAndroid Build Coastguard Worker # addCleanup() accepts regular functions 51*cda5da8dSAndroid Build Coastguard Worker # but addAsyncCleanup() accepts coroutines 52*cda5da8dSAndroid Build Coastguard Worker # 53*cda5da8dSAndroid Build Coastguard Worker # We intentionally don't add inspect.iscoroutinefunction() check 54*cda5da8dSAndroid Build Coastguard Worker # for func argument because there is no way 55*cda5da8dSAndroid Build Coastguard Worker # to check for async function reliably: 56*cda5da8dSAndroid Build Coastguard Worker # 1. It can be "async def func()" itself 57*cda5da8dSAndroid Build Coastguard Worker # 2. Class can implement "async def __call__()" method 58*cda5da8dSAndroid Build Coastguard Worker # 3. Regular "def func()" that returns awaitable object 59*cda5da8dSAndroid Build Coastguard Worker self.addCleanup(*(func, *args), **kwargs) 60*cda5da8dSAndroid Build Coastguard Worker 61*cda5da8dSAndroid Build Coastguard Worker async def enterAsyncContext(self, cm): 62*cda5da8dSAndroid Build Coastguard Worker """Enters the supplied asynchronous context manager. 63*cda5da8dSAndroid Build Coastguard Worker 64*cda5da8dSAndroid Build Coastguard Worker If successful, also adds its __aexit__ method as a cleanup 65*cda5da8dSAndroid Build Coastguard Worker function and returns the result of the __aenter__ method. 66*cda5da8dSAndroid Build Coastguard Worker """ 67*cda5da8dSAndroid Build Coastguard Worker # We look up the special methods on the type to match the with 68*cda5da8dSAndroid Build Coastguard Worker # statement. 69*cda5da8dSAndroid Build Coastguard Worker cls = type(cm) 70*cda5da8dSAndroid Build Coastguard Worker try: 71*cda5da8dSAndroid Build Coastguard Worker enter = cls.__aenter__ 72*cda5da8dSAndroid Build Coastguard Worker exit = cls.__aexit__ 73*cda5da8dSAndroid Build Coastguard Worker except AttributeError: 74*cda5da8dSAndroid Build Coastguard Worker raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does " 75*cda5da8dSAndroid Build Coastguard Worker f"not support the asynchronous context manager protocol" 76*cda5da8dSAndroid Build Coastguard Worker ) from None 77*cda5da8dSAndroid Build Coastguard Worker result = await enter(cm) 78*cda5da8dSAndroid Build Coastguard Worker self.addAsyncCleanup(exit, cm, None, None, None) 79*cda5da8dSAndroid Build Coastguard Worker return result 80*cda5da8dSAndroid Build Coastguard Worker 81*cda5da8dSAndroid Build Coastguard Worker def _callSetUp(self): 82*cda5da8dSAndroid Build Coastguard Worker # Force loop to be initialized and set as the current loop 83*cda5da8dSAndroid Build Coastguard Worker # so that setUp functions can use get_event_loop() and get the 84*cda5da8dSAndroid Build Coastguard Worker # correct loop instance. 85*cda5da8dSAndroid Build Coastguard Worker self._asyncioRunner.get_loop() 86*cda5da8dSAndroid Build Coastguard Worker self._asyncioTestContext.run(self.setUp) 87*cda5da8dSAndroid Build Coastguard Worker self._callAsync(self.asyncSetUp) 88*cda5da8dSAndroid Build Coastguard Worker 89*cda5da8dSAndroid Build Coastguard Worker def _callTestMethod(self, method): 90*cda5da8dSAndroid Build Coastguard Worker if self._callMaybeAsync(method) is not None: 91*cda5da8dSAndroid Build Coastguard Worker warnings.warn(f'It is deprecated to return a value that is not None from a ' 92*cda5da8dSAndroid Build Coastguard Worker f'test case ({method})', DeprecationWarning, stacklevel=4) 93*cda5da8dSAndroid Build Coastguard Worker 94*cda5da8dSAndroid Build Coastguard Worker def _callTearDown(self): 95*cda5da8dSAndroid Build Coastguard Worker self._callAsync(self.asyncTearDown) 96*cda5da8dSAndroid Build Coastguard Worker self._asyncioTestContext.run(self.tearDown) 97*cda5da8dSAndroid Build Coastguard Worker 98*cda5da8dSAndroid Build Coastguard Worker def _callCleanup(self, function, *args, **kwargs): 99*cda5da8dSAndroid Build Coastguard Worker self._callMaybeAsync(function, *args, **kwargs) 100*cda5da8dSAndroid Build Coastguard Worker 101*cda5da8dSAndroid Build Coastguard Worker def _callAsync(self, func, /, *args, **kwargs): 102*cda5da8dSAndroid Build Coastguard Worker assert self._asyncioRunner is not None, 'asyncio runner is not initialized' 103*cda5da8dSAndroid Build Coastguard Worker assert inspect.iscoroutinefunction(func), f'{func!r} is not an async function' 104*cda5da8dSAndroid Build Coastguard Worker return self._asyncioRunner.run( 105*cda5da8dSAndroid Build Coastguard Worker func(*args, **kwargs), 106*cda5da8dSAndroid Build Coastguard Worker context=self._asyncioTestContext 107*cda5da8dSAndroid Build Coastguard Worker ) 108*cda5da8dSAndroid Build Coastguard Worker 109*cda5da8dSAndroid Build Coastguard Worker def _callMaybeAsync(self, func, /, *args, **kwargs): 110*cda5da8dSAndroid Build Coastguard Worker assert self._asyncioRunner is not None, 'asyncio runner is not initialized' 111*cda5da8dSAndroid Build Coastguard Worker if inspect.iscoroutinefunction(func): 112*cda5da8dSAndroid Build Coastguard Worker return self._asyncioRunner.run( 113*cda5da8dSAndroid Build Coastguard Worker func(*args, **kwargs), 114*cda5da8dSAndroid Build Coastguard Worker context=self._asyncioTestContext, 115*cda5da8dSAndroid Build Coastguard Worker ) 116*cda5da8dSAndroid Build Coastguard Worker else: 117*cda5da8dSAndroid Build Coastguard Worker return self._asyncioTestContext.run(func, *args, **kwargs) 118*cda5da8dSAndroid Build Coastguard Worker 119*cda5da8dSAndroid Build Coastguard Worker def _setupAsyncioRunner(self): 120*cda5da8dSAndroid Build Coastguard Worker assert self._asyncioRunner is None, 'asyncio runner is already initialized' 121*cda5da8dSAndroid Build Coastguard Worker runner = asyncio.Runner(debug=True) 122*cda5da8dSAndroid Build Coastguard Worker self._asyncioRunner = runner 123*cda5da8dSAndroid Build Coastguard Worker 124*cda5da8dSAndroid Build Coastguard Worker def _tearDownAsyncioRunner(self): 125*cda5da8dSAndroid Build Coastguard Worker runner = self._asyncioRunner 126*cda5da8dSAndroid Build Coastguard Worker runner.close() 127*cda5da8dSAndroid Build Coastguard Worker 128*cda5da8dSAndroid Build Coastguard Worker def run(self, result=None): 129*cda5da8dSAndroid Build Coastguard Worker self._setupAsyncioRunner() 130*cda5da8dSAndroid Build Coastguard Worker try: 131*cda5da8dSAndroid Build Coastguard Worker return super().run(result) 132*cda5da8dSAndroid Build Coastguard Worker finally: 133*cda5da8dSAndroid Build Coastguard Worker self._tearDownAsyncioRunner() 134*cda5da8dSAndroid Build Coastguard Worker 135*cda5da8dSAndroid Build Coastguard Worker def debug(self): 136*cda5da8dSAndroid Build Coastguard Worker self._setupAsyncioRunner() 137*cda5da8dSAndroid Build Coastguard Worker super().debug() 138*cda5da8dSAndroid Build Coastguard Worker self._tearDownAsyncioRunner() 139*cda5da8dSAndroid Build Coastguard Worker 140*cda5da8dSAndroid Build Coastguard Worker def __del__(self): 141*cda5da8dSAndroid Build Coastguard Worker if self._asyncioRunner is not None: 142*cda5da8dSAndroid Build Coastguard Worker self._tearDownAsyncioRunner() 143