From 5000745534c380b939d29f7c8376a973a644effd Mon Sep 17 00:00:00 2001 From: janosmurai Date: Tue, 30 Apr 2024 15:22:25 +0200 Subject: [PATCH] The add-task command implemented. --- dem/cli/command/add_task_cmd.py | 18 ++++++++++++ dem/cli/main.py | 24 ++++++++++++--- dem/core/dev_env.py | 10 +++++-- docs/commands.md | 26 +++++++++++++++++ tests/cli/test_add_task_cmd.py | 51 ++++++++++++++++++++++++++++++++ tests/cli/test_main.py | 2 ++ tests/core/test_dev_env.py | 52 +++++++++++++++++++++++++++++---- 7 files changed, 171 insertions(+), 12 deletions(-) create mode 100644 dem/cli/command/add_task_cmd.py create mode 100644 tests/cli/test_add_task_cmd.py diff --git a/dem/cli/command/add_task_cmd.py b/dem/cli/command/add_task_cmd.py new file mode 100644 index 0000000..3afa75c --- /dev/null +++ b/dem/cli/command/add_task_cmd.py @@ -0,0 +1,18 @@ +from dem.core.platform import Platform +from dem.cli.console import stderr + +def execute(platform: Platform, dev_env_name: str, task_name: str, command: str) -> None: + """ Add a task to a Development Environment. + + Args: + platform -- the Platform + dev_env_name -- the Development Environment name + task_name -- the task name + command -- the command + """ + dev_env = platform.get_dev_env_by_name(dev_env_name) + if dev_env is None: + stderr.print(f"[red]Error: Development Environment '{dev_env_name}' not found![/]") + return + dev_env.add_task(task_name, command) + platform.flush_dev_env_properties() \ No newline at end of file diff --git a/dem/cli/main.py b/dem/cli/main.py index a515f77..42aad98 100644 --- a/dem/cli/main.py +++ b/dem/cli/main.py @@ -6,10 +6,11 @@ from typing_extensions import Annotated import os from dem import __command__, __app_name__ -from dem.cli.command import cp_cmd, import_cmd, info_cmd, list_cmd, create_cmd, modify_cmd, delete_cmd, \ - rename_cmd, run_cmd, export_cmd, clone_cmd, add_reg_cmd, list_reg_cmd, del_reg_cmd, add_cat_cmd, list_cat_cmd, del_cat_cmd, \ - add_host_cmd, set_default_cmd, uninstall_cmd, install_cmd, assign_cmd, init_cmd, \ - list_host_cmd, del_host_cmd, list_tools_cmd +from dem.cli.command import cp_cmd, import_cmd, info_cmd, list_cmd, create_cmd, modify_cmd, \ + delete_cmd, rename_cmd, run_cmd, export_cmd, clone_cmd, add_reg_cmd, \ + list_reg_cmd, del_reg_cmd, add_cat_cmd, list_cat_cmd, del_cat_cmd, \ + add_host_cmd, set_default_cmd, uninstall_cmd, install_cmd, assign_cmd, \ + init_cmd, list_host_cmd, del_host_cmd, list_tools_cmd, add_task_cmd from dem.cli.console import stdout from dem.core.platform import Platform from dem.core.exceptions import InternalError @@ -90,6 +91,21 @@ def autocomplete_host_name(incomplete: str) -> Generator: # DEM commands @typer_cli.command() +def add_task(dev_env_name: Annotated[str, typer.Argument(help="Name of the Development Environment to add the task to.", + autocompletion=autocomplete_dev_env_name)], + task_name: Annotated[str, typer.Argument(help="Name of the task.")], + command: Annotated[str, typer.Argument(help="The command the task should execute.")]) -> None: + """ + Add a new task to the Development Environment. + + The command will be executed when the `dem run dev_env_name task_name` command is called. The + command must be surrounded by quotes. + """ + if platform: + add_task_cmd.execute(platform, dev_env_name, task_name, command) + else: + raise InternalError("Error: The platform hasn't been initialized properly!") +@typer_cli.command() def set_default(dev_env_name: Annotated[str, typer.Argument(help="The name of the Development Environment to set as default.", autocompletion=autocomplete_installed_dev_env_name)]) -> None: diff --git a/dem/core/dev_env.py b/dem/core/dev_env.py index 8988173..0356500 100755 --- a/dem/core/dev_env.py +++ b/dem/core/dev_env.py @@ -45,8 +45,8 @@ def __init__(self, descriptor: dict | None = None, descriptor_path: str | None = self.name: str = descriptor["name"] self.tool_image_descriptors: list[dict[str, str]] = descriptor["tools"] self.tool_images: list[ToolImage] = [] - descriptor_installed = descriptor.get("installed", "False") - if "True" == descriptor_installed: + self.tasks: dict[str, str] = descriptor.get("tasks", {}) + if "True" == descriptor.get("installed", "False"): self.is_installed = True else: self.is_installed = False @@ -68,6 +68,9 @@ def assign_tool_image_instances(self, tool_images: ToolImages) -> None: tool_image = tool_images.all_tool_images.get(tool_image_name, ToolImage(tool_image_name)) self.tool_images.append(tool_image) + def add_task(self, task_name: str, command: str) -> None: + self.tasks[task_name] = command + def get_tool_image_status(self) -> Status: """ Get the status of the Tool Images. @@ -93,7 +96,8 @@ def get_deserialized(self, omit_is_installed: bool = False) -> dict[str, str]: """ dev_env_json_deserialized: dict = { "name": self.name, - "tools": self.tool_image_descriptors + "tools": self.tool_image_descriptors, + "tasks": self.tasks } if omit_is_installed is False: diff --git a/docs/commands.md b/docs/commands.md index fc4c0b8..850bdd6 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -12,6 +12,32 @@ title: Commands # Development Environment management +## **`dem add-task DEV_ENV_NAME TASK_NAME COMMAND`** + +**Description:** + +Add a new task to the Development Environment. + +A task is a command that can be run in the context of the Development Environment. +The task can be run with the `dem run` command. + +**Arguments:** + +| Argument | Description | Required | +|------------------|---------------------------------------------------------|----------------:| +| `DEV_ENV_NAME` | Name of the Development Environment. | :material-check:| +| `TASK_NAME` | Name of the task. | :material-check:| +| `COMMAND` | Command to run. Must be enclosed with quotes. | :material-check:| + +**Examples:** + +| Example | Description | +|--------------------|---------------------------------------------------------| +| `dem add-task dev_env_name list-dir "ls -la"` | Add a new command called `list-dir` that lists the content of the current directory. The task can be executed with `dem run dev_env_name list-dir`. | +| `dem add-task dev_env_name build "docker run --rm -v \"$(pwd)\":/work axemsolutions/make_gnu-arm:13.2 make"` | Add a new command called `build` that builds the project in a docker container. The task can be executed with `dem run dev_env_name build`. | + +--- + ## **`dem assign DEV_ENV_NAME, [PROJECT_PATH]`** **Description:** diff --git a/tests/cli/test_add_task_cmd.py b/tests/cli/test_add_task_cmd.py new file mode 100644 index 0000000..f578808 --- /dev/null +++ b/tests/cli/test_add_task_cmd.py @@ -0,0 +1,51 @@ +"""Unit tests for the add-task CLI command.""" +# tests/cli/test_add_task_cmd.py + +# Unit under test: +import dem.cli.main as main +import dem.cli.command.add_task_cmd as add_task_cmd + +# Test framework +from typer.testing import CliRunner +from unittest.mock import patch, MagicMock, call + +## Global test variables + +# In order to test stdout and stderr separately, the stderr can't be mixed into +# the stdout. +runner = CliRunner(mix_stderr=False) + +def test_add_task_cmd() -> None: + # Setup + mock_platform = MagicMock() + main.platform = mock_platform + + mock_dev_env = MagicMock() + mock_platform.get_dev_env_by_name.return_value = mock_dev_env + + # Run + result = runner.invoke(main.typer_cli, ["add-task", "my-dev-env", "my-task", "my-command"]) + + # Check + assert result.exit_code == 0 + + mock_platform.get_dev_env_by_name.assert_called_once_with("my-dev-env") + mock_dev_env.add_task.assert_called_once_with("my-task", "my-command") + mock_platform.flush_dev_env_properties.assert_called_once() + +@patch("dem.cli.command.add_task_cmd.stderr.print") +def test_execute_dev_env_not_found(mock_stderr_print: MagicMock) -> None: + # Setup + mock_platform = MagicMock() + mock_platform.get_dev_env_by_name.return_value = None + + test_dev_env_name = "my-dev-env" + test_task_name = "my-task" + test_command = "my-command" + + # Run + add_task_cmd.execute(mock_platform, test_dev_env_name, test_task_name, test_command) + + # Check + mock_platform.get_dev_env_by_name.assert_called_once_with(test_dev_env_name) + mock_stderr_print.assert_called_once_with(f"[red]Error: Development Environment '{test_dev_env_name}' not found![/]") \ No newline at end of file diff --git a/tests/cli/test_main.py b/tests/cli/test_main.py index e607c9f..78cecdb 100644 --- a/tests/cli/test_main.py +++ b/tests/cli/test_main.py @@ -193,10 +193,12 @@ def test_platform_not_initialized() -> None: test_path = "test_path" test_name = "test_name" test_url = "test_url" + test_command = "test_command" mock_ctx = MagicMock() main.platform = None units_to_test = { + main.add_task: [test_dev_env_name, test_name, test_command], main.set_default: [test_dev_env_name], main.list_: [], main.list_tools: [], diff --git a/tests/core/test_dev_env.py b/tests/core/test_dev_env.py index 96cf920..cad901a 100644 --- a/tests/core/test_dev_env.py +++ b/tests/core/test_dev_env.py @@ -15,7 +15,12 @@ def test_DevEnv() -> None: test_descriptor = { "name": "test_name", "installed": "True", - "tools": [MagicMock()] + "tools": [MagicMock()], + "tasks": { + "test_task_name1": "test_task_command1", + "test_task_name2": "test_task_command2", + "test_task_name3": "test_task_command3" + } } # Run unit under test @@ -24,6 +29,7 @@ def test_DevEnv() -> None: # Check expectations assert test_dev_env.name is test_descriptor["name"] assert test_dev_env.tool_image_descriptors is test_descriptor["tools"] + assert test_dev_env.tasks is test_descriptor["tasks"] @patch("dem.core.dev_env.json.load") @patch("dem.core.dev_env.open") @@ -35,7 +41,12 @@ def test_DevEnv_with_descriptor_path(mock_path_exists: MagicMock, mock_open: Mag test_descriptor = { "name": "test_name", "installed": "True", - "tools": [MagicMock()] + "tools": [MagicMock()], + "tasks": { + "test_task_name1": "test_task_command1", + "test_task_name2": "test_task_command2", + "test_task_name3": "test_task_command3" + } } mock_path_exists.return_value = True mock_file = MagicMock() @@ -49,6 +60,7 @@ def test_DevEnv_with_descriptor_path(mock_path_exists: MagicMock, mock_open: Mag assert test_dev_env.name is test_descriptor["name"] assert test_dev_env.tool_image_descriptors is test_descriptor["tools"] assert test_dev_env.is_installed is True + assert test_dev_env.tasks is test_descriptor["tasks"] mock_path_exists.assert_called_once_with(test_descriptor_path) mock_open.assert_called_once_with(test_descriptor_path, "r") @@ -131,6 +143,29 @@ def test_DevEnv_assign_tool_image_instances() -> None: for tool_image in test_dev_env.tool_images: assert tool_image is mock_tool_images.all_tool_images[tool_image.name] +def test_DevEnv_add_task() -> None: + # Test setup + test_descriptor = { + "name": "test_name", + "installed": "True", + "tools": [MagicMock()], + "tasks": { + "test_task_name1": "test_task_command1", + "test_task_name2": "test_task_command2", + "test_task_name3": "test_task_command3" + } + } + test_dev_env = dev_env.DevEnv(test_descriptor) + + test_task_name = "test_task_name4" + test_command = "test_task_command4" + + # Run unit under test + test_dev_env.add_task(test_task_name, test_command) + + # Check expectations + assert test_dev_env.tasks[test_task_name] == test_command + @patch.object(dev_env.DevEnv, "__init__") def test_DevEnv_get_tool_image_status(mock___init__: MagicMock) -> None: # Test setup @@ -222,7 +257,12 @@ def test_DevEnv_get_deserialized_is_installed_true() -> None: "image_name": "test_image_name4", "image_version": "test_image_tag4" }, - ] + ], + "tasks": { + "test_task_name1": "test_task_command1", + "test_task_name2": "test_task_command2", + "test_task_name3": "test_task_command3" + } } test_dev_env = dev_env.DevEnv(test_descriptor) @@ -254,7 +294,8 @@ def test_DevEnv_get_deserialized_is_installed_false() -> None: "image_name": "test_image_name4", "image_version": "test_image_tag4" }, - ] + ], + "tasks": {} } test_dev_env = dev_env.DevEnv(test_descriptor) @@ -286,7 +327,8 @@ def test_DevEnv_get_deserialized_omit_is_installed() -> None: "image_name": "test_image_name4", "image_version": "test_image_tag4" }, - ] + ], + "tasks": {} } test_dev_env = dev_env.DevEnv(test_descriptor)