diff --git a/src/ahriman/application/handlers/handler.py b/src/ahriman/application/handlers/handler.py index 466a9388..850b6c0d 100644 --- a/src/ahriman/application/handlers/handler.py +++ b/src/ahriman/application/handlers/handler.py @@ -27,15 +27,18 @@ from typing import Set, Type from ahriman.application.lock import Lock from ahriman.core.configuration import Configuration -from ahriman.core.exceptions import MissingArchitecture +from ahriman.core.exceptions import MissingArchitecture, MultipleArchitecture from ahriman.models.repository_paths import RepositoryPaths class Handler: """ base handler class for command callbacks + :cvar ALLOW_MULTI_ARCHITECTURE_RUN: allow to run with multiple architectures """ + ALLOW_MULTI_ARCHITECTURE_RUN = True + @classmethod def _call(cls: Type[Handler], args: argparse.Namespace, architecture: str) -> bool: """ @@ -61,9 +64,18 @@ class Handler: :return: 0 on success, 1 otherwise """ architectures = cls.extract_architectures(args) - with Pool(len(architectures)) as pool: - result = pool.starmap( - cls._call, [(args, architecture) for architecture in architectures]) + + # actually we do not have to spawn another process if it is single-process application, do we? + if len(architectures) > 1: + if not cls.ALLOW_MULTI_ARCHITECTURE_RUN: + raise MultipleArchitecture(args.command) + + with Pool(len(architectures)) as pool: + result = pool.starmap( + cls._call, [(args, architecture) for architecture in architectures]) + else: + result = [cls._call(args, architectures.pop())] + return 0 if all(result) else 1 @classmethod diff --git a/src/ahriman/application/handlers/web.py b/src/ahriman/application/handlers/web.py index 1ed31fec..29c6524d 100644 --- a/src/ahriman/application/handlers/web.py +++ b/src/ahriman/application/handlers/web.py @@ -31,6 +31,8 @@ class Web(Handler): web server handler """ + ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes + @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, no_report: bool) -> None: diff --git a/src/ahriman/core/exceptions.py b/src/ahriman/core/exceptions.py index 1e452517..10a09989 100644 --- a/src/ahriman/core/exceptions.py +++ b/src/ahriman/core/exceptions.py @@ -109,6 +109,19 @@ class MissingArchitecture(Exception): Exception.__init__(self, f"Architecture required for subcommand {command}, but missing") +class MultipleArchitecture(Exception): + """ + exception which will be raised if multiple architectures are not supported by the handler + """ + + def __init__(self, command: str) -> None: + """ + default constructor + :param command: command name which throws exception + """ + Exception.__init__(self, f"Multiple architectures are not supported by subcommand {command}") + + class ReportFailed(Exception): """ report generation exception diff --git a/tests/ahriman/application/handlers/test_handler.py b/tests/ahriman/application/handlers/test_handler.py index 2d9bc383..ce7ac526 100644 --- a/tests/ahriman/application/handlers/test_handler.py +++ b/tests/ahriman/application/handlers/test_handler.py @@ -6,7 +6,7 @@ from pytest_mock import MockerFixture from ahriman.application.handlers import Handler from ahriman.core.configuration import Configuration -from ahriman.core.exceptions import MissingArchitecture +from ahriman.core.exceptions import MissingArchitecture, MultipleArchitecture def test_call(args: argparse.Namespace, mocker: MockerFixture) -> None: @@ -44,6 +44,29 @@ def test_execute(args: argparse.Namespace, mocker: MockerFixture) -> None: starmap_mock.assert_called_once() +def test_execute_multiple_not_supported(args: argparse.Namespace, mocker: MockerFixture) -> None: + """ + must raise an exception if multiple architectures are not supported by the handler + """ + args.architecture = ["i686", "x86_64"] + args.command = "web" + mocker.patch.object(Handler, "ALLOW_MULTI_ARCHITECTURE_RUN", False) + + with pytest.raises(MultipleArchitecture): + Handler.execute(args) + + +def test_execute_single(args: argparse.Namespace, mocker: MockerFixture) -> None: + """ + must run execution in current process if only one architecture supplied + """ + args.architecture = ["x86_64"] + starmap_mock = mocker.patch("multiprocessing.pool.Pool.starmap") + + Handler.execute(args) + starmap_mock.assert_not_called() + + def test_extract_architectures(args: argparse.Namespace, mocker: MockerFixture) -> None: """ must generate list of available architectures diff --git a/tests/ahriman/application/handlers/test_handler_web.py b/tests/ahriman/application/handlers/test_handler_web.py index 7de98bec..0f5e91e1 100644 --- a/tests/ahriman/application/handlers/test_handler_web.py +++ b/tests/ahriman/application/handlers/test_handler_web.py @@ -29,3 +29,10 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc Web.run(args, "x86_64", configuration, True) setup_mock.assert_called_once() run_mock.assert_called_once() + + +def test_disallow_multi_architecture_run() -> None: + """ + must not allow multi architecture run + """ + assert not Web.ALLOW_MULTI_ARCHITECTURE_RUN