From 302ce432829ae7c25e100a5cca718f0aadbe4fd4 Mon Sep 17 00:00:00 2001 From: Dmitrii Agibov Date: Tue, 15 Nov 2022 13:19:53 +0000 Subject: MLIA-649 Support tosa-checker as a backend - Add new type of the backend based on python packages - Add installation class for TOSA checker - Update documentation - Extend support of the parameter "force" in the "install" command Change-Id: I95567b75e1cfe85daa1f1c3d359975bb67b2504e --- tests/test_cli_commands.py | 10 +++-- tests/test_mlia_utils_py_manager.py | 73 +++++++++++++++++++++++++++++++++ tests/test_tools_metadata_common.py | 59 ++++++++++++++++++++------ tests/test_tools_metadata_py_package.py | 62 ++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 tests/test_mlia_utils_py_manager.py create mode 100644 tests/test_tools_metadata_py_package.py (limited to 'tests') diff --git a/tests/test_cli_commands.py b/tests/test_cli_commands.py index f6e0843..3a01f78 100644 --- a/tests/test_cli_commands.py +++ b/tests/test_cli_commands.py @@ -175,16 +175,17 @@ def test_backend_command_action_uninstall( @pytest.mark.parametrize( - "i_agree_to_the_contained_eula, backend_name, expected_calls", + "i_agree_to_the_contained_eula, force, backend_name, expected_calls", [ - [False, "backend_name", [call("backend_name", True)]], - [True, "backend_name", [call("backend_name", False)]], - [True, "BACKEND_NAME", [call("BACKEND_NAME", False)]], + [False, False, "backend_name", [call("backend_name", True, False)]], + [True, False, "backend_name", [call("backend_name", False, False)]], + [True, True, "BACKEND_NAME", [call("BACKEND_NAME", False, True)]], ], ) def test_backend_command_action_add_download( installation_manager_mock: MagicMock, i_agree_to_the_contained_eula: bool, + force: bool, backend_name: str, expected_calls: Any, ) -> None: @@ -192,6 +193,7 @@ def test_backend_command_action_add_download( backend_install( name=backend_name, i_agree_to_the_contained_eula=i_agree_to_the_contained_eula, + force=force, ) assert installation_manager_mock.download_and_install.mock_calls == expected_calls diff --git a/tests/test_mlia_utils_py_manager.py b/tests/test_mlia_utils_py_manager.py new file mode 100644 index 0000000..e41680d --- /dev/null +++ b/tests/test_mlia_utils_py_manager.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: Copyright 2022, Arm Limited and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 +"""Tests for python package manager.""" +import sys +from unittest.mock import MagicMock + +import pytest + +from mlia.utils.py_manager import get_package_manager +from mlia.utils.py_manager import PyPackageManager + + +def test_get_package_manager() -> None: + """Test function get_package_manager.""" + manager = get_package_manager() + assert isinstance(manager, PyPackageManager) + + +@pytest.fixture(name="mock_check_call") +def mock_check_call_fixture(monkeypatch: pytest.MonkeyPatch) -> MagicMock: + """Mock check_call function.""" + mock_check_call = MagicMock() + monkeypatch.setattr("mlia.utils.py_manager.check_call", mock_check_call) + + return mock_check_call + + +def test_py_package_manager_metadata() -> None: + """Test getting package status.""" + manager = PyPackageManager() + assert manager.package_installed("pytest") + assert manager.packages_installed(["pytest", "mlia"]) + + +def test_py_package_manager_install(mock_check_call: MagicMock) -> None: + """Test package installation.""" + manager = PyPackageManager() + with pytest.raises(ValueError, match="No package names provided"): + manager.install([]) + + manager.install(["mlia", "pytest"]) + mock_check_call.assert_called_once_with( + [ + sys.executable, + "-m", + "pip", + "--disable-pip-version-check", + "install", + "mlia", + "pytest", + ] + ) + + +def test_py_package_manager_uninstall(mock_check_call: MagicMock) -> None: + """Test package removal.""" + manager = PyPackageManager() + with pytest.raises(ValueError, match="No package names provided"): + manager.uninstall([]) + + manager.uninstall(["mlia", "pytest"]) + mock_check_call.assert_called_once_with( + [ + sys.executable, + "-m", + "pip", + "--disable-pip-version-check", + "uninstall", + "--yes", + "mlia", + "pytest", + ] + ) diff --git a/tests/test_tools_metadata_common.py b/tests/test_tools_metadata_common.py index fefb024..9811852 100644 --- a/tests/test_tools_metadata_common.py +++ b/tests/test_tools_metadata_common.py @@ -46,7 +46,7 @@ def get_installation_mock( name: str, already_installed: bool = False, could_be_installed: bool = False, - supported_install_type: type | None = None, + supported_install_type: type | tuple | None = None, ) -> MagicMock: """Get mock instance for the installation.""" mock = MagicMock(spec=Installation) @@ -74,6 +74,7 @@ def _already_installed_mock() -> MagicMock: return get_installation_mock( name="already_installed", already_installed=True, + supported_install_type=(DownloadAndInstall, InstallFromPath), ) @@ -136,32 +137,38 @@ def test_installation_manager_filtering() -> None: ready_for_installation, 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") == [] @pytest.mark.parametrize("noninteractive", [True, False]) @pytest.mark.parametrize( - "install_mock, eula_agreement, backend_name, expected_call", + "install_mock, eula_agreement, backend_name, force, expected_call", [ [ _could_be_downloaded_and_installed_mock(), True, - None, + "could_be_downloaded_and_installed", + False, [call(DownloadAndInstall(eula_agreement=True))], ], [ _could_be_downloaded_and_installed_mock(), False, - None, + "could_be_downloaded_and_installed", + True, + [call(DownloadAndInstall(eula_agreement=False))], + ], + [ + _already_installed_mock(), + False, + "already_installed", + True, [call(DownloadAndInstall(eula_agreement=False))], ], [ _could_be_downloaded_and_installed_mock(), False, "unknown", + True, [], ], ], @@ -171,6 +178,7 @@ def test_installation_manager_download_and_install( noninteractive: bool, eula_agreement: bool, backend_name: str, + force: bool, expected_call: Any, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -179,35 +187,58 @@ def test_installation_manager_download_and_install( manager = get_installation_manager(noninteractive, [install_mock], monkeypatch) - manager.download_and_install(backend_name, eula_agreement=eula_agreement) + manager.download_and_install( + backend_name, eula_agreement=eula_agreement, force=force + ) + assert install_mock.install.mock_calls == expected_call + if force and install_mock.already_installed: + install_mock.uninstall.assert_called_once() + else: + install_mock.uninstall.assert_not_called() @pytest.mark.parametrize("noninteractive", [True, False]) @pytest.mark.parametrize( - "install_mock, backend_name, expected_call", + "install_mock, backend_name, force, expected_call", [ [ _could_be_installed_from_mock(), - None, + "could_be_installed_from", + False, [call(InstallFromPath(Path("some_path")))], ], [ _could_be_installed_from_mock(), "unknown", + False, + [], + ], + [ + _could_be_installed_from_mock(), + "unknown", + True, [], ], [ _already_installed_mock(), "already_installed", + False, [], ], + [ + _already_installed_mock(), + "already_installed", + True, + [call(InstallFromPath(Path("some_path")))], + ], ], ) def test_installation_manager_install_from( install_mock: MagicMock, noninteractive: bool, backend_name: str, + force: bool, expected_call: Any, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -215,9 +246,13 @@ def test_installation_manager_install_from( install_mock.reset_mock() manager = get_installation_manager(noninteractive, [install_mock], monkeypatch) - manager.install_from(Path("some_path"), backend_name) + manager.install_from(Path("some_path"), backend_name, force=force) assert install_mock.install.mock_calls == expected_call + if force and install_mock.already_installed: + install_mock.uninstall.assert_called_once() + else: + install_mock.uninstall.assert_not_called() @pytest.mark.parametrize("noninteractive", [True, False]) diff --git a/tests/test_tools_metadata_py_package.py b/tests/test_tools_metadata_py_package.py new file mode 100644 index 0000000..8b93e33 --- /dev/null +++ b/tests/test_tools_metadata_py_package.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: Copyright 2022, Arm Limited and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 +"""Tests for python package based installations.""" +from pathlib import Path +from unittest.mock import MagicMock + +import pytest + +from mlia.tools.metadata.common import DownloadAndInstall +from mlia.tools.metadata.common import InstallFromPath +from mlia.tools.metadata.py_package import get_pypackage_backend_installations +from mlia.tools.metadata.py_package import get_tosa_backend_installation +from mlia.tools.metadata.py_package import PyPackageBackendInstallation + + +def test_get_pypackage_backends() -> None: + """Test function get_pypackage_backends.""" + backend_installs = get_pypackage_backend_installations() + + assert isinstance(backend_installs, list) + assert len(backend_installs) == 1 + + tosa_installation = backend_installs[0] + assert isinstance(tosa_installation, PyPackageBackendInstallation) + + +def test_get_tosa_backend_installation( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """Test function get_tosa_backend_installation.""" + mock_package_manager = MagicMock() + monkeypatch.setattr( + "mlia.tools.metadata.py_package.get_package_manager", + lambda: mock_package_manager, + ) + + tosa_installation = get_tosa_backend_installation() + + assert isinstance(tosa_installation, PyPackageBackendInstallation) + assert tosa_installation.name == "tosa-checker" + assert ( + tosa_installation.description + == "Tool to check if a ML model is compatible with the TOSA specification" + ) + assert tosa_installation.could_be_installed + assert tosa_installation.supports(DownloadAndInstall()) + assert not tosa_installation.supports(InstallFromPath(tmp_path)) + + mock_package_manager.packages_installed.return_value = True + assert tosa_installation.already_installed + mock_package_manager.packages_installed.assert_called_once_with(["tosa-checker"]) + + with pytest.raises(Exception, match=r"Unsupported installation type.*"): + tosa_installation.install(InstallFromPath(tmp_path)) + + mock_package_manager.install.assert_not_called() + + tosa_installation.install(DownloadAndInstall()) + mock_package_manager.install.assert_called_once_with(["mlia[tosa]"]) + + tosa_installation.uninstall() + mock_package_manager.uninstall.assert_called_once_with(["tosa-checker"]) -- cgit v1.2.1