From 37959522a805a5e23c930ed79aac84920c3cb208 Mon Sep 17 00:00:00 2001 From: Dmitrii Agibov Date: Fri, 18 Nov 2022 16:34:03 +0000 Subject: Move backends functionality into separate modules - Move backend management/executor code into module backend_core - Create separate module for each backend in "backend" module - Move each backend into corresponding module - Split Vela wrapper into several submodules Change-Id: If01b6774aab6501951212541cc5d7f5aa7c97e95 --- tests/test_backend_corstone_install.py | 490 +++++++++++++++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 tests/test_backend_corstone_install.py (limited to 'tests/test_backend_corstone_install.py') diff --git a/tests/test_backend_corstone_install.py b/tests/test_backend_corstone_install.py new file mode 100644 index 0000000..3b05a49 --- /dev/null +++ b/tests/test_backend_corstone_install.py @@ -0,0 +1,490 @@ +# SPDX-FileCopyrightText: Copyright 2022, Arm Limited and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 +"""Tests for Corstone related installation functions..""" +from __future__ import annotations + +import tarfile +from pathlib import Path +from typing import Iterable +from unittest.mock import MagicMock + +import pytest + +from mlia.backend.corstone.install import Corstone300Installer +from mlia.backend.corstone.install import get_corstone_300_installation +from mlia.backend.corstone.install import get_corstone_310_installation +from mlia.backend.corstone.install import get_corstone_installations +from mlia.backend.corstone.install import PackagePathChecker +from mlia.backend.corstone.install import StaticPathChecker +from mlia.backend.executor.runner import BackendRunner +from mlia.backend.install import BackendInfo +from mlia.backend.install import BackendInstallation +from mlia.backend.install import BackendInstaller +from mlia.backend.install import BackendMetadata +from mlia.backend.install import CompoundPathChecker +from mlia.backend.install import DownloadAndInstall +from mlia.backend.install import InstallFromPath +from mlia.backend.install import PathChecker + + +@pytest.fixture(name="test_mlia_resources") +def fixture_test_mlia_resources( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> Path: + """Redirect MLIA resources resolution to the temp directory.""" + mlia_resources = tmp_path / "resources" + mlia_resources.mkdir() + + monkeypatch.setattr( + "mlia.backend.install.get_mlia_resources", + MagicMock(return_value=mlia_resources), + ) + + return mlia_resources + + +def get_backend_installation( # pylint: disable=too-many-arguments + backend_runner_mock: MagicMock = MagicMock(), + name: str = "test_name", + description: str = "test_description", + download_artifact: MagicMock | None = None, + path_checker: PathChecker = MagicMock(), + apps_resources: list[str] | None = None, + system_config: str | None = None, + backend_installer: BackendInstaller = MagicMock(), + supported_platforms: list[str] | None = None, +) -> BackendInstallation: + """Get backend installation.""" + return BackendInstallation( + backend_runner=backend_runner_mock, + metadata=BackendMetadata( + name=name, + description=description, + system_config=system_config or "", + apps_resources=apps_resources or [], + fvp_dir_name="sample_dir", + download_artifact=download_artifact, + supported_platforms=supported_platforms, + ), + path_checker=path_checker, + backend_installer=backend_installer, + ) + + +@pytest.mark.parametrize( + "platform, supported_platforms, expected_result", + [ + ["Linux", ["Linux"], True], + ["Linux", [], True], + ["Linux", None, True], + ["Windows", ["Linux"], False], + ], +) +def test_could_be_installed_depends_on_platform( + platform: str, + supported_platforms: list[str] | None, + expected_result: bool, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test that installation could not be installed on unsupported platform.""" + monkeypatch.setattr( + "mlia.backend.install.platform.system", + MagicMock(return_value=platform), + ) + monkeypatch.setattr( + "mlia.backend.install.all_paths_valid", + MagicMock(return_value=True), + ) + backend_runner_mock = MagicMock(spec=BackendRunner) + + installation = get_backend_installation( + backend_runner_mock, + supported_platforms=supported_platforms, + ) + assert installation.could_be_installed == expected_result + + +def test_get_corstone_installations() -> None: + """Test function get_corstone_installation.""" + installs = get_corstone_installations() + assert len(installs) == 2 + assert all(isinstance(install, BackendInstallation) for install in installs) + + +def test_backend_installation_metadata_resolving() -> None: + """Test backend installation metadata resolving.""" + backend_runner_mock = MagicMock(spec=BackendRunner) + installation = get_backend_installation(backend_runner_mock) + + assert installation.name == "test_name" + assert installation.description == "test_description" + + backend_runner_mock.all_installed.return_value = False + assert installation.already_installed is False + + assert installation.could_be_installed is True + + +def test_backend_installation_supported_install_types(tmp_path: Path) -> None: + """Test supported installation types.""" + installation_no_download_artifact = get_backend_installation() + assert installation_no_download_artifact.supports(DownloadAndInstall()) is False + + installation_with_download_artifact = get_backend_installation( + download_artifact=MagicMock() + ) + assert installation_with_download_artifact.supports(DownloadAndInstall()) is True + + path_checker_mock = MagicMock(return_value=BackendInfo(tmp_path)) + installation_can_install_from_dir = get_backend_installation( + path_checker=path_checker_mock + ) + assert installation_can_install_from_dir.supports(InstallFromPath(tmp_path)) is True + + any_installation = get_backend_installation() + assert any_installation.supports("unknown_install_type") is False # type: ignore + + +def test_backend_installation_install_wrong_type() -> None: + """Test that operation should fail if wrong install type provided.""" + with pytest.raises(Exception, match="Unable to install wrong_install_type"): + backend_runner_mock = MagicMock(spec=BackendRunner) + installation = get_backend_installation(backend_runner_mock) + + installation.install("wrong_install_type") # type: ignore + + +def test_backend_installation_install_from_path( + tmp_path: Path, test_mlia_resources: Path +) -> None: + """Test installation from the path.""" + system_config = test_mlia_resources / "example_config.json" + system_config.touch() + + sample_app = test_mlia_resources / "sample_app" + sample_app.mkdir() + + dist_dir = tmp_path / "dist" + dist_dir.mkdir() + + path_checker_mock = MagicMock(return_value=BackendInfo(dist_dir)) + + backend_runner_mock = MagicMock(spec=BackendRunner) + installation = get_backend_installation( + backend_runner_mock=backend_runner_mock, + path_checker=path_checker_mock, + apps_resources=[sample_app.name], + system_config="example_config.json", + ) + + assert installation.supports(InstallFromPath(dist_dir)) is True + installation.install(InstallFromPath(dist_dir)) + + backend_runner_mock.install_system.assert_called_once() + backend_runner_mock.install_application.assert_called_once_with(sample_app) + + +@pytest.mark.parametrize("copy_source", [True, False]) +def test_backend_installation_install_from_static_path( + tmp_path: Path, test_mlia_resources: Path, copy_source: bool +) -> None: + """Test installation from the predefined path.""" + system_config = test_mlia_resources / "example_config.json" + system_config.touch() + + custom_system_config = test_mlia_resources / "custom_config.json" + custom_system_config.touch() + + sample_app = test_mlia_resources / "sample_app" + sample_app.mkdir() + + predefined_location = tmp_path / "backend" + predefined_location.mkdir() + + predefined_location_file = predefined_location / "file.txt" + predefined_location_file.touch() + + predefined_location_dir = predefined_location / "folder" + predefined_location_dir.mkdir() + nested_file = predefined_location_dir / "nested_file.txt" + nested_file.touch() + + backend_runner_mock = MagicMock(spec=BackendRunner) + + def check_install_dir(install_dir: Path) -> None: + """Check content of the install dir.""" + assert install_dir.is_dir() + files = list(install_dir.iterdir()) + + if copy_source: + assert len(files) == 3 + assert all(install_dir / item in files for item in ["file.txt", "folder"]) + assert (install_dir / "folder/nested_file.txt").is_file() + else: + assert len(files) == 1 + + assert install_dir / "custom_config.json" in files + + backend_runner_mock.install_system.side_effect = check_install_dir + + installation = get_backend_installation( + backend_runner_mock=backend_runner_mock, + path_checker=StaticPathChecker( + predefined_location, + ["file.txt"], + copy_source=copy_source, + system_config=str(custom_system_config), + ), + apps_resources=[sample_app.name], + system_config="example_config.json", + ) + + assert installation.supports(InstallFromPath(predefined_location)) is True + installation.install(InstallFromPath(predefined_location)) + + backend_runner_mock.install_system.assert_called_once() + backend_runner_mock.install_application.assert_called_once_with(sample_app) + + +def create_sample_fvp_archive(tmp_path: Path) -> Path: + """Create sample FVP tar archive.""" + fvp_archive_dir = tmp_path / "archive" + fvp_archive_dir.mkdir() + + sample_file = fvp_archive_dir / "sample.txt" + sample_file.write_text("Sample file") + + sample_dir = fvp_archive_dir / "sample_dir" + sample_dir.mkdir() + + fvp_archive = tmp_path / "archive.tgz" + with tarfile.open(fvp_archive, "w:gz") as fvp_archive_tar: + fvp_archive_tar.add(fvp_archive_dir, arcname=fvp_archive_dir.name) + + return fvp_archive + + +def test_backend_installation_download_and_install( + test_mlia_resources: Path, tmp_path: Path +) -> None: + """Test downloading and installation process.""" + fvp_archive = create_sample_fvp_archive(tmp_path) + + system_config = test_mlia_resources / "example_config.json" + system_config.touch() + + download_artifact_mock = MagicMock() + download_artifact_mock.download_to.return_value = fvp_archive + + path_checker = PackagePathChecker(["archive/sample.txt"], "archive/sample_dir") + + def installer(_eula_agreement: bool, dist_dir: Path) -> Path: + """Sample installer.""" + return dist_dir + + backend_runner_mock = MagicMock(spec=BackendRunner) + installation = get_backend_installation( + backend_runner_mock, + download_artifact=download_artifact_mock, + backend_installer=installer, + path_checker=path_checker, + system_config="example_config.json", + ) + + installation.install(DownloadAndInstall()) + + backend_runner_mock.install_system.assert_called_once() + + +@pytest.mark.parametrize( + "dir_content, expected_result", + [ + [ + ["models/", "file1.txt", "file2.txt"], + "models", + ], + [ + ["file1.txt", "file2.txt"], + None, + ], + [ + ["models/", "file2.txt"], + None, + ], + ], +) +def test_corstone_path_checker_valid_path( + tmp_path: Path, dir_content: list[str], expected_result: str | None +) -> None: + """Test Corstone path checker valid scenario.""" + path_checker = PackagePathChecker(["file1.txt", "file2.txt"], "models") + + for item in dir_content: + if item.endswith("/"): + item_dir = tmp_path / item + item_dir.mkdir() + else: + item_file = tmp_path / item + item_file.touch() + + result = path_checker(tmp_path) + expected = ( + None if expected_result is None else BackendInfo(tmp_path / expected_result) + ) + + assert result == expected + + +@pytest.mark.parametrize("system_config", [None, "system_config"]) +@pytest.mark.parametrize("copy_source", [True, False]) +def test_static_path_checker( + tmp_path: Path, copy_source: bool, system_config: str | None +) -> None: + """Test static path checker.""" + static_checker = StaticPathChecker( + tmp_path, [], copy_source=copy_source, system_config=system_config + ) + assert static_checker(tmp_path) == BackendInfo( + tmp_path, copy_source=copy_source, system_config=system_config + ) + + +def test_static_path_checker_not_valid_path(tmp_path: Path) -> None: + """Test static path checker should return None if path is not valid.""" + static_checker = StaticPathChecker(tmp_path, ["file.txt"]) + assert static_checker(tmp_path / "backend") is None + + +def test_static_path_checker_not_valid_structure(tmp_path: Path) -> None: + """Test static path checker should return None if files are missing.""" + static_checker = StaticPathChecker(tmp_path, ["file.txt"]) + assert static_checker(tmp_path) is None + + missing_file = tmp_path / "file.txt" + missing_file.touch() + + assert static_checker(tmp_path) == BackendInfo(tmp_path, copy_source=False) + + +def test_compound_path_checker(tmp_path: Path) -> None: + """Test compound path checker.""" + path_checker_path_valid_path = MagicMock(return_value=BackendInfo(tmp_path)) + path_checker_path_not_valid_path = MagicMock(return_value=None) + + checker = CompoundPathChecker( + path_checker_path_valid_path, path_checker_path_not_valid_path + ) + assert checker(tmp_path) == BackendInfo(tmp_path) + + checker = CompoundPathChecker(path_checker_path_not_valid_path) + assert checker(tmp_path) is None + + +@pytest.mark.parametrize( + "eula_agreement, expected_command", + [ + [ + True, + [ + "./FVP_Corstone_SSE-300.sh", + "-q", + "-d", + "corstone-300", + ], + ], + [ + False, + [ + "./FVP_Corstone_SSE-300.sh", + "-q", + "-d", + "corstone-300", + "--nointeractive", + "--i-agree-to-the-contained-eula", + ], + ], + ], +) +def test_corstone_300_installer( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + eula_agreement: bool, + expected_command: list[str], +) -> None: + """Test Corstone-300 installer.""" + command_mock = MagicMock() + + monkeypatch.setattr( + "mlia.backend.corstone.install.subprocess.check_call", command_mock + ) + installer = Corstone300Installer() + result = installer(eula_agreement, tmp_path) + + command_mock.assert_called_once_with(expected_command) + assert result == tmp_path / "corstone-300" + + +@pytest.mark.parametrize( + "corstone_installation, expected_paths", + [ + [ + get_corstone_300_installation(), + { + "/opt/VHT/VHT_Corstone_SSE-300_Ethos-U55", + "/opt/VHT/VHT_Corstone_SSE-300_Ethos-U65", + }, + ], + [ + get_corstone_310_installation(), + { + "/opt/VHT/VHT_Corstone_SSE-310", + "/opt/VHT/VHT_Corstone_SSE-310_Ethos-U65", + }, + ], + ], +) +def test_corstone_vht_install( + corstone_installation: BackendInstallation, + expected_paths: set, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test if Corstone 300/310 could be installed from /opt/VHT.""" + + def _all_files_exist(paths: Iterable[Path]) -> bool: + """Check if all files exist.""" + pathset = {item.as_posix() for item in paths} + return pathset == expected_paths + + create_destination_and_install_mock = MagicMock() + + monkeypatch.setattr("mlia.backend.install.all_files_exist", _all_files_exist) + + monkeypatch.setattr( + "mlia.backend.executor.system.get_available_systems", lambda: [] + ) + + monkeypatch.setattr( + "mlia.backend.executor.system.create_destination_and_install", + create_destination_and_install_mock, + ) + + 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.backend.install.remove_system", + remove_system_mock, + ) + + installation = get_corstone_300_installation() + + installation.uninstall() + remove_system_mock.assert_called_once_with("corstone_300") -- cgit v1.2.1