diff --git a/src/cruiz/commands/context.py b/src/cruiz/commands/context.py index f1f00181..51047f80 100755 --- a/src/cruiz/commands/context.py +++ b/src/cruiz/commands/context.py @@ -13,18 +13,18 @@ from attr.converters import to_bool -from cruiz.exceptions import RecipeInspectionError from cruiz.recipe.logs.command import CommandListWidgetItem, RecipeCommandHistoryWidget from cruiz.settings.managers.namedlocalcache import NamedLocalCacheSettingsReader import cruizlib.workers.api as workers_api from cruizlib.constants import DEFAULT_CACHE_NAME +from cruizlib.exceptions import RecipeInspectionError from cruizlib.interop.commandparameters import CommandParameters +from cruizlib.workers.metarequestconaninvocation import MetaRequestConanInvocation from cruizlib.workers.utils.text2html import text_to_html from .conanenv import get_conan_env from .conaninvocation import ConanInvocation -from .metarequestconaninvocation import MetaRequestConanInvocation if typing.TYPE_CHECKING: from cruizlib.interop.packagebinaryparameters import PackageBinaryParameters diff --git a/src/cruiz/load_recipe/loadrecipewizard.py b/src/cruiz/load_recipe/loadrecipewizard.py index d2ac69d1..4fbe6993 100644 --- a/src/cruiz/load_recipe/loadrecipewizard.py +++ b/src/cruiz/load_recipe/loadrecipewizard.py @@ -12,11 +12,11 @@ import cruiz.globals from cruiz.commands.context import managed_conan_context from cruiz.commands.logdetails import LogDetails -from cruiz.exceptions import RecipeAlreadyOpenError, RecipeDoesNotExistError from cruiz.pyside6.load_recipe_wizard import Ui_LoadRecipeWizard from cruiz.settings.managers.recipe import RecipeSettings, RecipeSettingsReader from cruizlib.constants import DEFAULT_CACHE_NAME +from cruizlib.exceptions import RecipeAlreadyOpenError, RecipeDoesNotExistError if typing.TYPE_CHECKING: from cruiz.recipe.recipe import Recipe diff --git a/src/cruiz/mainwindow.py b/src/cruiz/mainwindow.py index f0d8bb50..73a3ff24 100755 --- a/src/cruiz/mainwindow.py +++ b/src/cruiz/mainwindow.py @@ -17,12 +17,6 @@ from PySide6 import QtCore, QtGui, QtWidgets import cruiz.globals -from cruiz.exceptions import ( - InconsistentSettingsError, - RecipeAlreadyOpenError, - RecipeDoesNotExistError, - RecipeInspectionError, -) from cruiz.load_recipe.loadrecipewizard import LoadRecipeWizard from cruiz.manage_local_cache import ManageLocalCachesDialog from cruiz.recipe.recipewidget import RecipeWidget @@ -52,6 +46,12 @@ import cruizlib.globals import cruizlib.runcommands from cruizlib.environ import EnvironSaver +from cruizlib.exceptions import ( + InconsistentSettingsError, + RecipeAlreadyOpenError, + RecipeDoesNotExistError, + RecipeInspectionError, +) import psutil diff --git a/src/cruiz/recipe/recipewidget.py b/src/cruiz/recipe/recipewidget.py index 2b9182da..9ba68c4c 100755 --- a/src/cruiz/recipe/recipewidget.py +++ b/src/cruiz/recipe/recipewidget.py @@ -16,7 +16,6 @@ import cruiz.revealonfilesystem from cruiz.commands.context import ConanContext from cruiz.commands.logdetails import LogDetails -from cruiz.exceptions import RecipeInspectionError from cruiz.manage_local_cache import ManageLocalCachesDialog from cruiz.model.graphaslistmodel import DependenciesListModel, DependenciesTreeModel from cruiz.pyside6.recipe_window import Ui_RecipeWindow @@ -34,6 +33,7 @@ import cruizlib.globals import cruizlib.workers.api as workers_api +from cruizlib.exceptions import RecipeInspectionError from cruizlib.interop.commandparameters import CommandParameters from cruizlib.interop.dependencygraph import dependencygraph_from_node_dependees from cruizlib.workers.utils.text2html import text_to_html diff --git a/src/cruiz/recipe/toolbars/command.py b/src/cruiz/recipe/toolbars/command.py index 90f2d632..ce652557 100755 --- a/src/cruiz/recipe/toolbars/command.py +++ b/src/cruiz/recipe/toolbars/command.py @@ -8,7 +8,6 @@ from PySide6 import QtCore, QtGui, QtWidgets -from cruiz.exceptions import RecipeInspectionError from cruiz.settings.managers.compilercachepreferences import CompilerCacheSettingsReader from cruiz.settings.managers.generalpreferences import GeneralSettingsReader from cruiz.settings.managers.recipe import RecipeSettings, RecipeSettingsReader @@ -18,6 +17,7 @@ import cruizlib.workers.api as workers_api from cruizlib.constants import BuildFeatureConstants, CompilerCacheTypes from cruizlib.environ import EnvironSaver +from cruizlib.exceptions import RecipeInspectionError from cruizlib.interop.commandparameters import CommandParameters IS_CONAN_V1 = cruizlib.globals.CONAN_MAJOR_VERSION == 1 diff --git a/src/cruiz/settings/managers/recipe.py b/src/cruiz/settings/managers/recipe.py index 4bad256f..44250b43 100755 --- a/src/cruiz/settings/managers/recipe.py +++ b/src/cruiz/settings/managers/recipe.py @@ -11,7 +11,7 @@ from PySide6 import QtCore -from cruiz.exceptions import InconsistentSettingsError +from cruizlib.exceptions import InconsistentSettingsError from .basesettings import ( BaseSettings, diff --git a/src/cruiz/exceptions.py b/src/cruizlib/exceptions.py similarity index 50% rename from src/cruiz/exceptions.py rename to src/cruizlib/exceptions.py index cbd0d6e1..dde75c7a 100644 --- a/src/cruiz/exceptions.py +++ b/src/cruizlib/exceptions.py @@ -2,6 +2,8 @@ """Exception types.""" +import typing + class RecipeInspectionError(Exception): """An error inspecting the recipe has occurred.""" @@ -17,3 +19,18 @@ class RecipeAlreadyOpenError(Exception): class InconsistentSettingsError(Exception): """An error indicating some settings have been discovered that are inconsistent.""" + + +class MetaCommandFailureError(Exception): + """An error coming from a meta command.""" + + def __init__( + self, + message: str, + exception_type_name: str, + exception_traceback: typing.List[str], + ) -> None: + """Initialise the exception object.""" + super().__init__(message, exception_type_name, exception_traceback) + self.exception_type_name = exception_type_name + self.exception_traceback = exception_traceback diff --git a/src/cruizlib/workers/api/v1/meta.py b/src/cruizlib/workers/api/v1/meta.py index cb08ce8e..17789191 100755 --- a/src/cruizlib/workers/api/v1/meta.py +++ b/src/cruizlib/workers/api/v1/meta.py @@ -12,7 +12,14 @@ from attrs.converters import to_bool -from cruizlib.interop.message import End, Failure, Stderr, Success +from cruizlib.interop.message import ( + ConanLogMessage, + End, + Failure, + Stderr, + Stdout, + Success, +) from cruizlib.interop.pod import ConanHook, ConanRemote from . import worker @@ -478,6 +485,15 @@ def invoke( elif request == "create_default_profile": _create_default_profile(api) result = None + elif request == "test_stdout": + reply_queue.put(Stdout("Testing Stdout messaging")) + result = None + elif request == "test_stderr": + reply_queue.put(Stderr("Testing Stderr messaging")) + result = None + elif request == "test_conanlog": + reply_queue.put(ConanLogMessage("Testing ConanLog messaging")) + result = None else: raise ValueError( f"Meta command request not implemented: '{request}' " diff --git a/src/cruizlib/workers/api/v2/meta.py b/src/cruizlib/workers/api/v2/meta.py index 173905e7..129bc973 100755 --- a/src/cruizlib/workers/api/v2/meta.py +++ b/src/cruizlib/workers/api/v2/meta.py @@ -14,7 +14,14 @@ import typing import urllib.parse -from cruizlib.interop.message import End, Failure, Success +from cruizlib.interop.message import ( + ConanLogMessage, + End, + Failure, + Stderr, + Stdout, + Success, +) from cruizlib.interop.pod import ConanHook, ConanRemote from . import worker @@ -153,7 +160,7 @@ def _interop_profile_meta( return details -# pylint: disable=too-many-branches +# pylint: disable=too-many-branches,too-many-statements def invoke( request_queue: MultiProcessingStringJoinableQueueType, reply_queue: MultiProcessingMessageQueueType, @@ -196,6 +203,12 @@ def invoke( result = _interop_get_config_envvars(api) elif request == "profile_meta": result = _interop_profile_meta(api, request_params["name"][0]) + elif request == "test_stdout": + reply_queue.put(Stdout("Testing Stdout messaging")) + elif request == "test_stderr": + reply_queue.put(Stderr("Testing Stderr messaging")) + elif request == "test_conanlog": + reply_queue.put(ConanLogMessage("Testing ConanLog messaging")) else: raise ValueError( f"Meta command request not implemented: '{request}' " diff --git a/src/cruiz/commands/metarequestconaninvocation.py b/src/cruizlib/workers/metarequestconaninvocation.py similarity index 86% rename from src/cruiz/commands/metarequestconaninvocation.py rename to src/cruizlib/workers/metarequestconaninvocation.py index 80a1f55a..ca741aac 100755 --- a/src/cruiz/commands/metarequestconaninvocation.py +++ b/src/cruizlib/workers/metarequestconaninvocation.py @@ -19,6 +19,7 @@ import cruizlib.workers.api as workers_api from cruizlib.dumpobjecttypes import dump_object_types +from cruizlib.exceptions import MetaCommandFailureError from cruizlib.interop.commandparameters import CommandParameters from cruizlib.interop.message import ( ConanLogMessage, @@ -30,7 +31,7 @@ ) if typing.TYPE_CHECKING: - from .logdetails import LogDetails + from cruiz.commands.logdetails import LogDetails logger = logging.getLogger(__name__) @@ -100,11 +101,12 @@ def _invoke_conan_process(self, params: CommandParameters) -> None: def __check_for_conan_leakage(entry: typing.Any = None) -> None: if "conans" not in sys.modules: return - if entry: + # remaining code is exceptional and fatal, so coverage is not needed + if entry: # pragma: no cover logger.critical("Conan has leaked into message queue object dump:") dump_object_types(entry, loglevel="CRITICAL") - logger.critical("Conan has leaked into cruiz") - sys.exit(1) + logger.critical("Conan has leaked into cruiz") # pragma: no cover + sys.exit(1) # pragma: no cover def request_data( self, @@ -156,8 +158,15 @@ def request_data( assert self._reply_queue.empty() self.active = False if response is not None: - if isinstance(reply, Success): - return (response.payload, None) # type: ignore - if isinstance(reply, Failure): - return (None, response.exception) # type: ignore - raise RuntimeError("No success message") + if isinstance(response, Success): + return (response.payload, None) + if isinstance(response, Failure): + return ( + None, + MetaCommandFailureError( + response.message, + response.exception_type_name, + response.exception_traceback, + ), + ) + raise RuntimeError("No identified response") # pragma: no cover diff --git a/tests/workers/conftest.py b/tests/workers/conftest.py index 87bc6538..1e626aed 100644 --- a/tests/workers/conftest.py +++ b/tests/workers/conftest.py @@ -12,6 +12,7 @@ import stat import sys import typing +from unittest.mock import MagicMock import cruizlib.workers.api as workers_api from cruizlib.globals import CONAN_MAJOR_VERSION, CONAN_VERSION_COMPONENTS @@ -25,6 +26,7 @@ Stdout, Success, ) +from cruizlib.workers.metarequestconaninvocation import MetaRequestConanInvocation # pylint: disable=wrong-import-order import pytest @@ -189,6 +191,24 @@ def meta( process.close() +@pytest.fixture() +def cruiz_meta( + conan_local_cache: typing.Dict[str, str], +) -> typing.Generator[typing.Tuple[MetaRequestConanInvocation, MagicMock], None, None]: + """Use cruiz's meta setup and shutdown.""" + log_details_mock = MagicMock() + meta_invoc = MetaRequestConanInvocation( + parent=None, # type: ignore[arg-type] + added_environment=conan_local_cache, + removed_environment=[], + log_details=log_details_mock, + ) + + yield meta_invoc, log_details_mock + + meta_invoc.close() + + @pytest.fixture() def reply_queue_fixture() -> SingleprocessReplyQueueFixture: """ diff --git a/tests/workers/test_meta_cruiz.py b/tests/workers/test_meta_cruiz.py new file mode 100644 index 00000000..13b3d4b8 --- /dev/null +++ b/tests/workers/test_meta_cruiz.py @@ -0,0 +1,60 @@ +"""Test the cruiz meta worker functionality.""" + +from __future__ import annotations + +import typing + +from cruizlib.exceptions import MetaCommandFailureError +from cruizlib.globals import CONAN_MAJOR_VERSION, CONAN_VERSION_COMPONENTS + +if typing.TYPE_CHECKING: + from unittest.mock import MagicMock + + from cruizlib.workers.metarequestconaninvocation import MetaRequestConanInvocation + + +def test_meta_get_version( + cruiz_meta: typing.Tuple[MetaRequestConanInvocation, MagicMock], +) -> None: + """Via the meta worker: Get the version.""" + meta_request, _ = cruiz_meta + reply_payload, reply_exception = meta_request.request_data("version") + if CONAN_MAJOR_VERSION == 1: + assert reply_exception is None + assert isinstance(reply_payload, str) + assert reply_payload == ".".join([str(i) for i in CONAN_VERSION_COMPONENTS]) + else: + assert reply_payload is None + assert isinstance(reply_exception, MetaCommandFailureError) + assert reply_exception.exception_type_name == "ValueError" + assert str(reply_exception).startswith('("Meta command request not implemented') + + +def test_meta_stdout( + cruiz_meta: typing.Tuple[MetaRequestConanInvocation, MagicMock], +) -> None: + """Via the meta worker: Test stdout messages.""" + meta_request, log_details_mock = cruiz_meta + reply_payload, _ = meta_request.request_data("test_stdout") + assert reply_payload is None + log_details_mock.stdout.assert_called_once_with("Testing Stdout messaging") + + +def test_meta_stderr( + cruiz_meta: typing.Tuple[MetaRequestConanInvocation, MagicMock], +) -> None: + """Via the meta worker: Test stderr messages.""" + meta_request, log_details_mock = cruiz_meta + reply_payload, _ = meta_request.request_data("test_stderr") + assert reply_payload is None + log_details_mock.stderr.assert_called_once_with("Testing Stderr messaging") + + +def test_meta_conanlogmessage( + cruiz_meta: typing.Tuple[MetaRequestConanInvocation, MagicMock], +) -> None: + """Via the meta worker: Test ConanLog messages.""" + meta_request, log_details_mock = cruiz_meta + reply_payload, _ = meta_request.request_data("test_conanlog") + assert reply_payload is None + log_details_mock.conan_log.assert_called_once_with("Testing ConanLog messaging")