"""test_executors.""" from __future__ import annotations import logging from concurrent.futures import Future, ThreadPoolExecutor from typing import TYPE_CHECKING, Any import pytest from python.parallelize import _parallelize_base, parallelize_process, parallelize_thread if TYPE_CHECKING: from collections.abc import Callable from pytest_mock import MockerFixture class MockFuture(Future): """MockFuture.""" def __init__(self, result: Any) -> None: # noqa: ANN401 """Init.""" super().__init__() self._result = result self._exception: BaseException | None = None self.set_result(result) def exception(self, timeout: float | None = None) -> BaseException | None: """Exception.""" logging.debug(f"{timeout}=") return self._exception def result(self, timeout: float | None = None) -> Any: # noqa: ANN401 """Result.""" logging.debug(f"{timeout}=") return self._result class MockPoolExecutor(ThreadPoolExecutor): """MockPoolExecutor.""" def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401 """Initializes a new ThreadPoolExecutor instance.""" super().__init__(*args, **kwargs) def submit(self, fn: Callable[..., Any], /, *args: Any, **kwargs: Any) -> Future: # noqa: ANN401 """Submits a callable to be executed with the given arguments. Args: fn: The callable to execute. *args: The positional arguments to pass to the callable. **kwargs: The keyword arguments to pass to the callable. Returns: A Future instance representing the execution of the callable. """ result = fn(*args, **kwargs) return MockFuture(result) def add(a: int, b: int) -> int: """Add.""" return a + b def test_parallelize_thread() -> None: """test_parallelize_thread.""" kwargs_list = [{"a": 1, "b": 2}, {"a": 3, "b": 4}] results = parallelize_thread(func=add, kwargs_list=kwargs_list, progress_tracker=1) assert results.results == [3, 7] assert not results.exceptions def test_parallelize_thread_exception() -> None: """test_parallelize_thread.""" kwargs_list: list[dict[str, int | None]] = [{"a": 1, "b": 2}, {"a": 3, "b": None}] results = parallelize_thread(func=add, kwargs_list=kwargs_list) assert results.results == [3] output = """[TypeError("unsupported operand type(s) for +: 'int' and 'NoneType'")]""" assert str(results.exceptions) == output def test_parallelize_process() -> None: """test_parallelize_process.""" kwargs_list = [{"a": 1, "b": 2}, {"a": 3, "b": 4}] results = parallelize_process(func=add, kwargs_list=kwargs_list) assert results.results == [3, 7] assert not results.exceptions def test_parallelize_process_to_many_max_workers(mocker: MockerFixture) -> None: """test_parallelize_process.""" mocker.patch(target="python" ".parallelize.cpu_count", return_value=1) with pytest.raises(RuntimeError, match="max_workers must be less than or equal to 1"): parallelize_process(func=add, kwargs_list=[{"a": 1, "b": 2}], max_workers=8) def test_executor_results_repr() -> None: """test_ExecutorResults_repr.""" results = parallelize_thread(func=add, kwargs_list=[{"a": 1, "b": 2}]) assert repr(results) == "results=[3] exceptions=[]" def test_early_error() -> None: """test_early_error.""" kwargs_list: list[dict[str, int | None]] = [{"a": 1, "b": 2}, {"a": 3, "b": None}] with pytest.raises(TypeError, match=r"unsupported operand type\(s\) for \+\: 'int' and 'NoneType'"): parallelize_thread(func=add, kwargs_list=kwargs_list, mode="early_error") def test_mock_pool_executor() -> None: """test_mock_pool_executor.""" results = _parallelize_base( executor_type=MockPoolExecutor, func=add, kwargs_list=[{"a": 1, "b": 2}, {"a": 3, "b": 4}], max_workers=None, progress_tracker=None, mode="normal", ) assert repr(results) == "results=[3, 7] exceptions=[]"