aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuomei Yan <ruomei.yan@arm.com>2022-11-02 16:47:56 +0000
committerRuomei Yan <ruomei.yan@arm.com>2022-11-15 13:02:56 +0000
commit47fc50576e7040680c19e152592b2c5e5cc297f5 (patch)
treef10fc331e7bc7358c7da8cf3582d9428db4e7367
parentef73bb773df214f3f33f8e4ca7d276041106cad2 (diff)
downloadmlia-47fc50576e7040680c19e152592b2c5e5cc297f5.tar.gz
MLIA-649 Strip mlia backend management into a new command
* add entry point for mlia-backend in setup.cfg and main.py * add --force option for install from path: uninstall existing backend in ML Inference Advisor and install from given path * add uninstall and list program parameters: uninstall has backend_name as input arg, install has backend_name as a mandatory argument * add unit tests in test_cli_commands.py, test_cli_main.py, test_tools_metadata_common.py, test_tools_metadata_corstone.py * updated README.md * remove --download option for installing backend * add new lines for the display section when we do mlia-backen list * add case insensitive support for backend names in command line argument Change-Id: Icb89d8957fa6be4b767710e24fa074f26472674b
-rw-r--r--README.md22
-rw-r--r--setup.cfg1
-rw-r--r--src/mlia/cli/commands.py41
-rw-r--r--src/mlia/cli/common.py3
-rw-r--r--src/mlia/cli/main.py81
-rw-r--r--src/mlia/cli/options.py37
-rw-r--r--src/mlia/tools/metadata/common.py57
-rw-r--r--src/mlia/tools/metadata/corstone.py6
-rw-r--r--tests/test_cli_commands.py77
-rw-r--r--tests/test_cli_main.py51
-rw-r--r--tests/test_tools_metadata_common.py63
-rw-r--r--tests/test_tools_metadata_corstone.py17
12 files changed, 326 insertions, 130 deletions
diff --git a/README.md b/README.md
index 7d3726e..1b9f494 100644
--- a/README.md
+++ b/README.md
@@ -38,12 +38,22 @@ The ML Inference Advisor is designed to support multiple performance
estimators (backends) that could generate performance analysis for individual
types of hardware.
-The `backend` command is used to manage the installation of new backends.
-The `install` sub-command can be used to either
-
-* install a backend installed locally already (option `--path`) or
-* (if available) automatically download the necessary components and
- dependencies, install them and configure them properly (option `--download`).
+The `mlia-backend` command is used to manage the installation of new backends.
+
+* The `install` sub-command can be used after `mlia-backend` to:
+ * install a backend of ML Inference Advisor from a directory
+ which contains installed backend (option `--path`). The name (mandatory
+ argument `name`) of the backend can be case insensitive. If backend
+ is already installed, it is possible to use option `--force`
+ to force the installation
+ * (if available) automatically download the necessary components and
+ dependencies, install them and configure them properly (default behavior)
+
+* The `uninstall` sub-command can be used with option `backend_name` after
+ `mlia-backend` to remove the backend installation folder
+
+* The `list` sub-command can be used after `mlia-backend` to display
+ the installed and available for installation backends
The usage is:
diff --git a/setup.cfg b/setup.cfg
index f262484..93ffa6e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -49,6 +49,7 @@ where = src
[options.entry_points]
console_scripts =
mlia=mlia.cli.main:main
+ mlia-backend=mlia.cli.main:backend_main
[options.extras_require]
dev =
diff --git a/src/mlia/cli/commands.py b/src/mlia/cli/commands.py
index 72ae4bb..4be7f3e 100644
--- a/src/mlia/cli/commands.py
+++ b/src/mlia/cli/commands.py
@@ -29,7 +29,6 @@ from mlia.api import PathOrFileLike
from mlia.cli.config import get_installation_manager
from mlia.cli.options import parse_optimization_parameters
from mlia.utils.console import create_section_header
-from mlia.utils.types import only_one_selected
logger = logging.getLogger(__name__)
@@ -243,34 +242,36 @@ def optimization(
)
-def backend(
- backend_action: str,
+def backend_install(
+ name: str,
path: Path | None = None,
- download: bool = False,
- name: str | None = None,
i_agree_to_the_contained_eula: bool = False,
noninteractive: bool = False,
+ force: bool = False,
) -> None:
- """Backends configuration."""
+ """Install configuration."""
logger.info(CONFIG)
manager = get_installation_manager(noninteractive)
- if backend_action == "status":
- manager.show_env_details()
+ install_from_path = path is not None
- if backend_action == "install":
- install_from_path = path is not None
+ if install_from_path:
+ manager.install_from(cast(Path, path), name, force)
+ else:
+ eula_agreement = not i_agree_to_the_contained_eula
+ manager.download_and_install(name, eula_agreement)
- if not only_one_selected(install_from_path, download):
- raise Exception(
- "Please select only one action: download or "
- "provide path to the backend installation"
- )
- if install_from_path:
- manager.install_from(cast(Path, path), name)
+def backend_uninstall(
+ name: str,
+) -> None:
+ """Uninstall backend(s)."""
+ manager = get_installation_manager(noninteractive=True)
+ manager.uninstall(name)
+
- if download:
- eula_agreement = not i_agree_to_the_contained_eula
- manager.download_and_install(name, eula_agreement)
+def backend_list() -> None:
+ """List backend status."""
+ manager = get_installation_manager(noninteractive=True)
+ manager.show_env_details()
diff --git a/src/mlia/cli/common.py b/src/mlia/cli/common.py
index 3f60668..077f456 100644
--- a/src/mlia/cli/common.py
+++ b/src/mlia/cli/common.py
@@ -16,11 +16,12 @@ class CommandInfo:
aliases: list[str]
opt_groups: list[Callable[[argparse.ArgumentParser], None]]
is_default: bool = False
+ name: str | None = None
@property
def command_name(self) -> str:
"""Return command name."""
- return self.func.__name__
+ return self.name or self.func.__name__
@property
def command_name_and_aliases(self) -> list[str]:
diff --git a/src/mlia/cli/main.py b/src/mlia/cli/main.py
index d36d2d9..61b8f05 100644
--- a/src/mlia/cli/main.py
+++ b/src/mlia/cli/main.py
@@ -12,14 +12,17 @@ from pathlib import Path
from mlia import __version__
from mlia.cli.commands import all_tests
-from mlia.cli.commands import backend
+from mlia.cli.commands import backend_install
+from mlia.cli.commands import backend_list
+from mlia.cli.commands import backend_uninstall
from mlia.cli.commands import operators
from mlia.cli.commands import optimization
from mlia.cli.commands import performance
from mlia.cli.common import CommandInfo
from mlia.cli.helpers import CLIActionResolver
from mlia.cli.logging import setup_logging
-from mlia.cli.options import add_backend_options
+from mlia.cli.options import add_backend_install_options
+from mlia.cli.options import add_backend_uninstall_options
from mlia.cli.options import add_custom_supported_operators_options
from mlia.cli.options import add_debug_options
from mlia.cli.options import add_evaluation_options
@@ -99,42 +102,66 @@ def get_commands() -> list[CommandInfo]:
add_evaluation_options,
],
),
+ ]
+
+
+def backend_commands() -> list[CommandInfo]:
+ """Return commands configuration."""
+ return [
+ CommandInfo(
+ backend_install,
+ [],
+ [
+ add_backend_install_options,
+ add_debug_options,
+ ],
+ name="install",
+ ),
CommandInfo(
- backend,
+ backend_uninstall,
[],
[
- add_backend_options,
+ add_backend_uninstall_options,
add_debug_options,
],
+ name="uninstall",
+ ),
+ CommandInfo(
+ backend_list,
+ [],
+ [
+ add_debug_options,
+ ],
+ name="list",
),
]
-def get_default_command() -> str | None:
+def get_default_command(commands: list[CommandInfo]) -> str | None:
"""Get name of the default command."""
- commands = get_commands()
-
marked_as_default = [cmd.command_name for cmd in commands if cmd.is_default]
assert len(marked_as_default) <= 1, "Only one command could be marked as default"
return next(iter(marked_as_default), None)
-def get_possible_command_names() -> list[str]:
+def get_possible_command_names(commands: list[CommandInfo]) -> list[str]:
"""Get all possible command names including aliases."""
return [
name_or_alias
- for cmd in get_commands()
+ for cmd in commands
for name_or_alias in cmd.command_name_and_aliases
]
-def init_commands(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
+def init_commands(
+ parser: argparse.ArgumentParser, commands: list[CommandInfo]
+) -> argparse.ArgumentParser:
"""Init cli subcommands."""
subparsers = parser.add_subparsers(title="Commands", dest="command")
subparsers.required = True
- for command in get_commands():
+ for command in commands:
command_parser = subparsers.add_parser(
command.command_name,
aliases=command.aliases,
@@ -188,7 +215,6 @@ def run_command(args: argparse.Namespace) -> int:
try:
logger.info(INFO_MESSAGE)
-
args.func(**func_args)
return 0
except KeyboardInterrupt:
@@ -251,12 +277,14 @@ def init_subcommand_parser(parent: argparse.ArgumentParser) -> argparse.Argument
return parser
-def add_default_command_if_needed(args: list[str]) -> None:
+def add_default_command_if_needed(
+ args: list[str], input_commands: list[CommandInfo]
+) -> None:
"""Add default command to the list of the arguments if needed."""
- default_command = get_default_command()
+ default_command = get_default_command(input_commands)
if default_command and len(args) > 0:
- commands = get_possible_command_names()
+ commands = get_possible_command_names(input_commands)
help_or_version = ["-h", "--help", "-v", "--version"]
command_is_missing = args[0] not in [*commands, *help_or_version]
@@ -264,16 +292,31 @@ def add_default_command_if_needed(args: list[str]) -> None:
args.insert(0, default_command)
-def main(argv: list[str] | None = None) -> int:
- """Entry point of the application."""
+def generic_main(
+ commands: list[CommandInfo], argv: list[str] | None = None
+) -> argparse.Namespace:
+ """Enable multiple entry points."""
common_parser = init_common_parser()
subcommand_parser = init_subcommand_parser(common_parser)
- init_commands(subcommand_parser)
+ init_commands(subcommand_parser, commands)
common_args, subcommand_args = common_parser.parse_known_args(argv)
- add_default_command_if_needed(subcommand_args)
+
+ add_default_command_if_needed(subcommand_args, commands)
args = subcommand_parser.parse_args(subcommand_args, common_args)
+ return args
+
+
+def main(argv: list[str] | None = None) -> int:
+ """Entry point of the main application."""
+ args = generic_main(get_commands(), argv)
+ return run_command(args)
+
+
+def backend_main(argv: list[str] | None = None) -> int:
+ """Entry point of the backend application."""
+ args = generic_main(backend_commands(), argv)
return run_command(args)
diff --git a/src/mlia/cli/options.py b/src/mlia/cli/options.py
index f6dcf75..bf2f09b 100644
--- a/src/mlia/cli/options.py
+++ b/src/mlia/cli/options.py
@@ -131,7 +131,7 @@ def add_custom_supported_operators_options(parser: argparse.ArgumentParser) -> N
)
-def add_backend_options(parser: argparse.ArgumentParser) -> None:
+def add_backend_install_options(parser: argparse.ArgumentParser) -> None:
"""Add options for the backends configuration."""
def valid_directory(param: str) -> Path:
@@ -141,42 +141,39 @@ def add_backend_options(parser: argparse.ArgumentParser) -> None:
return dir_path
- subparsers = parser.add_subparsers(title="Backend actions", dest="backend_action")
- subparsers.required = True
-
- install_subparser = subparsers.add_parser(
- "install", help="Install backend", allow_abbrev=False
- )
- install_type_group = install_subparser.add_mutually_exclusive_group()
- install_type_group.required = True
- install_type_group.add_argument(
+ parser.add_argument(
"--path", type=valid_directory, help="Path to the installed backend"
)
- install_type_group.add_argument(
- "--download",
+ parser.add_argument(
+ "--i-agree-to-the-contained-eula",
default=False,
action="store_true",
- help="Download and install backend",
+ help=argparse.SUPPRESS,
)
- install_subparser.add_argument(
- "--i-agree-to-the-contained-eula",
+ parser.add_argument(
+ "--force",
default=False,
action="store_true",
- help=argparse.SUPPRESS,
+ help="Force reinstall backend in the specified path",
)
- install_subparser.add_argument(
+ parser.add_argument(
"--noninteractive",
default=False,
action="store_true",
help="Non interactive mode with automatic confirmation of every action",
)
- install_subparser.add_argument(
+ parser.add_argument(
"name",
- nargs="?",
help="Name of the backend to install",
)
- subparsers.add_parser("status", help="Show backends status")
+
+def add_backend_uninstall_options(parser: argparse.ArgumentParser) -> None:
+ """Add options for the backends configuration."""
+ parser.add_argument(
+ "name",
+ help="Name of the installed backend",
+ )
def add_evaluation_options(parser: argparse.ArgumentParser) -> None:
diff --git a/src/mlia/tools/metadata/common.py b/src/mlia/tools/metadata/common.py
index dd4571a..927be74 100644
--- a/src/mlia/tools/metadata/common.py
+++ b/src/mlia/tools/metadata/common.py
@@ -65,6 +65,10 @@ class Installation(ABC):
def install(self, install_type: InstallationType) -> None:
"""Install the backend."""
+ @abstractmethod
+ def uninstall(self) -> None:
+ """Uninstall the backend."""
+
InstallationFilter = Callable[[Installation], bool]
@@ -106,20 +110,21 @@ class SearchByNameFilter:
def __call__(self, installation: Installation) -> bool:
"""Installation filter."""
- return not self.backend_name or installation.name == self.backend_name
+ return (
+ not self.backend_name
+ or installation.name.casefold() == self.backend_name.casefold()
+ )
class InstallationManager(ABC):
"""Helper class for managing installations."""
@abstractmethod
- def install_from(self, backend_path: Path, backend_name: str | None) -> None:
+ def install_from(self, backend_path: Path, backend_name: str, force: bool) -> None:
"""Install backend from the local directory."""
@abstractmethod
- def download_and_install(
- self, backend_name: str | None, eula_agreement: bool
- ) -> None:
+ def download_and_install(self, backend_name: str, eula_agreement: bool) -> None:
"""Download and install backends."""
@abstractmethod
@@ -130,6 +135,10 @@ class InstallationManager(ABC):
def backend_installed(self, backend_name: str) -> bool:
"""Return true if requested backend installed."""
+ @abstractmethod
+ def uninstall(self, backend_name: str) -> None:
+ """Delete the existing installation."""
+
class InstallationFiltersMixin:
"""Mixin for filtering installation based on different conditions."""
@@ -145,7 +154,7 @@ class InstallationFiltersMixin:
]
def could_be_installed_from(
- self, backend_path: Path, backend_name: str | None
+ self, backend_path: Path, backend_name: str
) -> list[Installation]:
"""Return installations that could be installed from provided directory."""
return self.filter_by(
@@ -154,7 +163,7 @@ class InstallationFiltersMixin:
)
def could_be_downloaded_and_installed(
- self, backend_name: str | None = None
+ self, backend_name: str
) -> list[Installation]:
"""Return installations that could be downloaded and installed."""
return self.filter_by(
@@ -163,7 +172,7 @@ class InstallationFiltersMixin:
ReadyForInstallationFilter(),
)
- def already_installed(self, backend_name: str | None = None) -> list[Installation]:
+ def already_installed(self, backend_name: str = None) -> list[Installation]:
"""Return list of backends that are already installed."""
return self.filter_by(
AlreadyInstalledFilter(), SearchByNameFilter(backend_name)
@@ -185,7 +194,7 @@ class DefaultInstallationManager(InstallationManager, InstallationFiltersMixin):
self.noninteractive = noninteractive
def choose_installation_for_path(
- self, backend_path: Path, backend_name: str | None
+ self, backend_path: Path, backend_name: str, force: bool
) -> Installation | None:
"""Check available installation and select one if possible."""
installs = self.could_be_installed_from(backend_path, backend_name)
@@ -210,21 +219,33 @@ class DefaultInstallationManager(InstallationManager, InstallationFiltersMixin):
installation = installs[0]
if installation.already_installed:
logger.info(
- "%s was found in %s, but it has been already installed.",
+ "%s was found in %s, but it has been already installed "
+ "in the ML Inference Advisor.",
installation.name,
backend_path,
)
- return None
+ return installation if force else None
return installation
- def install_from(self, backend_path: Path, backend_name: str | None) -> None:
+ def install_from(
+ self, backend_path: Path, backend_name: str, force: bool = False
+ ) -> None:
"""Install from the provided directory."""
- installation = self.choose_installation_for_path(backend_path, backend_name)
+ installation = self.choose_installation_for_path(
+ backend_path, backend_name, force
+ )
if not installation:
return
+ if force:
+ self.uninstall(backend_name)
+ logger.info(
+ "Force installing %s, so delete the existing installed backend first.",
+ installation.name,
+ )
+
prompt = (
f"{installation.name} was found in {backend_path}. "
"Would you like to install it?"
@@ -232,7 +253,7 @@ class DefaultInstallationManager(InstallationManager, InstallationFiltersMixin):
self._install(installation, InstallFromPath(backend_path), prompt)
def download_and_install(
- self, backend_name: str | None = None, eula_agreement: bool = True
+ self, backend_name: str, eula_agreement: bool = True
) -> None:
"""Download and install available backends."""
installations = self.could_be_downloaded_and_installed(backend_name)
@@ -275,6 +296,14 @@ class DefaultInstallationManager(InstallationManager, InstallationFiltersMixin):
for installation in installations:
logger.info(" - %s", installation.name)
+ def uninstall(self, backend_name: str) -> None:
+ """Uninstall the backend with name backend_name."""
+ installations = self.already_installed(backend_name)
+ if not installations:
+ raise Exception("No backend available for uninstall")
+ for installation in installations:
+ installation.uninstall()
+
def _install(
self,
installation: Installation,
diff --git a/src/mlia/tools/metadata/corstone.py b/src/mlia/tools/metadata/corstone.py
index cea1ec9..04b13b5 100644
--- a/src/mlia/tools/metadata/corstone.py
+++ b/src/mlia/tools/metadata/corstone.py
@@ -19,6 +19,7 @@ from typing import Iterable
from typing import Optional
import mlia.backend.manager as backend_manager
+from mlia.backend.system import remove_system
from mlia.tools.metadata.common import DownloadAndInstall
from mlia.tools.metadata.common import Installation
from mlia.tools.metadata.common import InstallationType
@@ -205,6 +206,11 @@ class BackendInstallation(Installation):
self.install(InstallFromPath(backend_path))
+ def uninstall(self) -> None:
+ """Uninstall the backend."""
+ remove_system(self.metadata.fvp_dir_name)
+ logger.info("%s successfully uninstalled.", self.name)
+
class PackagePathChecker:
"""Package path checker."""
diff --git a/tests/test_cli_commands.py b/tests/test_cli_commands.py
index fd9e29c..f6e0843 100644
--- a/tests/test_cli_commands.py
+++ b/tests/test_cli_commands.py
@@ -10,7 +10,9 @@ from unittest.mock import MagicMock
import pytest
-from mlia.cli.commands import backend
+from mlia.cli.commands import backend_install
+from mlia.cli.commands import backend_list
+from mlia.cli.commands import backend_uninstall
from mlia.cli.commands import operators
from mlia.cli.commands import optimization
from mlia.cli.commands import performance
@@ -19,7 +21,7 @@ from mlia.devices.ethosu.config import EthosUConfiguration
from mlia.devices.ethosu.performance import MemoryUsage
from mlia.devices.ethosu.performance import NPUCycles
from mlia.devices.ethosu.performance import PerformanceMetrics
-from mlia.tools.metadata.common import InstallationManager
+from mlia.tools.metadata.common import DefaultInstallationManager
def test_operators_expected_parameters(sample_context: ExecutionContext) -> None:
@@ -139,7 +141,7 @@ def mock_performance_estimation(monkeypatch: pytest.MonkeyPatch) -> None:
@pytest.fixture(name="installation_manager_mock")
def fixture_mock_installation_manager(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
"""Mock installation manager."""
- install_manager_mock = MagicMock(spec=InstallationManager)
+ install_manager_mock = MagicMock(spec=DefaultInstallationManager)
monkeypatch.setattr(
"mlia.cli.commands.get_installation_manager",
MagicMock(return_value=install_manager_mock),
@@ -147,32 +149,47 @@ def fixture_mock_installation_manager(monkeypatch: pytest.MonkeyPatch) -> MagicM
return install_manager_mock
-def test_backend_command_action_status(installation_manager_mock: MagicMock) -> None:
- """Test backend command "status"."""
- backend(backend_action="status")
+def test_backend_command_action_list(installation_manager_mock: MagicMock) -> None:
+ """Test mlia-backend command list."""
+ backend_list()
installation_manager_mock.show_env_details.assert_called_once()
@pytest.mark.parametrize(
+ "backend_name",
+ [
+ "backend_name",
+ "BACKEND_NAME",
+ "BaCkend_NAme",
+ ],
+)
+def test_backend_command_action_uninstall(
+ installation_manager_mock: MagicMock,
+ backend_name: str,
+) -> None:
+ """Test mlia-backend command uninstall."""
+ backend_uninstall(backend_name)
+
+ installation_manager_mock.uninstall.assert_called_once()
+
+
+@pytest.mark.parametrize(
"i_agree_to_the_contained_eula, backend_name, expected_calls",
[
- [False, None, [call(None, True)]],
- [True, None, [call(None, False)]],
[False, "backend_name", [call("backend_name", True)]],
[True, "backend_name", [call("backend_name", False)]],
+ [True, "BACKEND_NAME", [call("BACKEND_NAME", False)]],
],
)
-def test_backend_command_action_add_downoad(
+def test_backend_command_action_add_download(
installation_manager_mock: MagicMock,
i_agree_to_the_contained_eula: bool,
- backend_name: str | None,
+ backend_name: str,
expected_calls: Any,
) -> None:
- """Test backend command "install" with download option."""
- backend(
- backend_action="install",
- download=True,
+ """Test mlia-backend command "install" with download option."""
+ backend_install(
name=backend_name,
i_agree_to_the_contained_eula=i_agree_to_the_contained_eula,
)
@@ -180,26 +197,20 @@ def test_backend_command_action_add_downoad(
assert installation_manager_mock.download_and_install.mock_calls == expected_calls
-@pytest.mark.parametrize("backend_name", [None, "backend_name"])
+@pytest.mark.parametrize(
+ "backend_name, force",
+ [
+ ["backend_name", False],
+ ["backend_name", True],
+ ["BACKEND_NAME", True],
+ ],
+)
def test_backend_command_action_install_from_path(
installation_manager_mock: MagicMock,
tmp_path: Path,
- backend_name: str | None,
-) -> None:
- """Test backend command "install" with backend path."""
- backend(backend_action="install", path=tmp_path, name=backend_name)
-
- installation_manager_mock.install_from(tmp_path, backend_name)
-
-
-def test_backend_command_action_install_only_one_action(
- installation_manager_mock: MagicMock, # pylint: disable=unused-argument
- tmp_path: Path,
+ backend_name: str,
+ force: bool,
) -> None:
- """Test that only one of action type allowed."""
- with pytest.raises(
- Exception,
- match="Please select only one action: download or "
- "provide path to the backend installation",
- ):
- backend(backend_action="install", download=True, path=tmp_path)
+ """Test mlia-backend command "install" with backend path."""
+ backend_install(path=tmp_path, name=backend_name, force=force)
+ installation_manager_mock.install_from.assert_called_once()
diff --git a/tests/test_cli_main.py b/tests/test_cli_main.py
index 4b16ac5..d0f7152 100644
--- a/tests/test_cli_main.py
+++ b/tests/test_cli_main.py
@@ -15,6 +15,7 @@ from unittest.mock import MagicMock
import pytest
import mlia
+from mlia.cli.main import backend_main
from mlia.cli.main import CommandInfo
from mlia.cli.main import main
from mlia.core.context import ExecutionContext
@@ -122,6 +123,17 @@ def test_default_command(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Non
non_default_command.assert_called_once_with(param="test")
+def wrap_mock_command(mock: MagicMock, command: Callable) -> Callable:
+ """Wrap the command with the mock."""
+
+ @wraps(command)
+ def mock_command(*args: Any, **kwargs: Any) -> Any:
+ """Mock the command."""
+ mock(*args, **kwargs)
+
+ return mock_command
+
+
@pytest.mark.parametrize(
"params, expected_call",
[
@@ -273,16 +285,6 @@ def test_commands_execution(
"""Test calling commands from the main function."""
mock = MagicMock()
- def wrap_mock_command(command: Callable) -> Callable:
- """Wrap the command with the mock."""
-
- @wraps(command)
- def mock_command(*args: Any, **kwargs: Any) -> Any:
- """Mock the command."""
- mock(*args, **kwargs)
-
- return mock_command
-
monkeypatch.setattr(
"mlia.cli.options.get_default_backends", MagicMock(return_value=["Vela"])
)
@@ -295,7 +297,7 @@ def test_commands_execution(
for command in ["all_tests", "operators", "performance", "optimization"]:
monkeypatch.setattr(
f"mlia.cli.main.{command}",
- wrap_mock_command(getattr(mlia.cli.main, command)),
+ wrap_mock_command(mock, getattr(mlia.cli.main, command)),
)
main(params)
@@ -304,6 +306,33 @@ def test_commands_execution(
@pytest.mark.parametrize(
+ "params, expected_call",
+ [
+ [
+ ["list"],
+ call(),
+ ],
+ ],
+)
+def test_commands_execution_backend_main(
+ monkeypatch: pytest.MonkeyPatch,
+ params: list[str],
+ expected_call: Any,
+) -> None:
+ """Test calling commands from the backend_main function."""
+ mock = MagicMock()
+
+ monkeypatch.setattr(
+ "mlia.cli.main.backend_list",
+ wrap_mock_command(mock, getattr(mlia.cli.main, "backend_list")),
+ )
+
+ backend_main(params)
+
+ mock.assert_called_once_with(*expected_call.args, **expected_call.kwargs)
+
+
+@pytest.mark.parametrize(
"verbose, exc_mock, expected_output",
[
[
diff --git a/tests/test_tools_metadata_common.py b/tests/test_tools_metadata_common.py
index 69bc3e5..fefb024 100644
--- a/tests/test_tools_metadata_common.py
+++ b/tests/test_tools_metadata_common.py
@@ -18,6 +18,30 @@ from mlia.tools.metadata.common import InstallationType
from mlia.tools.metadata.common import InstallFromPath
+def get_default_installation_manager_mock(
+ name: str,
+ already_installed: bool = False,
+) -> MagicMock:
+ """Get mock instance for DefaultInstallationManager."""
+ mock = MagicMock(spec=DefaultInstallationManager)
+
+ props = {
+ "name": name,
+ "already_installed": already_installed,
+ }
+ for prop, value in props.items():
+ setattr(type(mock), prop, PropertyMock(return_value=value))
+
+ return mock
+
+
+def _ready_for_uninstall_mock() -> MagicMock:
+ return get_default_installation_manager_mock(
+ name="already_installed",
+ already_installed=True,
+ )
+
+
def get_installation_mock(
name: str,
already_installed: bool = False,
@@ -107,14 +131,14 @@ def test_installation_manager_filtering() -> None:
could_be_downloaded_and_installed,
]
)
- assert manager.already_installed() == [already_installed]
+ assert manager.already_installed("already_installed") == [already_installed]
assert manager.ready_for_installation() == [
ready_for_installation,
could_be_downloaded_and_installed,
]
- assert manager.could_be_downloaded_and_installed() == [
- could_be_downloaded_and_installed
- ]
+ assert manager.could_be_downloaded_and_installed(
+ "could_be_downloaded_and_installed"
+ ) == [could_be_downloaded_and_installed]
assert manager.could_be_downloaded_and_installed("some_installation") == []
@@ -146,7 +170,7 @@ def test_installation_manager_download_and_install(
install_mock: MagicMock,
noninteractive: bool,
eula_agreement: bool,
- backend_name: str | None,
+ backend_name: str,
expected_call: Any,
monkeypatch: pytest.MonkeyPatch,
) -> None:
@@ -183,7 +207,7 @@ def test_installation_manager_download_and_install(
def test_installation_manager_install_from(
install_mock: MagicMock,
noninteractive: bool,
- backend_name: str | None,
+ backend_name: str,
expected_call: Any,
monkeypatch: pytest.MonkeyPatch,
) -> None:
@@ -194,3 +218,30 @@ def test_installation_manager_install_from(
manager.install_from(Path("some_path"), backend_name)
assert install_mock.install.mock_calls == expected_call
+
+
+@pytest.mark.parametrize("noninteractive", [True, False])
+@pytest.mark.parametrize(
+ "install_mock, backend_name, expected_call",
+ [
+ [
+ _ready_for_uninstall_mock(),
+ "already_installed",
+ [call()],
+ ],
+ ],
+)
+def test_installation_manager_uninstall(
+ install_mock: MagicMock,
+ noninteractive: bool,
+ backend_name: str,
+ expected_call: Any,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """Test uninstallation."""
+ install_mock.reset_mock()
+
+ manager = get_installation_manager(noninteractive, [install_mock], monkeypatch)
+ manager.uninstall(backend_name)
+
+ assert install_mock.uninstall.mock_calls == expected_call
diff --git a/tests/test_tools_metadata_corstone.py b/tests/test_tools_metadata_corstone.py
index 02c04d4..a7d81f2 100644
--- a/tests/test_tools_metadata_corstone.py
+++ b/tests/test_tools_metadata_corstone.py
@@ -469,3 +469,20 @@ def test_corstone_vht_install(
corstone_installation.install(InstallFromPath(Path("/opt/VHT")))
create_destination_and_install_mock.assert_called_once()
+
+
+def test_corstone_uninstall(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """Test the uninstall function in Corstone."""
+ remove_system_mock = MagicMock()
+
+ monkeypatch.setattr(
+ "mlia.tools.metadata.corstone.remove_system",
+ remove_system_mock,
+ )
+
+ installation = get_corstone_300_installation()
+
+ installation.uninstall()
+ remove_system_mock.assert_called_once_with("corstone_300")