From 5bee8f1f734a0f9547301de93e43357ccb09c956 Mon Sep 17 00:00:00 2001 From: adrianciu <adrian.ciu@codemart.ro> Date: Thu, 27 Feb 2025 13:26:45 +0200 Subject: [PATCH] dedal: CLI; tests --- dedal/bll/SpackManager.py | 35 ++++ dedal/bll/cli_utils.py | 23 +++ dedal/cli/SpackManager.py | 0 dedal/cli/spack_manager_api.py | 153 +++++++++++++++ dedal/model/SpackDescriptor.py | 6 +- dedal/spack_factory/SpackOperation.py | 53 ++--- .../spack_from_scratch_test.py | 10 +- .../spack_operation_creator_test.py | 50 +++++ .../unit_tests/spack_manager_api_test.py | 183 ++++++++++++++++++ 9 files changed, 480 insertions(+), 33 deletions(-) create mode 100644 dedal/bll/SpackManager.py create mode 100644 dedal/bll/cli_utils.py delete mode 100644 dedal/cli/SpackManager.py create mode 100644 dedal/cli/spack_manager_api.py create mode 100644 dedal/tests/integration_tests/spack_operation_creator_test.py create mode 100644 dedal/tests/unit_tests/spack_manager_api_test.py diff --git a/dedal/bll/SpackManager.py b/dedal/bll/SpackManager.py new file mode 100644 index 00000000..e5fae221 --- /dev/null +++ b/dedal/bll/SpackManager.py @@ -0,0 +1,35 @@ +import os +from dedal.model.SpackDescriptor import SpackDescriptor +from dedal.spack_factory.SpackOperationCreator import SpackOperationCreator +from dedal.configuration.SpackConfig import SpackConfig + + +class SpackManager: + """ + This class defines the logic used by the CLI + """ + + def __init__(self, spack_config: SpackConfig = None, use_cache=False): + self._spack_config = spack_config + self._use_cache = use_cache + + def _get_spack_operation(self): + return SpackOperationCreator.get_spack_operator(self._spack_config, self._use_cache) + + def install_spack(self, version: str, bashrc_path=os.path.expanduser("~/.bashrc")): + self._get_spack_operation().install_spack(spack_version=f'v{version}', bashrc_path=bashrc_path) + + def add_spack_repo(self, repo: SpackDescriptor): + """ + After additional repo was added, setup_spack_env must be invoked + """ + self._spack_config.add_repo(repo) + + def setup_spack_env(self): + self._get_spack_operation().setup_spack_env() + + def concretize_spack_env(self): + self._get_spack_operation().concretize_spack_env() + + def install_packages(self, jobs: int): + self._get_spack_operation().install_packages(jobs=jobs) diff --git a/dedal/bll/cli_utils.py b/dedal/bll/cli_utils.py new file mode 100644 index 00000000..bfc74ed0 --- /dev/null +++ b/dedal/bll/cli_utils.py @@ -0,0 +1,23 @@ +import jsonpickle +import os + + +def save_config(spack_config_data, config_path: str): + """Save config to JSON file.""" + with open(config_path, "w") as data_file: + data_file.write(jsonpickle.encode(spack_config_data)) + + +def load_config(config_path: str): + """Load config from JSON file.""" + if os.path.exists(config_path): + with open(config_path, "r") as data_file: + data = jsonpickle.decode(data_file.read()) + return data + return {} + + +def clear_config(config_path: str): + """Delete the JSON config file.""" + if os.path.exists(config_path): + os.remove(config_path) diff --git a/dedal/cli/SpackManager.py b/dedal/cli/SpackManager.py deleted file mode 100644 index e69de29b..00000000 diff --git a/dedal/cli/spack_manager_api.py b/dedal/cli/spack_manager_api.py new file mode 100644 index 00000000..78918849 --- /dev/null +++ b/dedal/cli/spack_manager_api.py @@ -0,0 +1,153 @@ +import os +from pathlib import Path +import click +import jsonpickle + +from dedal.bll.SpackManager import SpackManager +from dedal.bll.cli_utils import save_config, load_config +from dedal.configuration.GpgConfig import GpgConfig +from dedal.configuration.SpackConfig import SpackConfig +from dedal.model.SpackDescriptor import SpackDescriptor +from dedal.utils.utils import resolve_path + +SESSION_CONFIG_PATH = os.path.expanduser(f'~/tmp/dedal/dedal_session.json') +os.makedirs(os.path.dirname(SESSION_CONFIG_PATH), exist_ok=True) + + +@click.group() +@click.pass_context +def cli(ctx: click.Context): + config = load_config(SESSION_CONFIG_PATH) + if ctx.invoked_subcommand not in ['set-config', 'install-spack'] and not config: + click.echo('No configuration set. Use `set-config` first.') + ctx.exit(1) + if config: + config['env_path'] = resolve_path(config['env_path']) + env = SpackDescriptor(config['env_name'], config['env_path'], config['env_git_path']) + gpg = GpgConfig(config['gpg_name'], config['gpg_mail']) if config['gpg_name'] and config['gpg_mail'] else None + spack_config = SpackConfig(env=env, repos=None, install_dir=config['install_dir'], + upstream_instance=config['upstream_instance'], + concretization_dir=config['concretization_dir'], + buildcache_dir=config['buildcache_dir'], + system_name=config['system_name'], gpg=gpg, + use_spack_global=config['use_spack_global']) + ctx.obj = SpackManager(spack_config, use_cache=config['use_cache']) + + +@cli.command() +@click.option('--use_cache', is_flag=True, default=False, help='Enables cashing') +@click.option('--use_spack_global', is_flag=True, default=False, help='Uses spack installed globally on the os') +@click.option('--env_name', type=str, default=None, help='Environment name') +@click.option('--env_path', type=str, default=None, help='Environment path to download locally') +@click.option('--env_git_path', type=str, default=None, help='Git path to download the environment') +@click.option('--install_dir', type=str, + help='Install directory for installing spack; spack environments and repositories are stored here') +@click.option('--upstream_instance', type=str, default=None, help='Upstream instance for spack environment') +@click.option('--system_name', type=str, default=None, help='System name; it is used inside the spack environment') +@click.option('--concretization_dir', type=str, default=None, + help='Directory where the concretization caching (spack.lock) will be downloaded') +@click.option('--buildcache_dir', type=str, default=None, + help='Directory where the binary caching is downloaded for the spack packages') +@click.option('--gpg_name', type=str, default=None, help='Gpg name') +@click.option('--gpg_mail', type=str, default=None, help='Gpg mail contact address') +@click.option('--cache_version_concretize', type=str, default='v1', help='Cache version for concretizaion data') +@click.option('--cache_version_build', type=str, default='v1', help='Cache version for binary caches data') +def set_config(use_cache, env_name, env_path, env_git_path, install_dir, upstream_instance, system_name, + concretization_dir, + buildcache_dir, gpg_name, gpg_mail, use_spack_global, cache_version_concretize, cache_version_build): + """Set configuration parameters for tahe session.""" + spack_config_data = { + 'use_cache': use_cache, + 'env_name': env_name, + 'env_path': env_path, + 'env_git_path': env_git_path, + 'install_dir': install_dir, + 'upstream_instance': upstream_instance, + 'system_name': system_name, + 'concretization_dir': Path(concretization_dir) if concretization_dir else None, + 'buildcache_dir': Path(buildcache_dir) if buildcache_dir else None, + 'gpg_name': gpg_name, + 'gpg_mail': gpg_mail, + 'use_spack_global': use_spack_global, + 'repos': [], + 'cache_version_concretize': cache_version_concretize, + 'cache_version_build': cache_version_build, + } + save_config(spack_config_data, SESSION_CONFIG_PATH) + click.echo('Configuration saved.') + + +@click.command() +def show_config(): + """Show the current configuration.""" + config = load_config(SESSION_CONFIG_PATH) + if config: + click.echo(jsonpickle.encode(config, indent=2)) + else: + click.echo('No configuration set. Use `set-config` first.') + + +@cli.command() +@click.option('--spack_version', type=str, default='0.23.0', help='Spack version') +@click.option('--bashrc_path', type=str, default="~/.bashrc", help='Path to .bashrc') +@click.pass_context +def install_spack(ctx: click.Context, spack_version: str, bashrc_path: str): + """Install spack in the install_dir folder""" + bashrc_path = os.path.expanduser(bashrc_path) + if ctx.obj is None: + SpackManager().install_spack(spack_version, bashrc_path) + else: + ctx.obj.install_spack(spack_version, bashrc_path) + + +@cli.command() +@click.option('--repo_name', type=str, required=True, default=None, help='Repository name') +@click.option('--path', type=str, required=True, default=None, help='Repository path to download locally') +@click.option('--git_path', type=str, required=True, default=None, help='Git path to download the repository') +def add_spack_repo(repo_name: str, path: str, git_path: str = None): + """Adds a spack repository to the spack environments. The setup command must be rerun.""" + path = resolve_path(path) + repo = SpackDescriptor(repo_name, path, git_path) + config = load_config(SESSION_CONFIG_PATH) + config['repos'].append(repo) + save_config(config, SESSION_CONFIG_PATH) + click.echo('dedal setup_spack_env must be reran after each repo is added for the environment.') + + +@cli.command() +@click.pass_context +def setup_spack_env(ctx: click.Context): + """Setups a spack environment according to the given configuration.""" + ctx.obj.setup_spack_env() + + +@cli.command() +@click.pass_context +def concretize(ctx: click.Context): + """Spack concretization step""" + ctx.obj.concretize_spack_env() + + +@cli.command() +@click.option('--jobs', type=int, default=2, help='Number of parallel jobs for spack installation') +@click.pass_context +def install_packages(ctx: click.Context, jobs): + """Installs spack packages present in the spack environment defined in configuration""" + ctx.obj.install_packages(jobs=jobs) + + +@click.command() +def clear_config(): + """Clear stored configuration""" + if os.path.exists(SESSION_CONFIG_PATH): + os.remove(SESSION_CONFIG_PATH) + click.echo('Configuration cleared!') + else: + click.echo('No configuration to clear.') + + +cli.add_command(show_config) +cli.add_command(clear_config) + +if __name__ == '__main__': + cli() diff --git a/dedal/model/SpackDescriptor.py b/dedal/model/SpackDescriptor.py index 421c4824..939164a0 100644 --- a/dedal/model/SpackDescriptor.py +++ b/dedal/model/SpackDescriptor.py @@ -7,7 +7,7 @@ class SpackDescriptor: Provides details about the spack environment """ - def __init__(self, env_name: str, path: Path = Path(os.getcwd()).resolve(), git_path: str = None): - self.env_name = env_name - self.path = path if isinstance(path,Path) else Path(path) + def __init__(self, name: str, path: Path = Path(os.getcwd()).resolve(), git_path: str = None): + self.name = name + self.path = path.resolve() if isinstance(path, Path) else Path(path).resolve() self.git_path = git_path diff --git a/dedal/spack_factory/SpackOperation.py b/dedal/spack_factory/SpackOperation.py index 58dcad8b..aebabc0b 100644 --- a/dedal/spack_factory/SpackOperation.py +++ b/dedal/spack_factory/SpackOperation.py @@ -39,24 +39,27 @@ class SpackOperation: self.spack_config.buildcache_dir = spack_config.buildcache_dir if self.spack_config.buildcache_dir: os.makedirs(self.spack_config.buildcache_dir, exist_ok=True) + if self.spack_config.env and spack_config.env.name: + self.env_path: Path = spack_config.env.path / spack_config.env.name + self.spack_command_on_env = f'{self.spack_setup_script} spack env activate -p {self.env_path}' + else: + self.spack_command_on_env = self.spack_setup_script if self.spack_config.env and spack_config.env.path: self.spack_config.env.path = spack_config.env.path self.spack_config.env.path.mkdir(parents=True, exist_ok=True) - self.env_path: Path = spack_config.env.path / spack_config.env.env_name - self.spack_command_on_env = f'{self.spack_setup_script} spack env activate -p {self.env_path}' def create_fetch_spack_environment(self): if self.spack_config.env.git_path: - git_clone_repo(self.spack_config.env.env_name, self.spack_config.env.path / self.spack_config.env.env_name, + git_clone_repo(self.spack_config.env.name, self.spack_config.env.path / self.spack_config.env.name, self.spack_config.env.git_path, logger=self.logger) else: - os.makedirs(self.spack_config.env.path / self.spack_config.env.env_name, exist_ok=True) + os.makedirs(self.spack_config.env.path / self.spack_config.env.name, exist_ok=True) run_command("bash", "-c", f'{self.spack_setup_script} spack env create -d {self.env_path}', check=True, logger=self.logger, - info_msg=f"Created {self.spack_config.env.env_name} spack environment", - exception_msg=f"Failed to create {self.spack_config.env.env_name} spack environment", + info_msg=f"Created {self.spack_config.env.name} spack environment", + exception_msg=f"Failed to create {self.spack_config.env.name} spack environment", exception=BashCommandException) def setup_spack_env(self): @@ -79,13 +82,13 @@ class SpackOperation: self.create_fetch_spack_environment() if self.spack_config.install_dir.exists(): for repo in self.spack_config.repos: - repo_dir = self.spack_config.install_dir / repo.path / repo.env_name - git_clone_repo(repo.env_name, repo_dir, repo.git_path, logger=self.logger) - if not self.spack_repo_exists(repo.env_name): - self.add_spack_repo(repo.path, repo.env_name) - self.logger.debug(f'Added spack repository {repo.env_name}') + repo_dir = self.spack_config.install_dir / repo.path / repo.name + git_clone_repo(repo.name, repo_dir, repo.git_path, logger=self.logger) + if not self.spack_repo_exists(repo.name): + self.add_spack_repo(repo.path, repo.name) + self.logger.debug(f'Added spack repository {repo.name}') else: - self.logger.debug(f'Spack repository {repo.env_name} already added') + self.logger.debug(f'Spack repository {repo.name} already added') def spack_repo_exists(self, repo_name: str) -> bool | None: """Check if the given Spack repository exists.""" @@ -116,7 +119,7 @@ class SpackOperation: self.spack_command_on_env, check=True, capture_output=True, text=True, logger=self.logger, - info_msg=f'Checking if environment {self.spack_config.env.env_name} exists') + info_msg=f'Checking if environment {self.spack_config.env.name} exists') if result is None: return False return True @@ -127,8 +130,8 @@ class SpackOperation: run_command("bash", "-c", f'{self.spack_command_on_env} && spack repo add {repo_path}/{repo_name}', check=True, logger=self.logger, - info_msg=f"Added {repo_name} to spack environment {self.spack_config.env.env_name}", - exception_msg=f"Failed to add {repo_name} to spack environment {self.spack_config.env.env_name}", + info_msg=f"Added {repo_name} to spack environment {self.spack_config.env.name}", + exception_msg=f"Failed to add {repo_name} to spack environment {self.spack_config.env.name}", exception=SpackRepoException) @check_spack_env @@ -137,18 +140,18 @@ class SpackOperation: f'{self.spack_command_on_env} && spack compiler list', check=True, logger=self.logger, capture_output=True, text=True, - info_msg=f"Checking spack environment compiler version for {self.spack_config.env.env_name}", - exception_msg=f"Failed to checking spack environment compiler version for {self.spack_config.env.env_name}", + info_msg=f"Checking spack environment compiler version for {self.spack_config.env.name}", + exception_msg=f"Failed to checking spack environment compiler version for {self.spack_config.env.name}", exception=BashCommandException) # todo add error handling and tests if result.stdout is None: - self.logger.debug('No gcc found for {self.env.env_name}') + self.logger.debug(f'No gcc found for {self.spack_config.env.name}') return None # Find the first occurrence of a GCC compiler using regex match = re.search(r"gcc@([\d\.]+)", result.stdout) gcc_version = match.group(1) - self.logger.debug(f'Found gcc for {self.spack_config.env.env_name}: {gcc_version}') + self.logger.debug(f'Found gcc for {self.spack_config.env.name}: {gcc_version}') return gcc_version def get_spack_installed_version(self): @@ -168,8 +171,8 @@ class SpackOperation: f'{self.spack_command_on_env} && spack concretize {force}', check=True, logger=self.logger, - info_msg=f'Concertization step for {self.spack_config.env.env_name}', - exception_msg=f'Failed the concertization step for {self.spack_config.env.env_name}', + info_msg=f'Concertization step for {self.spack_config.env.name}', + exception_msg=f'Failed the concertization step for {self.spack_config.env.name}', exception=SpackConcertizeException) def create_gpg_keys(self): @@ -178,8 +181,8 @@ class SpackOperation: f'{self.spack_setup_script} spack gpg init && spack gpg create {self.spack_config.gpg.name} {self.spack_config.gpg.mail}', check=True, logger=self.logger, - info_msg=f'Created pgp keys for {self.spack_config.env.env_name}', - exception_msg=f'Failed to create pgp keys mirror {self.spack_config.env.env_name}', + info_msg=f'Created pgp keys for {self.spack_config.env.name}', + exception_msg=f'Failed to create pgp keys mirror {self.spack_config.env.name}', exception=SpackGpgException) else: raise SpackGpgException('No GPG configuration was defined is spack configuration') @@ -225,8 +228,8 @@ class SpackOperation: stderr=subprocess.PIPE, text=True, logger=self.logger, - info_msg=f"Installing spack packages for {self.spack_config.env.env_name}", - exception_msg=f"Error installing spack packages for {self.spack_config.env.env_name}", + info_msg=f"Installing spack packages for {self.spack_config.env.name}", + exception_msg=f"Error installing spack packages for {self.spack_config.env.name}", exception=SpackInstallPackagesException) log_command(install_result, str(Path(os.getcwd()).resolve() / ".generate_cache.log")) return install_result diff --git a/dedal/tests/integration_tests/spack_from_scratch_test.py b/dedal/tests/integration_tests/spack_from_scratch_test.py index 794caef1..7e8d900c 100644 --- a/dedal/tests/integration_tests/spack_from_scratch_test.py +++ b/dedal/tests/integration_tests/spack_from_scratch_test.py @@ -16,7 +16,7 @@ def test_spack_repo_exists_1(tmp_path): spack_operation = SpackOperationCreator.get_spack_operator(config) spack_operation.install_spack() with pytest.raises(NoSpackEnvironmentException): - spack_operation.spack_repo_exists(env.env_name) + spack_operation.spack_repo_exists(env.name) def test_spack_repo_exists_2(tmp_path): @@ -27,7 +27,7 @@ def test_spack_repo_exists_2(tmp_path): assert isinstance(spack_operation, SpackOperation) spack_operation.install_spack() spack_operation.setup_spack_env() - assert spack_operation.spack_repo_exists(env.env_name) == False + assert spack_operation.spack_repo_exists(env.name) == False def test_spack_from_scratch_setup_1(tmp_path): @@ -38,7 +38,7 @@ def test_spack_from_scratch_setup_1(tmp_path): assert isinstance(spack_operation, SpackOperation) spack_operation.install_spack() spack_operation.setup_spack_env() - assert spack_operation.spack_repo_exists(env.env_name) == False + assert spack_operation.spack_repo_exists(env.name) == False def test_spack_from_scratch_setup_2(tmp_path): @@ -52,7 +52,7 @@ def test_spack_from_scratch_setup_2(tmp_path): assert isinstance(spack_operation, SpackOperation) spack_operation.install_spack() spack_operation.setup_spack_env() - assert spack_operation.spack_repo_exists(env.env_name) == True + assert spack_operation.spack_repo_exists(env.name) == True def test_spack_from_scratch_setup_3(tmp_path): @@ -88,7 +88,7 @@ def test_spack_not_a_valid_repo(): spack_operation = SpackOperationCreator.get_spack_operator(config) assert isinstance(spack_operation, SpackOperation) with pytest.raises(BashCommandException): - spack_operation.add_spack_repo(repo.path, repo.env_name) + spack_operation.add_spack_repo(repo.path, repo.name) @pytest.mark.skip( diff --git a/dedal/tests/integration_tests/spack_operation_creator_test.py b/dedal/tests/integration_tests/spack_operation_creator_test.py new file mode 100644 index 00000000..226184b0 --- /dev/null +++ b/dedal/tests/integration_tests/spack_operation_creator_test.py @@ -0,0 +1,50 @@ +from dedal.spack_factory.SpackOperationCreateCache import SpackOperationCreateCache + +from dedal.configuration.SpackConfig import SpackConfig +from dedal.model.SpackDescriptor import SpackDescriptor +from dedal.spack_factory.SpackOperation import SpackOperation +from dedal.spack_factory.SpackOperationCreator import SpackOperationCreator +from dedal.spack_factory.SpackOperationUseCache import SpackOperationUseCache +from dedal.tests.testing_variables import ebrains_spack_builds_git, test_spack_env_git + + +def test_spack_creator_scratch_1(tmp_path): + install_dir = tmp_path + env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git) + repo = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git) + spack_config = SpackConfig(env, install_dir=install_dir) + spack_config.add_repo(repo) + spack_operation = SpackOperationCreator.get_spack_operator(spack_config) + assert isinstance(spack_operation, SpackOperation) + + +def test_spack_creator_scratch_2(tmp_path): + spack_config = None + spack_operation = SpackOperationCreator.get_spack_operator(spack_config) + assert isinstance(spack_operation, SpackOperation) + + +def test_spack_creator_scratch_3(): + spack_config = SpackConfig() + spack_operation = SpackOperationCreator.get_spack_operator(spack_config) + assert isinstance(spack_operation, SpackOperation) + + +def test_spack_creator_create_cache(tmp_path): + install_dir = tmp_path + env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git) + repo = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git) + spack_config = SpackConfig(env, install_dir=install_dir, concretization_dir=install_dir, buildcache_dir=install_dir) + spack_config.add_repo(repo) + spack_operation = SpackOperationCreator.get_spack_operator(spack_config) + assert isinstance(spack_operation, SpackOperationCreateCache) + + +def test_spack_creator_use_cache(tmp_path): + install_dir = tmp_path + env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git) + repo = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git) + spack_config = SpackConfig(env, install_dir=install_dir, concretization_dir=install_dir, buildcache_dir=install_dir) + spack_config.add_repo(repo) + spack_operation = SpackOperationCreator.get_spack_operator(spack_config, use_cache=True) + assert isinstance(spack_operation, SpackOperationUseCache) diff --git a/dedal/tests/unit_tests/spack_manager_api_test.py b/dedal/tests/unit_tests/spack_manager_api_test.py new file mode 100644 index 00000000..5d32a56d --- /dev/null +++ b/dedal/tests/unit_tests/spack_manager_api_test.py @@ -0,0 +1,183 @@ +import os + +import pytest +from unittest.mock import patch, MagicMock +from click.testing import CliRunner +from dedal.cli.spack_manager_api import show_config, clear_config, install_spack, add_spack_repo, install_packages, \ + setup_spack_env, concretize, set_config +from dedal.model.SpackDescriptor import SpackDescriptor + + +@pytest.fixture +def runner(): + return CliRunner() + + +@pytest.fixture +def mocked_session_path(): + return '/mocked/tmp/session.json' + + +@pytest.fixture +def mock_spack_manager(): + mock_spack_manager = MagicMock() + mock_spack_manager.install_spack = MagicMock() + mock_spack_manager.add_spack_repo = MagicMock() + mock_spack_manager.setup_spack_env = MagicMock() + mock_spack_manager.concretize_spack_env = MagicMock() + mock_spack_manager.install_packages = MagicMock() + return mock_spack_manager + + +@pytest.fixture +def mock_load_config(): + with patch('dedal.cli.spack_manager_api.load_config') as mock_load: + yield mock_load + + +@pytest.fixture +def mock_save_config(): + with patch('dedal.cli.spack_manager_api.save_config') as mock_save: + yield mock_save + + +@pytest.fixture +def mock_clear_config(): + with patch('dedal.cli.spack_manager_api.clear_config') as mock_clear: + yield mock_clear + + +def test_show_config_no_config(runner, mock_load_config): + mock_load_config.return_value = None + result = runner.invoke(show_config) + assert 'No configuration set. Use `set-config` first.' in result.output + + +def test_show_config_with_config(runner, mock_load_config): + """Test the show_config command when config is present.""" + mock_load_config.return_value = {"key": "value"} + result = runner.invoke(show_config) + assert result.exit_code == 0 + assert '"key": "value"' in result.output + + +def test_clear_config(runner, mock_clear_config): + """Test the clear_config command.""" + with patch('os.path.exists', return_value=True), patch('os.remove') as mock_remove: + result = runner.invoke(clear_config) + assert 'Configuration cleared!' in result.output + mock_remove.assert_called_once() + + +def test_install_spack_no_context_1(runner, mock_spack_manager): + """Test install_spack with no context, using SpackManager.""" + with patch('dedal.cli.spack_manager_api.SpackManager', return_value=mock_spack_manager): + result = runner.invoke(install_spack, ['--spack_version', '0.24.0']) + mock_spack_manager.install_spack.assert_called_once_with('0.24.0', os.path.expanduser("~/.bashrc")) + assert result.exit_code == 0 + + +def test_install_spack_no_context_2(runner, mock_spack_manager): + """Test install_spack with no context, using SpackManager and the default value for spack_version.""" + with patch('dedal.cli.spack_manager_api.SpackManager', return_value=mock_spack_manager): + result = runner.invoke(install_spack) + mock_spack_manager.install_spack.assert_called_once_with('0.23.0', os.path.expanduser("~/.bashrc")) + assert result.exit_code == 0 + + +def test_install_spack_with_mocked_context_1(runner, mock_spack_manager): + """Test install_spack with a mocked context, using ctx.obj as SpackManager.""" + result = runner.invoke(install_spack, ['--spack_version', '0.24.0', '--bashrc_path', '/home/.bahsrc'], obj=mock_spack_manager) + mock_spack_manager.install_spack.assert_called_once_with('0.24.0', '/home/.bahsrc') + assert result.exit_code == 0 + + +def test_install_spack_with_mocked_context_2(runner, mock_spack_manager): + """Test install_spack with a mocked context, using ctx.obj as SpackManager and the default value for spack_version.""" + result = runner.invoke(install_spack, obj=mock_spack_manager) + mock_spack_manager.install_spack.assert_called_once_with('0.23.0', os.path.expanduser("~/.bashrc")) + assert result.exit_code == 0 + + +def test_setup_spack_env(runner, mock_spack_manager): + """Test setup_spack_env with a mocked context, using ctx.obj as SpackManager.""" + result = runner.invoke(setup_spack_env, obj=mock_spack_manager) + mock_spack_manager.setup_spack_env.assert_called_once_with() + assert result.exit_code == 0 + + +def test_concretize(runner, mock_spack_manager): + """Test install_spack with a mocked context, using ctx.obj as SpackManager.""" + result = runner.invoke(concretize, obj=mock_spack_manager) + mock_spack_manager.concretize_spack_env.assert_called_once_with() + assert result.exit_code == 0 + + +def test_install_packages_1(runner, mock_spack_manager): + """Test install_packages with a mocked context, using ctx.obj as SpackManager.""" + result = runner.invoke(install_packages, obj=mock_spack_manager) + mock_spack_manager.install_packages.assert_called_once_with(jobs=2) + assert result.exit_code == 0 + + +def test_install_packages(runner, mock_spack_manager): + """Test install_packages with a mocked context, using ctx.obj as SpackManager.""" + result = runner.invoke(install_packages, ['--jobs', 3], obj=mock_spack_manager) + mock_spack_manager.install_packages.assert_called_once_with(jobs=3) + assert result.exit_code == 0 + + +@patch('dedal.cli.spack_manager_api.resolve_path') +@patch('dedal.cli.spack_manager_api.SpackDescriptor') +def test_add_spack_repo(mock_spack_descriptor, mock_resolve_path, mock_load_config, mock_save_config, + mocked_session_path, runner): + """Test adding a spack repository with mocks.""" + expected_config = {'repos': [SpackDescriptor(name='test-repo')]} + repo_name = 'test-repo' + path = '/path' + git_path = 'https://example.com/repo.git' + mock_resolve_path.return_value = '/resolved/path' + mock_load_config.return_value = expected_config + mock_repo_instance = MagicMock() + mock_spack_descriptor.return_value = mock_repo_instance + + with patch('dedal.cli.spack_manager_api.SESSION_CONFIG_PATH', mocked_session_path): + result = runner.invoke(add_spack_repo, ['--repo_name', repo_name, '--path', path, '--git_path', git_path]) + + assert result.exit_code == 0 + assert 'dedal setup_spack_env must be reran after each repo is added' in result.output + mock_resolve_path.assert_called_once_with(path) + mock_spack_descriptor.assert_called_once_with(repo_name, '/resolved/path', git_path) + assert mock_repo_instance in expected_config['repos'] + mock_save_config.assert_called_once_with(expected_config, mocked_session_path) + + +def test_set_config(runner, mock_save_config, mocked_session_path): + """Test set_config.""" + with patch('dedal.cli.spack_manager_api.SESSION_CONFIG_PATH', mocked_session_path): + result = runner.invoke(set_config, ['--env_name', 'test', '--system_name', 'sys']) + + expected_config = { + 'use_cache': False, + 'env_name': 'test', + 'env_path': None, + 'env_git_path': None, + 'install_dir': None, + 'upstream_instance': None, + 'system_name': 'sys', + 'concretization_dir': None, + 'buildcache_dir': None, + 'gpg_name': None, + 'gpg_mail': None, + 'use_spack_global': False, + 'repos': [], + 'cache_version_concretize': 'v1', + 'cache_version_build': 'v1', + } + + mock_save_config.assert_called_once() + saved_config, saved_path = mock_save_config.call_args[0] + assert saved_path == mocked_session_path + assert saved_config == expected_config + assert result.exit_code == 0 + assert 'Configuration saved.' in result.output -- GitLab