2022-02-09 23:14:42 +01:00
|
|
|
__author__ = "Daniil Fajnberg"
|
|
|
|
__copyright__ = "Copyright © 2022 Daniil Fajnberg"
|
|
|
|
__license__ = """GNU LGPLv3.0
|
|
|
|
|
|
|
|
This file is part of asyncio-taskpool.
|
|
|
|
|
|
|
|
asyncio-taskpool is free software: you can redistribute it and/or modify it under the terms of
|
|
|
|
version 3.0 of the GNU Lesser General Public License as published by the Free Software Foundation.
|
|
|
|
|
|
|
|
asyncio-taskpool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public License along with asyncio-taskpool.
|
|
|
|
If not, see <https://www.gnu.org/licenses/>."""
|
|
|
|
|
|
|
|
__doc__ = """
|
|
|
|
Unittests for the `asyncio_taskpool.pool` module.
|
|
|
|
"""
|
|
|
|
|
2022-02-07 14:23:49 +01:00
|
|
|
from asyncio.exceptions import CancelledError
|
2022-02-24 19:16:24 +01:00
|
|
|
from asyncio.locks import Semaphore
|
2022-02-07 14:23:49 +01:00
|
|
|
from unittest import IsolatedAsyncioTestCase
|
|
|
|
from unittest.mock import PropertyMock, MagicMock, AsyncMock, patch, call
|
2022-02-08 13:29:57 +01:00
|
|
|
from typing import Type
|
2022-02-05 19:34:52 +01:00
|
|
|
|
2022-02-07 14:23:49 +01:00
|
|
|
from asyncio_taskpool import pool, exceptions
|
2022-02-05 19:34:52 +01:00
|
|
|
|
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
EMPTY_LIST, EMPTY_DICT, EMPTY_SET = [], {}, set()
|
|
|
|
FOO, BAR, BAZ = 'foo', 'bar', 'baz'
|
2022-02-05 19:34:52 +01:00
|
|
|
|
|
|
|
|
2022-02-07 14:23:49 +01:00
|
|
|
class TestException(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2022-02-08 13:29:57 +01:00
|
|
|
class CommonTestCase(IsolatedAsyncioTestCase):
|
|
|
|
TEST_CLASS: Type[pool.BaseTaskPool] = pool.BaseTaskPool
|
|
|
|
TEST_POOL_SIZE: int = 420
|
|
|
|
TEST_POOL_NAME: str = 'test123'
|
|
|
|
|
|
|
|
task_pool: pool.BaseTaskPool
|
2022-02-07 14:23:49 +01:00
|
|
|
log_lvl: int
|
|
|
|
|
2022-02-08 13:29:57 +01:00
|
|
|
def get_task_pool_init_params(self) -> dict:
|
|
|
|
return {'pool_size': self.TEST_POOL_SIZE, 'name': self.TEST_POOL_NAME}
|
2022-02-05 19:34:52 +01:00
|
|
|
|
2022-02-08 13:29:57 +01:00
|
|
|
def setUp(self) -> None:
|
2022-02-24 19:16:24 +01:00
|
|
|
self.log_lvl = pool.log.level
|
|
|
|
pool.log.setLevel(999)
|
2022-02-08 13:29:57 +01:00
|
|
|
self._pools = self.TEST_CLASS._pools
|
|
|
|
# These three methods are called during initialization, so we mock them by default during setup:
|
|
|
|
self._add_pool_patcher = patch.object(self.TEST_CLASS, '_add_pool')
|
|
|
|
self.pool_size_patcher = patch.object(self.TEST_CLASS, 'pool_size', new_callable=PropertyMock)
|
|
|
|
self.dunder_str_patcher = patch.object(self.TEST_CLASS, '__str__')
|
2022-02-05 19:34:52 +01:00
|
|
|
self.mock__add_pool = self._add_pool_patcher.start()
|
2022-02-06 13:08:39 +01:00
|
|
|
self.mock_pool_size = self.pool_size_patcher.start()
|
2022-02-08 13:29:57 +01:00
|
|
|
self.mock___str__ = self.dunder_str_patcher.start()
|
2022-02-05 19:34:52 +01:00
|
|
|
self.mock__add_pool.return_value = self.mock_idx = 123
|
|
|
|
self.mock___str__.return_value = self.mock_str = 'foobar'
|
|
|
|
|
2022-02-08 13:29:57 +01:00
|
|
|
self.task_pool = self.TEST_CLASS(**self.get_task_pool_init_params())
|
2022-02-05 19:34:52 +01:00
|
|
|
|
|
|
|
def tearDown(self) -> None:
|
2022-02-08 13:29:57 +01:00
|
|
|
self.TEST_CLASS._pools.clear()
|
2022-02-05 19:34:52 +01:00
|
|
|
self._add_pool_patcher.stop()
|
2022-02-06 13:08:39 +01:00
|
|
|
self.pool_size_patcher.stop()
|
2022-02-08 13:29:57 +01:00
|
|
|
self.dunder_str_patcher.stop()
|
2022-02-24 19:16:24 +01:00
|
|
|
pool.log.setLevel(self.log_lvl)
|
2022-02-08 13:29:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
class BaseTaskPoolTestCase(CommonTestCase):
|
2022-02-05 19:34:52 +01:00
|
|
|
|
|
|
|
def test__add_pool(self):
|
|
|
|
self.assertListEqual(EMPTY_LIST, self._pools)
|
|
|
|
self._add_pool_patcher.stop()
|
2022-02-08 13:29:57 +01:00
|
|
|
output = pool.BaseTaskPool._add_pool(self.task_pool)
|
2022-02-05 19:34:52 +01:00
|
|
|
self.assertEqual(0, output)
|
2022-02-08 13:29:57 +01:00
|
|
|
self.assertListEqual([self.task_pool], pool.BaseTaskPool._pools)
|
2022-02-05 19:34:52 +01:00
|
|
|
|
|
|
|
def test_init(self):
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(0, self.task_pool._num_started)
|
|
|
|
|
2022-02-08 23:09:33 +01:00
|
|
|
self.assertFalse(self.task_pool._locked)
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertFalse(self.task_pool._closed)
|
2022-02-08 13:29:57 +01:00
|
|
|
self.assertEqual(self.TEST_POOL_NAME, self.task_pool._name)
|
2022-02-24 19:16:24 +01:00
|
|
|
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._tasks_running)
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._tasks_cancelled)
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._tasks_ended)
|
|
|
|
|
|
|
|
self.assertIsInstance(self.task_pool._enough_room, Semaphore)
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._task_groups)
|
|
|
|
|
2022-03-30 16:17:34 +02:00
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._group_meta_tasks_running)
|
|
|
|
self.assertSetEqual(EMPTY_SET, self.task_pool._meta_tasks_cancelled)
|
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(self.mock_idx, self.task_pool._idx)
|
|
|
|
|
2022-02-05 19:34:52 +01:00
|
|
|
self.mock__add_pool.assert_called_once_with(self.task_pool)
|
2022-02-08 13:29:57 +01:00
|
|
|
self.mock_pool_size.assert_called_once_with(self.TEST_POOL_SIZE)
|
2022-02-05 19:34:52 +01:00
|
|
|
self.mock___str__.assert_called_once_with()
|
|
|
|
|
|
|
|
def test___str__(self):
|
2022-02-08 13:29:57 +01:00
|
|
|
self.dunder_str_patcher.stop()
|
|
|
|
expected_str = f'{pool.BaseTaskPool.__name__}-{self.TEST_POOL_NAME}'
|
2022-02-05 19:34:52 +01:00
|
|
|
self.assertEqual(expected_str, str(self.task_pool))
|
2022-02-08 13:29:57 +01:00
|
|
|
self.task_pool._name = None
|
2022-02-05 19:34:52 +01:00
|
|
|
expected_str = f'{pool.BaseTaskPool.__name__}-{self.task_pool._idx}'
|
|
|
|
self.assertEqual(expected_str, str(self.task_pool))
|
|
|
|
|
2022-02-06 13:08:39 +01:00
|
|
|
def test_pool_size(self):
|
|
|
|
self.pool_size_patcher.stop()
|
2022-03-17 13:52:02 +01:00
|
|
|
self.task_pool._enough_room._value = self.TEST_POOL_SIZE
|
2022-02-08 13:29:57 +01:00
|
|
|
self.assertEqual(self.TEST_POOL_SIZE, self.task_pool.pool_size)
|
2022-02-06 13:08:39 +01:00
|
|
|
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
self.task_pool.pool_size = -1
|
|
|
|
|
|
|
|
self.task_pool.pool_size = new_size = 69
|
2022-03-17 13:52:02 +01:00
|
|
|
self.assertEqual(new_size, self.task_pool._enough_room._value)
|
2022-02-06 13:08:39 +01:00
|
|
|
|
2022-02-08 23:09:33 +01:00
|
|
|
def test_is_locked(self):
|
|
|
|
self.task_pool._locked = FOO
|
|
|
|
self.assertEqual(FOO, self.task_pool.is_locked)
|
|
|
|
|
|
|
|
def test_lock(self):
|
|
|
|
assert not self.task_pool._locked
|
|
|
|
self.task_pool.lock()
|
|
|
|
self.assertTrue(self.task_pool._locked)
|
|
|
|
self.task_pool.lock()
|
|
|
|
self.assertTrue(self.task_pool._locked)
|
|
|
|
|
|
|
|
def test_unlock(self):
|
|
|
|
self.task_pool._locked = True
|
|
|
|
self.task_pool.unlock()
|
|
|
|
self.assertFalse(self.task_pool._locked)
|
|
|
|
self.task_pool.unlock()
|
|
|
|
self.assertFalse(self.task_pool._locked)
|
2022-02-05 19:34:52 +01:00
|
|
|
|
|
|
|
def test_num_running(self):
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._tasks_running = {1: FOO, 2: BAR, 3: BAZ}
|
2022-02-05 19:34:52 +01:00
|
|
|
self.assertEqual(3, self.task_pool.num_running)
|
|
|
|
|
2022-03-17 13:52:02 +01:00
|
|
|
def test_num_cancelled(self):
|
|
|
|
self.task_pool._tasks_cancelled = {1: FOO, 2: BAR, 3: BAZ}
|
|
|
|
self.assertEqual(3, self.task_pool.num_cancelled)
|
2022-02-05 19:34:52 +01:00
|
|
|
|
|
|
|
def test_num_ended(self):
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._tasks_ended = {1: FOO, 2: BAR, 3: BAZ}
|
2022-02-05 19:34:52 +01:00
|
|
|
self.assertEqual(3, self.task_pool.num_ended)
|
|
|
|
|
|
|
|
def test_is_full(self):
|
2022-02-06 13:08:39 +01:00
|
|
|
self.assertEqual(self.task_pool._enough_room.locked(), self.task_pool.is_full)
|
2022-02-05 19:34:52 +01:00
|
|
|
|
2022-03-07 14:21:24 +01:00
|
|
|
def test_get_group_ids(self):
|
2022-02-24 19:16:24 +01:00
|
|
|
group_name, ids = 'abcdef', [1, 2, 3]
|
|
|
|
self.task_pool._task_groups[group_name] = MagicMock(__iter__=lambda _: iter(ids))
|
2022-03-07 14:21:24 +01:00
|
|
|
self.assertEqual(set(ids), self.task_pool.get_group_ids(group_name))
|
2022-02-24 19:16:24 +01:00
|
|
|
with self.assertRaises(exceptions.InvalidGroupName):
|
2022-03-07 14:21:24 +01:00
|
|
|
self.task_pool.get_group_ids(group_name, 'something else')
|
2022-02-24 19:16:24 +01:00
|
|
|
|
|
|
|
async def test__check_start(self):
|
|
|
|
self.task_pool._closed = True
|
|
|
|
mock_coroutine, mock_coroutine_function = AsyncMock()(), AsyncMock()
|
|
|
|
try:
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
self.task_pool._check_start(awaitable=None, function=None)
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
self.task_pool._check_start(awaitable=mock_coroutine, function=mock_coroutine_function)
|
|
|
|
with self.assertRaises(exceptions.NotCoroutine):
|
|
|
|
self.task_pool._check_start(awaitable=mock_coroutine_function, function=None)
|
|
|
|
with self.assertRaises(exceptions.NotCoroutine):
|
|
|
|
self.task_pool._check_start(awaitable=None, function=mock_coroutine)
|
|
|
|
with self.assertRaises(exceptions.PoolIsClosed):
|
|
|
|
self.task_pool._check_start(awaitable=mock_coroutine, function=None)
|
|
|
|
self.task_pool._closed = False
|
|
|
|
self.task_pool._locked = True
|
|
|
|
with self.assertRaises(exceptions.PoolIsLocked):
|
|
|
|
self.task_pool._check_start(awaitable=mock_coroutine, function=None, ignore_lock=False)
|
|
|
|
self.assertIsNone(self.task_pool._check_start(awaitable=mock_coroutine, function=None, ignore_lock=True))
|
|
|
|
finally:
|
|
|
|
await mock_coroutine
|
|
|
|
|
2022-02-05 19:34:52 +01:00
|
|
|
def test__task_name(self):
|
|
|
|
i = 123
|
|
|
|
self.assertEqual(f'{self.mock_str}_Task-{i}', self.task_pool._task_name(i))
|
2022-02-07 14:23:49 +01:00
|
|
|
|
2022-02-07 17:31:43 +01:00
|
|
|
@patch.object(pool, 'execute_optional')
|
2022-02-07 14:23:49 +01:00
|
|
|
@patch.object(pool.BaseTaskPool, '_task_name', return_value=FOO)
|
2022-02-07 17:31:43 +01:00
|
|
|
async def test__task_cancellation(self, mock__task_name: MagicMock, mock_execute_optional: AsyncMock):
|
2022-02-07 14:23:49 +01:00
|
|
|
task_id, mock_task, mock_callback = 1, MagicMock(), MagicMock()
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._tasks_running[task_id] = mock_task
|
2022-02-07 14:23:49 +01:00
|
|
|
self.assertIsNone(await self.task_pool._task_cancellation(task_id, mock_callback))
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertNotIn(task_id, self.task_pool._tasks_running)
|
|
|
|
self.assertEqual(mock_task, self.task_pool._tasks_cancelled[task_id])
|
2022-02-07 14:23:49 +01:00
|
|
|
mock__task_name.assert_called_with(task_id)
|
2022-02-07 17:31:43 +01:00
|
|
|
mock_execute_optional.assert_awaited_once_with(mock_callback, args=(task_id, ))
|
2022-02-07 14:23:49 +01:00
|
|
|
|
2022-02-07 17:31:43 +01:00
|
|
|
@patch.object(pool, 'execute_optional')
|
2022-02-07 14:23:49 +01:00
|
|
|
@patch.object(pool.BaseTaskPool, '_task_name', return_value=FOO)
|
2022-02-07 17:31:43 +01:00
|
|
|
async def test__task_ending(self, mock__task_name: MagicMock, mock_execute_optional: AsyncMock):
|
2022-02-07 14:23:49 +01:00
|
|
|
task_id, mock_task, mock_callback = 1, MagicMock(), MagicMock()
|
|
|
|
self.task_pool._enough_room._value = room = 123
|
|
|
|
|
|
|
|
# End running task:
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._tasks_running[task_id] = mock_task
|
2022-02-07 14:23:49 +01:00
|
|
|
self.assertIsNone(await self.task_pool._task_ending(task_id, mock_callback))
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertNotIn(task_id, self.task_pool._tasks_running)
|
|
|
|
self.assertEqual(mock_task, self.task_pool._tasks_ended[task_id])
|
2022-02-07 14:23:49 +01:00
|
|
|
self.assertEqual(room + 1, self.task_pool._enough_room._value)
|
|
|
|
mock__task_name.assert_called_with(task_id)
|
2022-02-07 17:31:43 +01:00
|
|
|
mock_execute_optional.assert_awaited_once_with(mock_callback, args=(task_id, ))
|
2022-02-07 14:23:49 +01:00
|
|
|
mock__task_name.reset_mock()
|
2022-02-07 17:31:43 +01:00
|
|
|
mock_execute_optional.reset_mock()
|
2022-02-07 14:23:49 +01:00
|
|
|
|
|
|
|
# End cancelled task:
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._tasks_cancelled[task_id] = self.task_pool._tasks_ended.pop(task_id)
|
2022-02-07 14:23:49 +01:00
|
|
|
self.assertIsNone(await self.task_pool._task_ending(task_id, mock_callback))
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertNotIn(task_id, self.task_pool._tasks_cancelled)
|
|
|
|
self.assertEqual(mock_task, self.task_pool._tasks_ended[task_id])
|
2022-02-07 14:23:49 +01:00
|
|
|
self.assertEqual(room + 2, self.task_pool._enough_room._value)
|
|
|
|
mock__task_name.assert_called_with(task_id)
|
2022-02-07 17:31:43 +01:00
|
|
|
mock_execute_optional.assert_awaited_once_with(mock_callback, args=(task_id, ))
|
2022-02-07 14:23:49 +01:00
|
|
|
|
|
|
|
@patch.object(pool.BaseTaskPool, '_task_ending')
|
|
|
|
@patch.object(pool.BaseTaskPool, '_task_cancellation')
|
|
|
|
@patch.object(pool.BaseTaskPool, '_task_name', return_value=FOO)
|
|
|
|
async def test__task_wrapper(self, mock__task_name: MagicMock,
|
|
|
|
mock__task_cancellation: AsyncMock, mock__task_ending: AsyncMock):
|
|
|
|
task_id = 42
|
|
|
|
mock_cancel_cb, mock_end_cb = MagicMock(), MagicMock()
|
|
|
|
mock_coroutine_func = AsyncMock(return_value=FOO, side_effect=CancelledError)
|
|
|
|
|
|
|
|
# Cancelled during execution:
|
|
|
|
mock_awaitable = mock_coroutine_func()
|
|
|
|
output = await self.task_pool._task_wrapper(mock_awaitable, task_id,
|
|
|
|
end_callback=mock_end_cb, cancel_callback=mock_cancel_cb)
|
|
|
|
self.assertIsNone(output)
|
|
|
|
mock_coroutine_func.assert_awaited_once()
|
|
|
|
mock__task_name.assert_called_with(task_id)
|
|
|
|
mock__task_cancellation.assert_awaited_once_with(task_id, custom_callback=mock_cancel_cb)
|
|
|
|
mock__task_ending.assert_awaited_once_with(task_id, custom_callback=mock_end_cb)
|
|
|
|
|
|
|
|
mock_coroutine_func.reset_mock(side_effect=True)
|
|
|
|
mock__task_name.reset_mock()
|
|
|
|
mock__task_cancellation.reset_mock()
|
|
|
|
mock__task_ending.reset_mock()
|
|
|
|
|
|
|
|
# Not cancelled:
|
|
|
|
mock_awaitable = mock_coroutine_func()
|
|
|
|
output = await self.task_pool._task_wrapper(mock_awaitable, task_id,
|
|
|
|
end_callback=mock_end_cb, cancel_callback=mock_cancel_cb)
|
|
|
|
self.assertEqual(FOO, output)
|
|
|
|
mock_coroutine_func.assert_awaited_once()
|
|
|
|
mock__task_name.assert_called_with(task_id)
|
|
|
|
mock__task_cancellation.assert_not_awaited()
|
|
|
|
mock__task_ending.assert_awaited_once_with(task_id, custom_callback=mock_end_cb)
|
|
|
|
|
|
|
|
@patch.object(pool, 'create_task')
|
|
|
|
@patch.object(pool.BaseTaskPool, '_task_wrapper', new_callable=MagicMock)
|
|
|
|
@patch.object(pool.BaseTaskPool, '_task_name', return_value=FOO)
|
2022-02-24 19:16:24 +01:00
|
|
|
@patch.object(pool, 'TaskGroupRegister')
|
|
|
|
@patch.object(pool.BaseTaskPool, '_check_start')
|
|
|
|
async def test__start_task(self, mock__check_start: MagicMock, mock_reg_cls: MagicMock, mock__task_name: MagicMock,
|
|
|
|
mock__task_wrapper: AsyncMock, mock_create_task: MagicMock):
|
|
|
|
mock_group_reg = set_up_mock_group_register(mock_reg_cls)
|
2022-02-07 14:23:49 +01:00
|
|
|
mock_create_task.return_value = mock_task = MagicMock()
|
|
|
|
mock__task_wrapper.return_value = mock_wrapped = MagicMock()
|
2022-02-24 19:16:24 +01:00
|
|
|
mock_coroutine, mock_cancel_cb, mock_end_cb = MagicMock(), MagicMock(), MagicMock()
|
|
|
|
self.task_pool._num_started = count = 123
|
2022-02-07 14:23:49 +01:00
|
|
|
self.task_pool._enough_room._value = room = 123
|
2022-02-24 19:16:24 +01:00
|
|
|
group_name, ignore_lock = 'testgroup', True
|
|
|
|
output = await self.task_pool._start_task(mock_coroutine, group_name=group_name, ignore_lock=ignore_lock,
|
2022-02-07 14:23:49 +01:00
|
|
|
end_callback=mock_end_cb, cancel_callback=mock_cancel_cb)
|
|
|
|
self.assertEqual(count, output)
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__check_start.assert_called_once_with(awaitable=mock_coroutine, ignore_lock=ignore_lock)
|
2022-02-07 14:23:49 +01:00
|
|
|
self.assertEqual(room - 1, self.task_pool._enough_room._value)
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(mock_group_reg, self.task_pool._task_groups[group_name])
|
|
|
|
mock_reg_cls.assert_called_once_with()
|
|
|
|
mock_group_reg.__aenter__.assert_awaited_once_with()
|
|
|
|
mock_group_reg.add.assert_called_once_with(count)
|
2022-02-07 14:23:49 +01:00
|
|
|
mock__task_name.assert_called_once_with(count)
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__task_wrapper.assert_called_once_with(mock_coroutine, count, mock_end_cb, mock_cancel_cb)
|
|
|
|
mock_create_task.assert_called_once_with(coro=mock_wrapped, name=FOO)
|
|
|
|
self.assertEqual(mock_task, self.task_pool._tasks_running[count])
|
|
|
|
mock_group_reg.__aexit__.assert_awaited_once()
|
2022-02-07 14:23:49 +01:00
|
|
|
|
|
|
|
@patch.object(pool.BaseTaskPool, '_task_name', return_value=FOO)
|
|
|
|
def test__get_running_task(self, mock__task_name: MagicMock):
|
|
|
|
task_id, mock_task = 555, MagicMock()
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._tasks_running[task_id] = mock_task
|
2022-02-07 14:23:49 +01:00
|
|
|
output = self.task_pool._get_running_task(task_id)
|
|
|
|
self.assertEqual(mock_task, output)
|
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._tasks_cancelled[task_id] = self.task_pool._tasks_running.pop(task_id)
|
2022-02-07 14:23:49 +01:00
|
|
|
with self.assertRaises(exceptions.AlreadyCancelled):
|
|
|
|
self.task_pool._get_running_task(task_id)
|
|
|
|
mock__task_name.assert_called_once_with(task_id)
|
|
|
|
mock__task_name.reset_mock()
|
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._tasks_ended[task_id] = self.task_pool._tasks_cancelled.pop(task_id)
|
2022-02-07 14:23:49 +01:00
|
|
|
with self.assertRaises(exceptions.TaskEnded):
|
|
|
|
self.task_pool._get_running_task(task_id)
|
|
|
|
mock__task_name.assert_called_once_with(task_id)
|
|
|
|
mock__task_name.reset_mock()
|
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
del self.task_pool._tasks_ended[task_id]
|
2022-02-07 14:23:49 +01:00
|
|
|
with self.assertRaises(exceptions.InvalidTaskID):
|
|
|
|
self.task_pool._get_running_task(task_id)
|
|
|
|
mock__task_name.assert_not_called()
|
|
|
|
|
|
|
|
@patch.object(pool.BaseTaskPool, '_get_running_task')
|
|
|
|
def test_cancel(self, mock__get_running_task: MagicMock):
|
|
|
|
task_id1, task_id2, task_id3 = 1, 4, 9
|
|
|
|
mock__get_running_task.return_value.cancel = mock_cancel = MagicMock()
|
|
|
|
self.assertIsNone(self.task_pool.cancel(task_id1, task_id2, task_id3, msg=FOO))
|
|
|
|
mock__get_running_task.assert_has_calls([call(task_id1), call(task_id2), call(task_id3)])
|
|
|
|
mock_cancel.assert_has_calls([call(msg=FOO), call(msg=FOO), call(msg=FOO)])
|
|
|
|
|
2022-03-30 16:17:34 +02:00
|
|
|
def test__cancel_group_meta_tasks(self):
|
|
|
|
mock_task1, mock_task2 = MagicMock(), MagicMock()
|
|
|
|
self.task_pool._group_meta_tasks_running[BAR] = {mock_task1, mock_task2}
|
|
|
|
self.assertIsNone(self.task_pool._cancel_group_meta_tasks(FOO))
|
|
|
|
self.assertDictEqual({BAR: {mock_task1, mock_task2}}, self.task_pool._group_meta_tasks_running)
|
|
|
|
self.assertSetEqual(EMPTY_SET, self.task_pool._meta_tasks_cancelled)
|
|
|
|
mock_task1.cancel.assert_not_called()
|
|
|
|
mock_task2.cancel.assert_not_called()
|
|
|
|
|
|
|
|
self.assertIsNone(self.task_pool._cancel_group_meta_tasks(BAR))
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._group_meta_tasks_running)
|
|
|
|
self.assertSetEqual({mock_task1, mock_task2}, self.task_pool._meta_tasks_cancelled)
|
|
|
|
mock_task1.cancel.assert_called_once_with()
|
|
|
|
mock_task2.cancel.assert_called_once_with()
|
|
|
|
|
|
|
|
@patch.object(pool.BaseTaskPool, '_cancel_group_meta_tasks')
|
|
|
|
def test__cancel_and_remove_all_from_group(self, mock__cancel_group_meta_tasks: MagicMock):
|
2022-02-24 19:16:24 +01:00
|
|
|
task_id = 555
|
|
|
|
mock_cancel = MagicMock()
|
2022-03-30 16:17:34 +02:00
|
|
|
|
|
|
|
def add_mock_task_to_running(_):
|
|
|
|
self.task_pool._tasks_running[task_id] = MagicMock(cancel=mock_cancel)
|
|
|
|
# We add the fake task to the `_tasks_running` dictionary as a side effect of calling the mocked method,
|
|
|
|
# to verify that it is called first, before the cancellation loop starts.
|
|
|
|
mock__cancel_group_meta_tasks.side_effect = add_mock_task_to_running
|
2022-02-24 19:16:24 +01:00
|
|
|
|
|
|
|
class MockRegister(set, MagicMock):
|
|
|
|
pass
|
|
|
|
self.assertIsNone(self.task_pool._cancel_and_remove_all_from_group(' ', MockRegister({task_id, 'x'}), msg=FOO))
|
|
|
|
mock_cancel.assert_called_once_with(msg=FOO)
|
|
|
|
|
|
|
|
@patch.object(pool.BaseTaskPool, '_cancel_and_remove_all_from_group')
|
|
|
|
async def test_cancel_group(self, mock__cancel_and_remove_all_from_group: MagicMock):
|
|
|
|
mock_grp_aenter, mock_grp_aexit = AsyncMock(), AsyncMock()
|
|
|
|
mock_group_reg = MagicMock(__aenter__=mock_grp_aenter, __aexit__=mock_grp_aexit)
|
|
|
|
self.task_pool._task_groups[FOO] = mock_group_reg
|
|
|
|
with self.assertRaises(exceptions.InvalidGroupName):
|
|
|
|
await self.task_pool.cancel_group(BAR)
|
|
|
|
mock__cancel_and_remove_all_from_group.assert_not_called()
|
|
|
|
mock_grp_aenter.assert_not_called()
|
|
|
|
mock_grp_aexit.assert_not_called()
|
|
|
|
self.assertIsNone(await self.task_pool.cancel_group(FOO, msg=BAR))
|
|
|
|
mock__cancel_and_remove_all_from_group.assert_called_once_with(FOO, mock_group_reg, msg=BAR)
|
|
|
|
mock_grp_aenter.assert_awaited_once_with()
|
|
|
|
mock_grp_aexit.assert_awaited_once()
|
|
|
|
|
|
|
|
@patch.object(pool.BaseTaskPool, '_cancel_and_remove_all_from_group')
|
|
|
|
async def test_cancel_all(self, mock__cancel_and_remove_all_from_group: MagicMock):
|
|
|
|
mock_grp_aenter, mock_grp_aexit = AsyncMock(), AsyncMock()
|
|
|
|
mock_group_reg = MagicMock(__aenter__=mock_grp_aenter, __aexit__=mock_grp_aexit)
|
|
|
|
self.task_pool._task_groups[BAR] = mock_group_reg
|
|
|
|
self.assertIsNone(await self.task_pool.cancel_all(FOO))
|
|
|
|
mock__cancel_and_remove_all_from_group.assert_called_once_with(BAR, mock_group_reg, msg=FOO)
|
|
|
|
mock_grp_aenter.assert_awaited_once_with()
|
|
|
|
mock_grp_aexit.assert_awaited_once()
|
2022-02-07 14:23:49 +01:00
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
def test__pop_ended_meta_tasks(self):
|
|
|
|
mock_task, mock_done_task1 = MagicMock(done=lambda: False), MagicMock(done=lambda: True)
|
|
|
|
self.task_pool._group_meta_tasks_running[FOO] = {mock_task, mock_done_task1}
|
|
|
|
mock_done_task2, mock_done_task3 = MagicMock(done=lambda: True), MagicMock(done=lambda: True)
|
|
|
|
self.task_pool._group_meta_tasks_running[BAR] = {mock_done_task2, mock_done_task3}
|
|
|
|
expected_output = {mock_done_task1, mock_done_task2, mock_done_task3}
|
|
|
|
output = self.task_pool._pop_ended_meta_tasks()
|
|
|
|
self.assertSetEqual(expected_output, output)
|
|
|
|
self.assertDictEqual({FOO: {mock_task}}, self.task_pool._group_meta_tasks_running)
|
|
|
|
|
2022-03-30 16:17:34 +02:00
|
|
|
@patch.object(pool.BaseTaskPool, '_pop_ended_meta_tasks')
|
|
|
|
async def test_flush(self, mock__pop_ended_meta_tasks: MagicMock):
|
|
|
|
# Meta tasks:
|
2022-02-24 19:16:24 +01:00
|
|
|
mock_ended_meta_task = AsyncMock()
|
|
|
|
mock__pop_ended_meta_tasks.return_value = {mock_ended_meta_task()}
|
|
|
|
mock_cancelled_meta_task = AsyncMock(side_effect=CancelledError)
|
|
|
|
self.task_pool._meta_tasks_cancelled = {mock_cancelled_meta_task()}
|
2022-03-30 16:17:34 +02:00
|
|
|
# Actual tasks:
|
|
|
|
mock_ended_func, mock_cancelled_func = AsyncMock(), AsyncMock(side_effect=Exception)
|
|
|
|
self.task_pool._tasks_ended = {123: mock_ended_func()}
|
|
|
|
self.task_pool._tasks_cancelled = {456: mock_cancelled_func()}
|
|
|
|
|
|
|
|
self.assertIsNone(await self.task_pool.flush(return_exceptions=True))
|
|
|
|
|
|
|
|
# Meta tasks:
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__pop_ended_meta_tasks.assert_called_once_with()
|
|
|
|
mock_ended_meta_task.assert_awaited_once_with()
|
|
|
|
mock_cancelled_meta_task.assert_awaited_once_with()
|
|
|
|
self.assertSetEqual(EMPTY_SET, self.task_pool._meta_tasks_cancelled)
|
2022-03-30 16:17:34 +02:00
|
|
|
# Actual tasks:
|
|
|
|
mock_ended_func.assert_awaited_once_with()
|
|
|
|
mock_cancelled_func.assert_awaited_once_with()
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._tasks_ended)
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._tasks_cancelled)
|
2022-02-24 19:16:24 +01:00
|
|
|
|
2022-03-30 12:37:32 +02:00
|
|
|
@patch.object(pool.BaseTaskPool, 'lock')
|
2022-03-30 16:17:34 +02:00
|
|
|
async def test_gather_and_close(self, mock_lock: MagicMock):
|
|
|
|
# Meta tasks:
|
2022-02-24 19:16:24 +01:00
|
|
|
mock_meta_task1, mock_meta_task2 = AsyncMock(), AsyncMock()
|
|
|
|
self.task_pool._group_meta_tasks_running = {FOO: {mock_meta_task1()}, BAR: {mock_meta_task2()}}
|
|
|
|
mock_cancelled_meta_task = AsyncMock(side_effect=CancelledError)
|
|
|
|
self.task_pool._meta_tasks_cancelled = {mock_cancelled_meta_task()}
|
2022-03-30 16:17:34 +02:00
|
|
|
# Actual tasks:
|
|
|
|
mock_running_func = AsyncMock()
|
|
|
|
mock_ended_func, mock_cancelled_func = AsyncMock(), AsyncMock(side_effect=Exception)
|
|
|
|
self.task_pool._tasks_ended = {123: mock_ended_func()}
|
|
|
|
self.task_pool._tasks_cancelled = {456: mock_cancelled_func()}
|
|
|
|
self.task_pool._tasks_running = {789: mock_running_func()}
|
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertIsNone(await self.task_pool.gather_and_close(return_exceptions=True))
|
2022-03-30 16:17:34 +02:00
|
|
|
|
2022-03-30 12:37:32 +02:00
|
|
|
mock_lock.assert_called_once_with()
|
2022-03-30 16:17:34 +02:00
|
|
|
# Meta tasks:
|
2022-02-24 19:16:24 +01:00
|
|
|
mock_meta_task1.assert_awaited_once_with()
|
|
|
|
mock_meta_task2.assert_awaited_once_with()
|
|
|
|
mock_cancelled_meta_task.assert_awaited_once_with()
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._group_meta_tasks_running)
|
|
|
|
self.assertSetEqual(EMPTY_SET, self.task_pool._meta_tasks_cancelled)
|
2022-03-30 16:17:34 +02:00
|
|
|
# Actual tasks:
|
|
|
|
mock_ended_func.assert_awaited_once_with()
|
|
|
|
mock_cancelled_func.assert_awaited_once_with()
|
|
|
|
mock_running_func.assert_awaited_once_with()
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._tasks_ended)
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._tasks_cancelled)
|
|
|
|
self.assertDictEqual(EMPTY_DICT, self.task_pool._tasks_running)
|
|
|
|
self.assertTrue(self.task_pool._closed)
|
|
|
|
|
|
|
|
|
|
|
|
class TaskPoolTestCase(CommonTestCase):
|
|
|
|
TEST_CLASS = pool.TaskPool
|
|
|
|
task_pool: pool.TaskPool
|
2022-02-24 19:16:24 +01:00
|
|
|
|
2022-03-30 12:37:32 +02:00
|
|
|
def test__generate_group_name(self):
|
2022-02-24 19:16:24 +01:00
|
|
|
prefix, func = 'x y z', AsyncMock(__name__=BAR)
|
2022-03-30 15:31:38 +02:00
|
|
|
base_name = f'{prefix}-{BAR}-group'
|
2022-03-30 12:37:32 +02:00
|
|
|
self.task_pool._task_groups = {
|
2022-03-30 15:31:38 +02:00
|
|
|
f'{base_name}-0': MagicMock(),
|
|
|
|
f'{base_name}-1': MagicMock(),
|
|
|
|
f'{base_name}-100': MagicMock(),
|
2022-03-30 12:37:32 +02:00
|
|
|
}
|
2022-03-30 15:31:38 +02:00
|
|
|
expected_output = f'{base_name}-2'
|
2022-03-30 12:37:32 +02:00
|
|
|
output = self.task_pool._generate_group_name(prefix, func)
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(expected_output, output)
|
|
|
|
|
2022-02-08 13:29:57 +01:00
|
|
|
@patch.object(pool.TaskPool, '_start_task')
|
2022-02-24 19:16:24 +01:00
|
|
|
async def test__apply_num(self, mock__start_task: AsyncMock):
|
|
|
|
group_name = FOO + BAR
|
|
|
|
mock_awaitable = object()
|
2022-02-08 13:29:57 +01:00
|
|
|
mock_func = MagicMock(return_value=mock_awaitable)
|
2022-02-24 19:16:24 +01:00
|
|
|
args, kwargs, num = (FOO, BAR), {'a': 1, 'b': 2}, 3
|
2022-02-08 13:29:57 +01:00
|
|
|
end_cb, cancel_cb = MagicMock(), MagicMock()
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertIsNone(await self.task_pool._apply_num(group_name, mock_func, args, kwargs, num, end_cb, cancel_cb))
|
|
|
|
mock_func.assert_has_calls(3 * [call(*args, **kwargs)])
|
|
|
|
mock__start_task.assert_has_awaits(3 * [
|
|
|
|
call(mock_awaitable, group_name=group_name, end_callback=end_cb, cancel_callback=cancel_cb)
|
|
|
|
])
|
2022-02-08 13:29:57 +01:00
|
|
|
|
|
|
|
mock_func.reset_mock()
|
|
|
|
mock__start_task.reset_mock()
|
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertIsNone(await self.task_pool._apply_num(group_name, mock_func, args, None, num, end_cb, cancel_cb))
|
|
|
|
mock_func.assert_has_calls(num * [call(*args)])
|
|
|
|
mock__start_task.assert_has_awaits(num * [
|
|
|
|
call(mock_awaitable, group_name=group_name, end_callback=end_cb, cancel_callback=cancel_cb)
|
|
|
|
])
|
2022-02-08 13:29:57 +01:00
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
@patch.object(pool, 'create_task')
|
|
|
|
@patch.object(pool.TaskPool, '_apply_num', new_callable=MagicMock())
|
|
|
|
@patch.object(pool, 'TaskGroupRegister')
|
|
|
|
@patch.object(pool.TaskPool, '_generate_group_name')
|
|
|
|
@patch.object(pool.BaseTaskPool, '_check_start')
|
|
|
|
async def test_apply(self, mock__check_start: MagicMock, mock__generate_group_name: MagicMock,
|
|
|
|
mock_reg_cls: MagicMock, mock__apply_num: MagicMock, mock_create_task: MagicMock):
|
|
|
|
mock__generate_group_name.return_value = generated_name = 'name 123'
|
|
|
|
mock_group_reg = set_up_mock_group_register(mock_reg_cls)
|
|
|
|
mock__apply_num.return_value = mock_apply_coroutine = object()
|
2022-03-29 20:01:44 +02:00
|
|
|
mock_create_task.return_value = fake_task = object()
|
2022-02-24 19:16:24 +01:00
|
|
|
mock_func, num, group_name = MagicMock(), 3, FOO + BAR
|
2022-02-08 13:29:57 +01:00
|
|
|
args, kwargs = (FOO, BAR), {'a': 1, 'b': 2}
|
|
|
|
end_cb, cancel_cb = MagicMock(), MagicMock()
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._task_groups = {}
|
|
|
|
|
|
|
|
def check_assertions(_group_name, _output):
|
|
|
|
self.assertEqual(_group_name, _output)
|
|
|
|
mock__check_start.assert_called_once_with(function=mock_func)
|
|
|
|
self.assertEqual(mock_group_reg, self.task_pool._task_groups[_group_name])
|
|
|
|
mock_group_reg.__aenter__.assert_awaited_once_with()
|
2022-03-29 20:01:44 +02:00
|
|
|
mock__apply_num.assert_called_once_with(_group_name, mock_func, args, kwargs, num,
|
|
|
|
end_callback=end_cb, cancel_callback=cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
mock_create_task.assert_called_once_with(mock_apply_coroutine)
|
|
|
|
mock_group_reg.__aexit__.assert_awaited_once()
|
2022-03-29 20:01:44 +02:00
|
|
|
self.assertSetEqual({fake_task}, self.task_pool._group_meta_tasks_running[group_name])
|
2022-02-24 19:16:24 +01:00
|
|
|
|
|
|
|
output = await self.task_pool.apply(mock_func, args, kwargs, num, group_name, end_cb, cancel_cb)
|
|
|
|
check_assertions(group_name, output)
|
|
|
|
mock__generate_group_name.assert_not_called()
|
|
|
|
|
|
|
|
mock__check_start.reset_mock()
|
|
|
|
self.task_pool._task_groups.clear()
|
|
|
|
mock_group_reg.__aenter__.reset_mock()
|
|
|
|
mock__apply_num.reset_mock()
|
|
|
|
mock_create_task.reset_mock()
|
|
|
|
mock_group_reg.__aexit__.reset_mock()
|
|
|
|
|
|
|
|
output = await self.task_pool.apply(mock_func, args, kwargs, num, None, end_cb, cancel_cb)
|
|
|
|
check_assertions(generated_name, output)
|
|
|
|
mock__generate_group_name.assert_called_once_with('apply', mock_func)
|
2022-02-08 13:29:57 +01:00
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
@patch.object(pool, 'execute_optional')
|
|
|
|
async def test__get_map_end_callback(self, mock_execute_optional: AsyncMock):
|
|
|
|
semaphore, mock_end_cb = Semaphore(1), MagicMock()
|
|
|
|
wrapped = pool.TaskPool._get_map_end_callback(semaphore, mock_end_cb)
|
|
|
|
task_id = 1234
|
|
|
|
await wrapped(task_id)
|
|
|
|
self.assertEqual(2, semaphore._value)
|
|
|
|
mock_execute_optional.assert_awaited_once_with(mock_end_cb, args=(task_id,))
|
|
|
|
|
2022-02-08 13:29:57 +01:00
|
|
|
@patch.object(pool, 'star_function')
|
|
|
|
@patch.object(pool.TaskPool, '_start_task')
|
2022-02-24 19:16:24 +01:00
|
|
|
@patch.object(pool.TaskPool, '_get_map_end_callback')
|
2022-03-16 16:57:03 +01:00
|
|
|
@patch.object(pool, 'Semaphore')
|
|
|
|
async def test__queue_consumer(self, mock_semaphore_cls: MagicMock, mock__get_map_end_callback: MagicMock,
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__start_task: AsyncMock, mock_star_function: MagicMock):
|
2022-03-26 10:49:32 +01:00
|
|
|
n = 2
|
|
|
|
mock_semaphore_cls.return_value = semaphore = Semaphore(n)
|
2022-03-16 16:57:03 +01:00
|
|
|
mock__get_map_end_callback.return_value = map_cb = MagicMock()
|
|
|
|
awaitable = 'totally an awaitable'
|
2022-03-26 10:49:32 +01:00
|
|
|
mock_star_function.side_effect = [awaitable, Exception(), awaitable]
|
2022-03-16 16:57:03 +01:00
|
|
|
arg1, arg2, bad = 123456789, 'function argument', None
|
2022-03-26 10:49:32 +01:00
|
|
|
args = [arg1, bad, arg2]
|
2022-03-16 16:57:03 +01:00
|
|
|
group_name, mock_func, stars = 'whatever', MagicMock(__name__="mock"), 3
|
2022-02-08 13:29:57 +01:00
|
|
|
end_cb, cancel_cb = MagicMock(), MagicMock()
|
2022-03-26 10:49:32 +01:00
|
|
|
self.assertIsNone(await self.task_pool._arg_consumer(group_name, n, mock_func, args, stars, end_cb, cancel_cb))
|
|
|
|
# We expect the semaphore to be acquired 2 times, then be released once after the exception occurs, then
|
|
|
|
# acquired once more is reached. Since we initialized it with a value of 2, we expect it be locked.
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertTrue(semaphore.locked())
|
2022-03-26 10:49:32 +01:00
|
|
|
mock_semaphore_cls.assert_called_once_with(n)
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__get_map_end_callback.assert_called_once_with(semaphore, actual_end_callback=end_cb)
|
|
|
|
mock__start_task.assert_has_awaits(2 * [
|
|
|
|
call(awaitable, group_name=group_name, ignore_lock=True, end_callback=map_cb, cancel_callback=cancel_cb)
|
|
|
|
])
|
|
|
|
mock_star_function.assert_has_calls([
|
|
|
|
call(mock_func, arg1, arg_stars=stars),
|
2022-03-26 10:49:32 +01:00
|
|
|
call(mock_func, bad, arg_stars=stars),
|
|
|
|
call(mock_func, arg2, arg_stars=stars)
|
2022-02-24 19:16:24 +01:00
|
|
|
])
|
|
|
|
|
2022-03-25 12:58:18 +01:00
|
|
|
mock_semaphore_cls.reset_mock()
|
|
|
|
mock__get_map_end_callback.reset_mock()
|
|
|
|
mock__start_task.reset_mock()
|
|
|
|
mock_star_function.reset_mock()
|
|
|
|
|
|
|
|
# With a CancelledError thrown while starting a task:
|
|
|
|
mock_semaphore_cls.return_value = semaphore = Semaphore(1)
|
|
|
|
mock_star_function.side_effect = CancelledError()
|
2022-03-26 10:49:32 +01:00
|
|
|
self.assertIsNone(await self.task_pool._arg_consumer(group_name, n, mock_func, args, stars, end_cb, cancel_cb))
|
2022-03-25 12:58:18 +01:00
|
|
|
self.assertFalse(semaphore.locked())
|
2022-03-26 10:49:32 +01:00
|
|
|
mock_semaphore_cls.assert_called_once_with(n)
|
2022-03-25 12:58:18 +01:00
|
|
|
mock__get_map_end_callback.assert_called_once_with(semaphore, actual_end_callback=end_cb)
|
|
|
|
mock__start_task.assert_not_called()
|
|
|
|
mock_star_function.assert_called_once_with(mock_func, arg1, arg_stars=stars)
|
|
|
|
|
2022-02-08 13:29:57 +01:00
|
|
|
@patch.object(pool, 'create_task')
|
2022-03-26 10:49:32 +01:00
|
|
|
@patch.object(pool.TaskPool, '_arg_consumer', new_callable=MagicMock)
|
2022-02-24 19:16:24 +01:00
|
|
|
@patch.object(pool, 'TaskGroupRegister')
|
|
|
|
@patch.object(pool.BaseTaskPool, '_check_start')
|
2022-03-26 10:49:32 +01:00
|
|
|
async def test__map(self, mock__check_start: MagicMock, mock_reg_cls: MagicMock, mock__arg_consumer: MagicMock,
|
|
|
|
mock_create_task: MagicMock):
|
2022-02-24 19:16:24 +01:00
|
|
|
mock_group_reg = set_up_mock_group_register(mock_reg_cls)
|
2022-03-26 10:49:32 +01:00
|
|
|
mock__arg_consumer.return_value = fake_consumer = object()
|
|
|
|
mock_create_task.return_value = fake_task = object()
|
2022-02-24 19:16:24 +01:00
|
|
|
|
2022-03-26 10:49:32 +01:00
|
|
|
group_name, n = 'onetwothree', 0
|
2022-02-24 19:16:24 +01:00
|
|
|
func, arg_iter, stars = AsyncMock(), [55, 66, 77], 3
|
2022-02-08 13:29:57 +01:00
|
|
|
end_cb, cancel_cb = MagicMock(), MagicMock()
|
|
|
|
|
2022-02-24 19:16:24 +01:00
|
|
|
with self.assertRaises(ValueError):
|
2022-03-26 10:49:32 +01:00
|
|
|
await self.task_pool._map(group_name, n, func, arg_iter, stars, end_cb, cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__check_start.assert_called_once_with(function=func)
|
|
|
|
|
|
|
|
mock__check_start.reset_mock()
|
2022-02-08 13:29:57 +01:00
|
|
|
|
2022-03-26 10:49:32 +01:00
|
|
|
n = 1234
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._task_groups = {group_name: MagicMock()}
|
|
|
|
|
|
|
|
with self.assertRaises(exceptions.InvalidGroupName):
|
2022-03-26 10:49:32 +01:00
|
|
|
await self.task_pool._map(group_name, n, func, arg_iter, stars, end_cb, cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__check_start.assert_called_once_with(function=func)
|
|
|
|
|
|
|
|
mock__check_start.reset_mock()
|
|
|
|
|
|
|
|
self.task_pool._task_groups.clear()
|
|
|
|
|
2022-03-26 10:49:32 +01:00
|
|
|
self.assertIsNone(await self.task_pool._map(group_name, n, func, arg_iter, stars, end_cb, cancel_cb))
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__check_start.assert_called_once_with(function=func)
|
|
|
|
mock_reg_cls.assert_called_once_with()
|
|
|
|
self.task_pool._task_groups[group_name] = mock_group_reg
|
|
|
|
mock_group_reg.__aenter__.assert_awaited_once_with()
|
2022-03-26 10:49:32 +01:00
|
|
|
mock__arg_consumer.assert_called_once_with(group_name, n, func, arg_iter, stars,
|
|
|
|
end_callback=end_cb, cancel_callback=cancel_cb)
|
|
|
|
mock_create_task.assert_called_once_with(fake_consumer)
|
|
|
|
self.assertSetEqual({fake_task}, self.task_pool._group_meta_tasks_running[group_name])
|
2022-02-24 19:16:24 +01:00
|
|
|
mock_group_reg.__aexit__.assert_awaited_once()
|
2022-02-08 13:29:57 +01:00
|
|
|
|
|
|
|
@patch.object(pool.TaskPool, '_map')
|
2022-02-24 19:16:24 +01:00
|
|
|
@patch.object(pool.TaskPool, '_generate_group_name')
|
|
|
|
async def test_map(self, mock__generate_group_name: MagicMock, mock__map: AsyncMock):
|
|
|
|
mock__generate_group_name.return_value = generated_name = 'name 1 2 3'
|
2022-02-08 13:29:57 +01:00
|
|
|
mock_func = MagicMock()
|
2022-03-29 19:28:58 +02:00
|
|
|
arg_iter, num_concurrent, group_name = (FOO, BAR, 1, 2, 3), 2, FOO + BAR
|
2022-02-08 13:29:57 +01:00
|
|
|
end_cb, cancel_cb = MagicMock(), MagicMock()
|
2022-03-29 19:28:58 +02:00
|
|
|
output = await self.task_pool.map(mock_func, arg_iter, num_concurrent, group_name, end_cb, cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(group_name, output)
|
2022-03-29 19:28:58 +02:00
|
|
|
mock__map.assert_awaited_once_with(group_name, num_concurrent, mock_func, arg_iter, 0,
|
2022-02-24 19:16:24 +01:00
|
|
|
end_callback=end_cb, cancel_callback=cancel_cb)
|
|
|
|
mock__generate_group_name.assert_not_called()
|
|
|
|
|
|
|
|
mock__map.reset_mock()
|
2022-03-29 19:28:58 +02:00
|
|
|
output = await self.task_pool.map(mock_func, arg_iter, num_concurrent, None, end_cb, cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(generated_name, output)
|
2022-03-29 19:28:58 +02:00
|
|
|
mock__map.assert_awaited_once_with(generated_name, num_concurrent, mock_func, arg_iter, 0,
|
2022-02-08 13:29:57 +01:00
|
|
|
end_callback=end_cb, cancel_callback=cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__generate_group_name.assert_called_once_with('map', mock_func)
|
2022-02-08 13:29:57 +01:00
|
|
|
|
|
|
|
@patch.object(pool.TaskPool, '_map')
|
2022-02-24 19:16:24 +01:00
|
|
|
@patch.object(pool.TaskPool, '_generate_group_name')
|
|
|
|
async def test_starmap(self, mock__generate_group_name: MagicMock, mock__map: AsyncMock):
|
|
|
|
mock__generate_group_name.return_value = generated_name = 'name 1 2 3'
|
2022-02-08 13:29:57 +01:00
|
|
|
mock_func = MagicMock()
|
2022-03-29 19:28:58 +02:00
|
|
|
args_iter, num_concurrent, group_name = ([FOO], [BAR]), 2, FOO + BAR
|
2022-02-08 13:29:57 +01:00
|
|
|
end_cb, cancel_cb = MagicMock(), MagicMock()
|
2022-03-29 19:28:58 +02:00
|
|
|
output = await self.task_pool.starmap(mock_func, args_iter, num_concurrent, group_name, end_cb, cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(group_name, output)
|
2022-03-29 19:28:58 +02:00
|
|
|
mock__map.assert_awaited_once_with(group_name, num_concurrent, mock_func, args_iter, 1,
|
2022-02-24 19:16:24 +01:00
|
|
|
end_callback=end_cb, cancel_callback=cancel_cb)
|
|
|
|
mock__generate_group_name.assert_not_called()
|
|
|
|
|
|
|
|
mock__map.reset_mock()
|
2022-03-29 19:28:58 +02:00
|
|
|
output = await self.task_pool.starmap(mock_func, args_iter, num_concurrent, None, end_cb, cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(generated_name, output)
|
2022-03-29 19:28:58 +02:00
|
|
|
mock__map.assert_awaited_once_with(generated_name, num_concurrent, mock_func, args_iter, 1,
|
2022-02-08 13:29:57 +01:00
|
|
|
end_callback=end_cb, cancel_callback=cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__generate_group_name.assert_called_once_with('starmap', mock_func)
|
2022-02-08 13:29:57 +01:00
|
|
|
|
|
|
|
@patch.object(pool.TaskPool, '_map')
|
2022-02-24 19:16:24 +01:00
|
|
|
@patch.object(pool.TaskPool, '_generate_group_name')
|
|
|
|
async def test_doublestarmap(self, mock__generate_group_name: MagicMock, mock__map: AsyncMock):
|
|
|
|
mock__generate_group_name.return_value = generated_name = 'name 1 2 3'
|
2022-02-08 13:29:57 +01:00
|
|
|
mock_func = MagicMock()
|
2022-03-29 19:28:58 +02:00
|
|
|
kw_iter, num_concurrent, group_name = [{'a': FOO}, {'a': BAR}], 2, FOO + BAR
|
2022-02-08 13:29:57 +01:00
|
|
|
end_cb, cancel_cb = MagicMock(), MagicMock()
|
2022-03-29 19:28:58 +02:00
|
|
|
output = await self.task_pool.doublestarmap(mock_func, kw_iter, num_concurrent, group_name, end_cb, cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(group_name, output)
|
2022-03-29 19:28:58 +02:00
|
|
|
mock__map.assert_awaited_once_with(group_name, num_concurrent, mock_func, kw_iter, 2,
|
2022-02-08 13:29:57 +01:00
|
|
|
end_callback=end_cb, cancel_callback=cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
mock__generate_group_name.assert_not_called()
|
|
|
|
|
|
|
|
mock__map.reset_mock()
|
2022-03-29 19:28:58 +02:00
|
|
|
output = await self.task_pool.doublestarmap(mock_func, kw_iter, num_concurrent, None, end_cb, cancel_cb)
|
2022-02-24 19:16:24 +01:00
|
|
|
self.assertEqual(generated_name, output)
|
2022-03-29 19:28:58 +02:00
|
|
|
mock__map.assert_awaited_once_with(generated_name, num_concurrent, mock_func, kw_iter, 2,
|
2022-02-24 19:16:24 +01:00
|
|
|
end_callback=end_cb, cancel_callback=cancel_cb)
|
|
|
|
mock__generate_group_name.assert_called_once_with('doublestarmap', mock_func)
|
2022-02-08 14:30:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
class SimpleTaskPoolTestCase(CommonTestCase):
|
|
|
|
TEST_CLASS = pool.SimpleTaskPool
|
|
|
|
task_pool: pool.SimpleTaskPool
|
|
|
|
|
|
|
|
TEST_POOL_FUNC = AsyncMock(__name__=FOO)
|
|
|
|
TEST_POOL_ARGS = (FOO, BAR)
|
|
|
|
TEST_POOL_KWARGS = {'a': 1, 'b': 2}
|
|
|
|
TEST_POOL_END_CB = MagicMock()
|
|
|
|
TEST_POOL_CANCEL_CB = MagicMock()
|
|
|
|
|
|
|
|
def get_task_pool_init_params(self) -> dict:
|
|
|
|
return super().get_task_pool_init_params() | {
|
|
|
|
'func': self.TEST_POOL_FUNC,
|
|
|
|
'args': self.TEST_POOL_ARGS,
|
|
|
|
'kwargs': self.TEST_POOL_KWARGS,
|
|
|
|
'end_callback': self.TEST_POOL_END_CB,
|
|
|
|
'cancel_callback': self.TEST_POOL_CANCEL_CB,
|
|
|
|
}
|
|
|
|
|
|
|
|
def setUp(self) -> None:
|
|
|
|
self.base_class_init_patcher = patch.object(pool.BaseTaskPool, '__init__')
|
|
|
|
self.base_class_init = self.base_class_init_patcher.start()
|
|
|
|
super().setUp()
|
|
|
|
|
|
|
|
def tearDown(self) -> None:
|
|
|
|
self.base_class_init_patcher.stop()
|
|
|
|
|
|
|
|
def test_init(self):
|
|
|
|
self.assertEqual(self.TEST_POOL_FUNC, self.task_pool._func)
|
|
|
|
self.assertEqual(self.TEST_POOL_ARGS, self.task_pool._args)
|
|
|
|
self.assertEqual(self.TEST_POOL_KWARGS, self.task_pool._kwargs)
|
|
|
|
self.assertEqual(self.TEST_POOL_END_CB, self.task_pool._end_callback)
|
|
|
|
self.assertEqual(self.TEST_POOL_CANCEL_CB, self.task_pool._cancel_callback)
|
|
|
|
self.base_class_init.assert_called_once_with(pool_size=self.TEST_POOL_SIZE, name=self.TEST_POOL_NAME)
|
|
|
|
|
|
|
|
with self.assertRaises(exceptions.NotCoroutine):
|
|
|
|
pool.SimpleTaskPool(MagicMock())
|
|
|
|
|
|
|
|
def test_func_name(self):
|
|
|
|
self.assertEqual(self.TEST_POOL_FUNC.__name__, self.task_pool.func_name)
|
|
|
|
|
|
|
|
@patch.object(pool.SimpleTaskPool, '_start_task')
|
|
|
|
async def test__start_one(self, mock__start_task: AsyncMock):
|
|
|
|
mock__start_task.return_value = expected_output = 99
|
|
|
|
self.task_pool._func = MagicMock(return_value=BAR)
|
2022-03-30 15:31:38 +02:00
|
|
|
group_name = FOO + BAR + 'abc'
|
|
|
|
output = await self.task_pool._start_one(group_name)
|
2022-02-08 14:30:42 +01:00
|
|
|
self.assertEqual(expected_output, output)
|
|
|
|
self.task_pool._func.assert_called_once_with(*self.task_pool._args, **self.task_pool._kwargs)
|
2022-03-30 15:31:38 +02:00
|
|
|
mock__start_task.assert_awaited_once_with(BAR, group_name=group_name, end_callback=self.task_pool._end_callback,
|
2022-02-08 14:30:42 +01:00
|
|
|
cancel_callback=self.task_pool._cancel_callback)
|
|
|
|
|
|
|
|
@patch.object(pool.SimpleTaskPool, '_start_one')
|
|
|
|
async def test_start(self, mock__start_one: AsyncMock):
|
|
|
|
mock__start_one.return_value = FOO
|
|
|
|
num = 5
|
2022-03-30 15:31:38 +02:00
|
|
|
self.task_pool._start_calls = 42
|
2022-02-08 14:30:42 +01:00
|
|
|
output = await self.task_pool.start(num)
|
2022-03-30 15:31:38 +02:00
|
|
|
expected_output = 'start-group-42'
|
|
|
|
self.assertEqual(expected_output, output)
|
|
|
|
mock__start_one.assert_has_awaits(num * [call(expected_output)])
|
2022-02-08 14:30:42 +01:00
|
|
|
|
|
|
|
@patch.object(pool.SimpleTaskPool, 'cancel')
|
|
|
|
def test_stop(self, mock_cancel: MagicMock):
|
|
|
|
num = 2
|
|
|
|
id1, id2, id3 = 5, 6, 7
|
2022-02-24 19:16:24 +01:00
|
|
|
self.task_pool._tasks_running = {id1: FOO, id2: BAR, id3: FOO + BAR}
|
2022-02-08 14:30:42 +01:00
|
|
|
output = self.task_pool.stop(num)
|
|
|
|
expected_output = [id3, id2]
|
|
|
|
self.assertEqual(expected_output, output)
|
|
|
|
mock_cancel.assert_called_once_with(*expected_output)
|
|
|
|
mock_cancel.reset_mock()
|
|
|
|
|
|
|
|
num = 50
|
|
|
|
output = self.task_pool.stop(num)
|
|
|
|
expected_output = [id3, id2, id1]
|
|
|
|
self.assertEqual(expected_output, output)
|
|
|
|
mock_cancel.assert_called_once_with(*expected_output)
|
|
|
|
|
|
|
|
@patch.object(pool.SimpleTaskPool, 'num_running', new_callable=PropertyMock)
|
|
|
|
@patch.object(pool.SimpleTaskPool, 'stop')
|
|
|
|
def test_stop_all(self, mock_stop: MagicMock, mock_num_running: MagicMock):
|
|
|
|
mock_num_running.return_value = num = 9876
|
|
|
|
mock_stop.return_value = expected_output = 'something'
|
|
|
|
output = self.task_pool.stop_all()
|
|
|
|
self.assertEqual(expected_output, output)
|
|
|
|
mock_num_running.assert_called_once_with()
|
|
|
|
mock_stop.assert_called_once_with(num)
|
2022-02-24 19:16:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
def set_up_mock_group_register(mock_reg_cls: MagicMock) -> MagicMock:
|
|
|
|
mock_grp_aenter, mock_grp_aexit, mock_grp_add = AsyncMock(), AsyncMock(), MagicMock()
|
|
|
|
mock_reg_cls.return_value = mock_group_reg = MagicMock(__aenter__=mock_grp_aenter, __aexit__=mock_grp_aexit,
|
|
|
|
add=mock_grp_add)
|
|
|
|
return mock_group_reg
|