"""Concurrency utilities unit tests.""" import asyncio from unittest import TestCase from unittest.mock import Mock from unittest.mock import patch from api.utils import concurrency as conc class ConcurrencyTestCase(TestCase): """Provide scaffolding for event loops.""" def setUp(self): """Preserve old loop, replace with new one.""" self.old_loop = asyncio.get_event_loop() self.loop = asyncio.new_event_loop() self.loop_factory_patcher = patch( 'api.utils.concurrency.get_loop', return_value=self.loop) self.loop_factory_patcher.start() asyncio.set_event_loop(self.loop) def tearDown(self): """Restore old loop.""" asyncio.set_event_loop(self.old_loop) self.loop.close() self.loop_factory_patcher.stop() def test_call_future_async(self): """Test making a future with an async callable.""" async def async_func(): return 'async result' future = conc.call_future(async_func) assert isinstance(future, asyncio.Future) self.loop.run_until_complete(future) assert future.result() == 'async result' def test_call_future_sync(self): """Test making a future with an blocking callable.""" test_value = 'blocking result' future = conc.call_future(Mock(return_value=test_value)) assert isinstance(future, asyncio.Future) self.loop.run_until_complete(future) assert future.result() == test_value def test_call_in_thread(self): """Test direct threadpool future calls.""" test_value = 'threadpool result' future = conc.call_in_thread(Mock(return_value=test_value)) assert isinstance(future, asyncio.Future) self.loop.run_until_complete(future) assert future.result() == test_value def test_wait_for_futures(self): """Test gethering and concurrent waiting of multiple futures.""" test_values = ['foo', 'bar', 'baz'] futures = [] for value in test_values: future = conc.call_future(Mock(return_value=value)) futures.append(future) results = conc.wait_for_futures(*futures) assert results == test_values @patch('api.utils.concurrency.asyncio') def test_get_loop_new(asyncio): """Test factory to get new global event loop.""" try: old_loop = conc._loop # new loop mock_loop = Mock() conc._loop = None asyncio.new_event_loop.return_value = mock_loop assert conc.get_loop() == mock_loop assert asyncio.new_event_loop.called # cached loop newer_loop = Mock() asyncio.reset_mock() asyncio.new_event_loop.return_value = newer_loop assert conc.get_loop() == mock_loop assert not asyncio.new_event_loop.called finally: conc._loop = old_loop @patch('api.utils.concurrency.asyncio') def test_get_loop_cached(asyncio): """Test factory to get cached global event loop.""" try: old_loop = conc._loop mock_loop = Mock() conc._loop = mock_loop assert conc.get_loop() == mock_loop finally: conc._loop = old_loop