# #region Test.BlockingExecutors [C:3] [TYPE Module] [SEMANTICS test,async,executors,blocking,threadpool] # @BRIEF Tests for core/utils/executors.py — init, shutdown, run_blocking, run_cpu_blocking. # @RELATION BINDS_TO -> [BlockingExecutorsModule] from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).parent.parent / "src")) import unittest from unittest.mock import MagicMock, patch, AsyncMock import asyncio import pytest class TestInitShutdown: """init_executors and shutdown_executors.""" def test_init_creates_executors(self): from src.core.utils.executors import init_executors with patch("src.core.utils.executors.ThreadPoolExecutor") as mock_tpe: init_executors(db_workers=5, file_workers=3, git_workers=2) assert mock_tpe.call_count == 3 def test_shutdown_without_init(self): # shutdown should not raise when executors are None from src.core.utils.executors import shutdown_executors shutdown_executors() def test_shutdown_calls_shutdown(self): from src.core.utils.executors import init_executors, shutdown_executors import src.core.utils.executors as exec_mod with patch.object(exec_mod, "_db_executor", None), \ patch.object(exec_mod, "_file_executor", None), \ patch.object(exec_mod, "_git_executor", None): with patch("src.core.utils.executors.ThreadPoolExecutor") as mock_tpe: mock_executor = MagicMock() mock_tpe.return_value = mock_executor init_executors() shutdown_executors() assert mock_executor.shutdown.call_count >= 1 for call in mock_executor.shutdown.call_args_list: assert call == unittest.mock.call(wait=True, cancel_futures=True) def test_shutdown_no_wait(self): from src.core.utils.executors import init_executors, shutdown_executors import src.core.utils.executors as exec_mod with patch.object(exec_mod, "_db_executor", None), \ patch.object(exec_mod, "_file_executor", None), \ patch.object(exec_mod, "_git_executor", None): with patch("src.core.utils.executors.ThreadPoolExecutor") as mock_tpe: mock_executor = MagicMock() mock_tpe.return_value = mock_executor init_executors() shutdown_executors(wait=False, cancel_futures=False) assert mock_executor.shutdown.call_count >= 1 for call in mock_executor.shutdown.call_args_list: assert call == unittest.mock.call(wait=False, cancel_futures=False) class TestGetExecutor: """_get_executor — returns executor and semaphore by kind.""" def test_db_kind(self): from src.core.utils.executors import _get_executor, init_executors, shutdown_executors with patch("src.core.utils.executors.ThreadPoolExecutor"): init_executors() executor, sem = _get_executor("db") assert executor is not None assert sem is not None shutdown_executors() def test_file_kind(self): from src.core.utils.executors import _get_executor, init_executors, shutdown_executors with patch("src.core.utils.executors.ThreadPoolExecutor"): init_executors() executor, sem = _get_executor("file") assert executor is not None assert sem is not None shutdown_executors() def test_git_kind(self): from src.core.utils.executors import _get_executor, init_executors, shutdown_executors with patch("src.core.utils.executors.ThreadPoolExecutor"): init_executors() executor, sem = _get_executor("git") assert executor is not None assert sem is not None shutdown_executors() def test_unknown_kind_raises(self): from src.core.utils.executors import _get_executor, init_executors, shutdown_executors with patch("src.core.utils.executors.ThreadPoolExecutor"): init_executors() with pytest.raises(ValueError, match="Unknown executor kind"): _get_executor("unknown") shutdown_executors() class TestRunBlocking: """run_blocking — execute blocking function in named executor.""" @pytest.mark.asyncio async def test_runs_function(self): from src.core.utils.executors import run_blocking, init_executors, shutdown_executors with patch("src.core.utils.executors.ThreadPoolExecutor") as mock_tpe: mock_executor = MagicMock() mock_tpe.return_value = mock_executor init_executors() # Make run_in_executor work mock_loop = AsyncMock() mock_loop.run_in_executor.return_value = "result" get_running_loop = asyncio.get_running_loop with patch.object(asyncio, "get_running_loop", return_value=mock_loop): result = await run_blocking("db", lambda: "hello") assert result == "result" shutdown_executors() @pytest.mark.asyncio async def test_semaphore_acquisition_failure(self): from src.core.utils.executors import run_blocking, init_executors, shutdown_executors with patch("src.core.utils.executors.ThreadPoolExecutor"): init_executors(queue_timeout=0.1) with ( patch("src.core.utils.executors._db_semaphore") as mock_sem, pytest.raises(TimeoutError, match="queue timeout"), ): mock_sem.acquire.side_effect = TimeoutError() await run_blocking("db", lambda: "hello", timeout=0.1) shutdown_executors() @pytest.mark.asyncio async def test_semaphore_release_on_success(self): from src.core.utils.executors import run_blocking, init_executors, shutdown_executors with patch("src.core.utils.executors.ThreadPoolExecutor"): init_executors() mock_loop = AsyncMock() mock_loop.run_in_executor.return_value = "done" # Track semaphore acquire/release with patch.object(asyncio, "get_running_loop", return_value=mock_loop): result = await run_blocking("file", lambda: "work") assert result == "done" shutdown_executors() @pytest.mark.asyncio async def test_cancelled_error_propagates(self): from src.core.utils.executors import run_blocking, init_executors, shutdown_executors with patch("src.core.utils.executors.ThreadPoolExecutor"): init_executors() mock_loop = AsyncMock() mock_loop.run_in_executor.side_effect = asyncio.CancelledError() with ( patch.object(asyncio, "get_running_loop", return_value=mock_loop), pytest.raises(asyncio.CancelledError), ): await run_blocking("git", lambda: "fail") shutdown_executors() class TestRunCpuBlocking: """run_cpu_blocking — CPU-bound work in default executor.""" @pytest.mark.asyncio async def test_runs_in_default_executor(self): from src.core.utils.executors import run_cpu_blocking mock_loop = AsyncMock() mock_loop.run_in_executor.return_value = 42 with patch.object(asyncio, "get_running_loop", return_value=mock_loop): result = await run_cpu_blocking(lambda: 42) assert result == 42 mock_loop.run_in_executor.assert_called_once_with(None, mock_loop.run_in_executor.call_args[0][1]) # #endregion Test.BlockingExecutors