diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b248ae041b36e3fac5d7ef73ea268774ba923790..c164c870b480e9419c17fea9bc2f436f85be9e4f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,7 @@ build-wheel: expire_in: 1 week -testing-pytest: +testing-pytest-coverage: stage: test tags: - docker-runner @@ -30,8 +30,8 @@ testing-pytest: script: - chmod +x dedal/utils/bootstrap.sh - ./dedal/utils/bootstrap.sh - - pip install e .[tests,dev] - - pytest ./dedal/tests/ -s --junitxml=test-results.xml + - pip install e .[tests] + - coverage run -m pytest -s --tb=short tests && coverage html -i -d htmlcov artifacts: when: always reports: diff --git a/MANIFEST.ini b/MANIFEST.ini index d6809e0967f4d931510b20c673a811a27c9825a2..e62be46716825eab56feaa6d891b2f5d0bcf314d 100644 --- a/MANIFEST.ini +++ b/MANIFEST.ini @@ -1,3 +1,3 @@ include README.md -recursive-include yashchiki/esd *.* \ No newline at end of file +recursive-include yashchiki/dedal *.* \ No newline at end of file diff --git a/dedal/build_cache/BuildCacheManager.py b/dedal/build_cache/BuildCacheManager.py index eed6a45d41db7d677a5e347a4699caef90fce09e..f52acb8a43cd483b7572a1c0c17fc10c779bdd20 100644 --- a/dedal/build_cache/BuildCacheManager.py +++ b/dedal/build_cache/BuildCacheManager.py @@ -3,9 +3,10 @@ import os from os.path import join from pathlib import Path +import oras.client + from dedal.build_cache.BuildCacheManagerInterface import BuildCacheManagerInterface from dedal.logger.logger_builder import get_logger -from dedal.utils.utils import clean_up class BuildCacheManager(BuildCacheManagerInterface): @@ -112,12 +113,12 @@ class BuildCacheManager(BuildCacheManagerInterface): if tags is not None: try: self._client.delete_tags(self._oci_registry_path, tags) - self._logger.info(f"Successfully deleted all artifacts form OCI registry.") + self._logger.info("Successfully deleted all artifacts form OCI registry.") except RuntimeError as e: self._logger.error( f"Failed to delete artifacts: {e}") - def _log_warning_if_needed(self, warn_message: str, items: list[str]) -> None: + def __log_warning_if_needed(self, warn_message: str, items: list[str]) -> None: """Logs a warning message if the number of items is greater than 1. (Private function) This method logs a warning message using the provided message and items if the list of items has more than one element. @@ -148,14 +149,13 @@ class BuildCacheManager(BuildCacheManagerInterface): if not pgp_folders: self._logger.warning("No _pgp folder found in the build cache!") return None - self._log_warning_if_needed( + self.__log_warning_if_needed( "More than one PGP folders found in the build cache: %s, using the first one in the list: %s", pgp_folders) pgp_folder = pgp_folders[0] key_files = glob.glob(join(pgp_folder, "**")) if not key_files: self._logger.warning("No PGP key files found in the build cache!") return None - self._log_warning_if_needed( + self.__log_warning_if_needed( "More than one PGP key files found in the build cache: %s, using the first one in the list: %s", key_files) return key_files[0] - diff --git a/esd/commands/__init__.py b/dedal/commands/__init__.py similarity index 100% rename from esd/commands/__init__.py rename to dedal/commands/__init__.py diff --git a/esd/commands/bash_command_executor.py b/dedal/commands/bash_command_executor.py similarity index 91% rename from esd/commands/bash_command_executor.py rename to dedal/commands/bash_command_executor.py index 1811b757b1fe1fd14702d56a5b1e63a924ca0d6b..db464c67d8c4454ae31342a4c44f0952fc6ed420 100644 --- a/esd/commands/bash_command_executor.py +++ b/dedal/commands/bash_command_executor.py @@ -11,8 +11,8 @@ import os import subprocess from logging import Logger -from esd.commands.command import Command -from esd.commands.command_sequence import CommandSequence +from dedal.commands.command import Command +from dedal.commands.command_sequence import CommandSequence class BashCommandExecutor: @@ -91,3 +91,10 @@ class BashCommandExecutor: except subprocess.SubprocessError as e: error = f"Subprocess error occurred: {e}" return None, error + + def reset(self) -> None: + """Resets the command executor. + + Clears the internal command sequence, preparing the executor for a new set of commands. + """ + self.sequence.clear() diff --git a/esd/commands/command.py b/dedal/commands/command.py similarity index 100% rename from esd/commands/command.py rename to dedal/commands/command.py diff --git a/esd/commands/command_enum.py b/dedal/commands/command_enum.py similarity index 100% rename from esd/commands/command_enum.py rename to dedal/commands/command_enum.py diff --git a/esd/commands/command_registry.py b/dedal/commands/command_registry.py similarity index 98% rename from esd/commands/command_registry.py rename to dedal/commands/command_registry.py index ea2ab6466eaccc7d3df11b41f1ff7286a5a5ec60..adaa3d6eb95119f3e028ea05fe7640f9145cdb5f 100644 --- a/esd/commands/command_registry.py +++ b/dedal/commands/command_registry.py @@ -7,7 +7,7 @@ # Created by: Murugan, Jithu <j.murugan@fz-juelich.de> # Created on: 2025-02-17 -from esd.commands.command_enum import CommandEnum +from dedal.commands.command_enum import CommandEnum class CommandRegistry: diff --git a/esd/commands/command_runner.py b/dedal/commands/command_runner.py similarity index 77% rename from esd/commands/command_runner.py rename to dedal/commands/command_runner.py index a9bd89a02b12adbc44ec33a92e9a7647f048c680..88ee46a8248d900a0e721e83721067f71fde1060 100644 --- a/esd/commands/command_runner.py +++ b/dedal/commands/command_runner.py @@ -13,12 +13,12 @@ import logging from logging import Logger from typing import Callable, Any -from esd.commands.bash_command_executor import BashCommandExecutor -from esd.commands.command_enum import CommandEnum -from esd.commands.command_sequence import CommandSequence -from esd.commands.command_sequence_factory import CommandSequenceFactory -from esd.commands.preconfigured_command_enum import PreconfiguredCommandEnum -from esd.commands.spack_command_sequence_factory import SpackCommandSequenceFactory +from dedal.commands.bash_command_executor import BashCommandExecutor +from dedal.commands.command_enum import CommandEnum +from dedal.commands.command_sequence import CommandSequence +from dedal.commands.command_sequence_factory import CommandSequenceFactory +from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum +from dedal.commands.spack_command_sequence_factory import SpackCommandSequenceFactory class CommandRunner: @@ -43,8 +43,64 @@ class CommandRunner: 1. Define the commands as a dictionary, where the key represents the command already registered in the command_registry, and the value contains the corresponding placeholder values expected by the command. 2. Invoke the run_custom_command_sequence using generated dictionary to execute the custom command sequence. + Command Pattern Diagram + ------------------------ + + The Command pattern is used to encapsulate the request as an object, allowing for more flexibility and extensibility in handling requests. + + ``` + +---------------+ + | Client | + +---------------+ + | + | + v + +---------------+ + | Invoker | + | (CommandRunner)| + +---------------+ + | + | + v + +---------------+ + | Command | + | (PreconfiguredCommandEnum)| + +---------------+ + | + | + v + +---------------+ + | Receiver | + | (SpackCommandSequenceFactory)| + +---------------+ + ``` + + In this diagram: + + * The Client is the user of the CommandRunner. + * The Invoker is the CommandRunner itself, which receives the Command object and invokes the corresponding action. + * The Command is the PreconfiguredCommandEnum, which represents the request. + * The Receiver is the SpackCommandSequenceFactory, which performs the action requested by the Command object. + Benefits This module provides a flexible and unified interface for executing both predefined and custom command sequences, allowing users to easily manage and execute complex command workflows. + + + Attributes + ---------- + logger : Logger + The logger instance used for logging purposes. + executor : BashCommandExecutor + The BashCommandExecutor instance used for executing commands. + commands_map : dict[PreconfiguredCommandEnum, Callable[..., CommandSequence]] + A dictionary mapping preconfigured commands to their corresponding functions. + + Methods + ------- + run_preconfigured_command_sequence(preconfigured_command: PreconfiguredCommandEnum) + Executes a preconfigured command sequence. + run_custom_command_sequence(command_sequence: dict) + Executes a custom command sequence. """ def __init__(self) -> None: @@ -88,6 +144,7 @@ class CommandRunner: """ self.executor.add_command(command_sequence) output, error = self.executor.execute() + self.executor.reset() return { "success": error is None, "output": output.strip() if output else None, diff --git a/esd/commands/command_sequence.py b/dedal/commands/command_sequence.py similarity index 86% rename from esd/commands/command_sequence.py rename to dedal/commands/command_sequence.py index d0a3b469dc7941ee26735a2f0993ef8bbdf35af3..6115215a86a5b9d8fef5d3ea5af37f25210652ad 100644 --- a/esd/commands/command_sequence.py +++ b/dedal/commands/command_sequence.py @@ -7,7 +7,7 @@ # Created by: Murugan, Jithu <j.murugan@fz-juelich.de> # Created on: 2025-02-17 -from esd.commands.command import Command +from dedal.commands.command import Command class CommandSequence(Command): @@ -45,3 +45,10 @@ class CommandSequence(Command): The combined execution string of all commands in the sequence. """ return " && ".join(cmd.execute().strip() for cmd in self.commands).strip() + + def clear(self) -> None: + """Clears the command sequence. + + Removes all commands from the sequence, making it empty. + """ + self.commands.clear() diff --git a/esd/commands/command_sequence_builder.py b/dedal/commands/command_sequence_builder.py similarity index 91% rename from esd/commands/command_sequence_builder.py rename to dedal/commands/command_sequence_builder.py index 5f1d2c3f440573920e6957201019bbf1737aaa39..bd35505772d8d618b17a7e4a81b8d6c7698ef91c 100644 --- a/esd/commands/command_sequence_builder.py +++ b/dedal/commands/command_sequence_builder.py @@ -8,10 +8,10 @@ # Created on: 2025-02-19 from __future__ import annotations -from esd.commands.command_enum import CommandEnum -from esd.commands.command_registry import CommandRegistry -from esd.commands.command_sequence import CommandSequence -from esd.commands.shell_command_factory import ShellCommandFactory +from dedal.commands.command_enum import CommandEnum +from dedal.commands.command_registry import CommandRegistry +from dedal.commands.command_sequence import CommandSequence +from dedal.commands.shell_command_factory import ShellCommandFactory class CommandSequenceBuilder: diff --git a/esd/commands/command_sequence_factory.py b/dedal/commands/command_sequence_factory.py similarity index 90% rename from esd/commands/command_sequence_factory.py rename to dedal/commands/command_sequence_factory.py index 96804660f2f18b3ef42ac479ecad51885d0f57e3..1c1872451b4ec41950ce7138246996f16018b6e5 100644 --- a/esd/commands/command_sequence_factory.py +++ b/dedal/commands/command_sequence_factory.py @@ -7,13 +7,14 @@ # Created by: Murugan, Jithu <j.murugan@fz-juelich.de> # Created on: 2025-02-17 -from esd.commands.command_enum import CommandEnum -from esd.commands.command_sequence import CommandSequence -from esd.commands.command_sequence_builder import CommandSequenceBuilder +from dedal.commands.command_enum import CommandEnum +from dedal.commands.command_sequence import CommandSequence +from dedal.commands.command_sequence_builder import CommandSequenceBuilder class CommandSequenceFactory: """Factory for creating CommandSequence objects.""" + @staticmethod def create_custom_command_sequence( command_placeholders_map: dict[CommandEnum, dict[str, str]]) -> CommandSequence: diff --git a/esd/commands/generic_shell_command.py b/dedal/commands/generic_shell_command.py similarity index 97% rename from esd/commands/generic_shell_command.py rename to dedal/commands/generic_shell_command.py index ea40079d2d810ef2475390b54ae43f94c222b0f5..0a02b09537722087a93ab43a211b20e1c82750e9 100644 --- a/esd/commands/generic_shell_command.py +++ b/dedal/commands/generic_shell_command.py @@ -7,7 +7,7 @@ # Created by: Murugan, Jithu <j.murugan@fz-juelich.de> # Created on: 2025-02-17 -from esd.commands.command import Command +from dedal.commands.command import Command class GenericShellCommand(Command): @@ -16,6 +16,7 @@ class GenericShellCommand(Command): Encapsulates a shell command with its name and arguments, providing a way to execute it. """ + def __init__(self, command_name: str, *args: str) -> None: """Initializes a new GenericShellCommand. diff --git a/esd/commands/preconfigured_command_enum.py b/dedal/commands/preconfigured_command_enum.py similarity index 100% rename from esd/commands/preconfigured_command_enum.py rename to dedal/commands/preconfigured_command_enum.py diff --git a/esd/commands/shell_command_factory.py b/dedal/commands/shell_command_factory.py similarity index 89% rename from esd/commands/shell_command_factory.py rename to dedal/commands/shell_command_factory.py index 99baec39b378a9f30eb4b4b31fb4b6813fc9233d..e63a456e201903a8ebf6a4885a4e230c509e97f6 100644 --- a/esd/commands/shell_command_factory.py +++ b/dedal/commands/shell_command_factory.py @@ -7,8 +7,8 @@ # Created by: Murugan, Jithu <j.murugan@fz-juelich.de> # Created on: 2025-02-17 -from esd.commands.command import Command -from esd.commands.generic_shell_command import GenericShellCommand +from dedal.commands.command import Command +from dedal.commands.generic_shell_command import GenericShellCommand class ShellCommandFactory: diff --git a/esd/commands/spack_command_sequence_factory.py b/dedal/commands/spack_command_sequence_factory.py similarity index 98% rename from esd/commands/spack_command_sequence_factory.py rename to dedal/commands/spack_command_sequence_factory.py index e8d7e52229ae7aea84b3a8dee0e1ddf6c2549e01..ce7afac388e152412842ba448a9f17c126f90513 100644 --- a/esd/commands/spack_command_sequence_factory.py +++ b/dedal/commands/spack_command_sequence_factory.py @@ -7,9 +7,9 @@ # Created by: Murugan, Jithu <j.murugan@fz-juelich.de> # Created on: 2025-02-17 -from esd.commands.command_enum import CommandEnum -from esd.commands.command_sequence import CommandSequence -from esd.commands.command_sequence_builder import CommandSequenceBuilder +from dedal.commands.command_enum import CommandEnum +from dedal.commands.command_sequence import CommandSequence +from dedal.commands.command_sequence_builder import CommandSequenceBuilder def get_name_version(name: str | None, version: str | None) -> str: diff --git a/dedal/spack_factory/SpackOperation.py b/dedal/spack_factory/SpackOperation.py index ecfbb8e5084fc1e49b32894aecd47190df36fd98..d37a5e0872267023f52d1048d9779894a3180fee 100644 --- a/dedal/spack_factory/SpackOperation.py +++ b/dedal/spack_factory/SpackOperation.py @@ -2,13 +2,16 @@ import os import re import subprocess from pathlib import Path + +from dedal.commands.command_runner import CommandRunner +from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum +from dedal.configuration.SpackConfig import SpackConfig from dedal.error_handling.exceptions import BashCommandException, NoSpackEnvironmentException, \ SpackInstallPackagesException, SpackConcertizeException, SpackMirrorException, SpackGpgException from dedal.logger.logger_builder import get_logger -from dedal.configuration.SpackConfig import SpackConfig from dedal.tests.testing_variables import SPACK_VERSION -from dedal.wrapper.spack_wrapper import check_spack_env from dedal.utils.utils import run_command, git_clone_repo, log_command, set_bashrc_variable +from dedal.wrapper.spack_wrapper import check_spack_env class SpackOperation: @@ -36,6 +39,7 @@ class SpackOperation: self.spack_config.env.path.mkdir(parents=True, exist_ok=True) self.env_path = spack_config.env.path / spack_config.env.env_name self.spack_command_on_env = f'source {self.spack_setup_script} && spack env activate -p {self.env_path}' + self.command_runner = CommandRunner() def create_fetch_spack_environment(self): if self.spack_config.env.git_path: @@ -109,9 +113,7 @@ class SpackOperation: check=True, capture_output=True, text=True, logger=self.logger, info_msg=f'Checking if environment {self.spack_config.env.env_name} exists') - if result is None: - return False - return True + return result is not None @check_spack_env def add_spack_repo(self, repo_path: Path, repo_name: str): @@ -159,7 +161,7 @@ class SpackOperation: run_command("bash", "-c", f'{self.spack_command_on_env} && spack concretize {force}', check=True, - logger=self.logger, + 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}', exception=SpackConcertizeException) @@ -176,26 +178,104 @@ class SpackOperation: else: raise SpackGpgException('No GPG configuration was defined is spack configuration') - def add_mirror(self, mirror_name: str, mirror_path: Path, signed=False, autopush=False, global_mirror=False): - autopush = '--autopush' if autopush else '' - signed = '--signed' if signed else '' - if global_mirror: - run_command("bash", "-c", - f'source {self.spack_setup_script} && spack mirror add {autopush} {signed} {mirror_name} {mirror_path}', - check=True, - logger=self.logger, - info_msg=f'Added mirror {mirror_name}', - exception_msg=f'Failed to add mirror {mirror_name}', - exception=SpackMirrorException) - else: - check_spack_env( - run_command("bash", "-c", - f'{self.spack_command_on_env} && spack mirror add {autopush} {signed} {mirror_name} {mirror_path}', - check=True, - logger=self.logger, - info_msg=f'Added mirror {mirror_name}', - exception_msg=f'Failed to add mirror {mirror_name}', - exception=SpackMirrorException)) + def add_mirror(self, + mirror_name: str, + mirror_path: str, + signed=False, + autopush=False, + global_mirror=False) -> bool: + """Adds a Spack mirror. + + Adds a new mirror to the Spack configuration, either globally or to a specific environment. + + Args: + mirror_name (str): The name of the mirror. + mirror_path (str): The path or URL of the mirror. + signed (bool): Whether to require signed packages from the mirror. + autopush (bool): Whether to enable autopush for the mirror. + global_mirror (bool): Whether to add the mirror globally (True) or to the current environment (False). + + Returns: + True if the mirror was added successfully, False otherwise. + + Raises: + ValueError: If mirror_name or mirror_path are empty. + NoSpackEnvironmentException: If global_mirror is False and no environment is defined. + """ + if not mirror_name or not mirror_path: + raise ValueError("mirror_name and mirror_path are required") + if not global_mirror and not self.env_path: + raise NoSpackEnvironmentException('No spack environment defined') + result = self.command_runner.run_preconfigured_command_sequence( + PreconfiguredCommandEnum.SPACK_MIRROR_ADD, + self.spack_setup_script, + "" if global_mirror else self.env_path, + mirror_name, + mirror_path, + autopush, + signed) + return self.handle_result( + result, + "Failed to add mirror %s, reason: %s, output: %s", + (mirror_name,), + "Added mirror %s", + (mirror_name,) + ) + + def trust_gpg_key(self, public_key_path: str): + """Adds a GPG public key to the trusted keyring. + + This method attempts to add the provided GPG public key to the + Spack trusted keyring. + + Args: + public_key_path (str): Path to the GPG public key file. + + Returns: + bool: True if the key was added successfully, False otherwise. + + Raises: + ValueError: If public_key_path is empty. + """ + if not public_key_path: + raise ValueError("public_key_path is required") + result = self.command_runner.run_preconfigured_command_sequence( + PreconfiguredCommandEnum.SPACK_GPG_TRUST, + self.spack_setup_script, + public_key_path) + return self.handle_result( + result, + "Failed to add public key %s as trusted, reason: %s, output: %s", + (public_key_path,), + "Added public key %s as trusted", + (public_key_path,), + ) + + def handle_result(self, + result: dict[str, str | bool | None], + error_msg: str, + error_msg_args: tuple[str, ...], + info_msg: str, + info_msg_args: tuple[str, ...]): + """Handles the result of a command execution. + + Checks the success status of the result and logs either an error or an info message accordingly. + + Args: + result (dict[str, str | bool | None]): A dictionary containing the result of the command execution. + error_msg (str): The error message to log if the command failed. + error_msg_args (tuple[str, ...]): Arguments to format the error message. + info_msg (str): The info message to log if the command succeeded. + info_msg_args (tuple[str, ...]): Arguments to format the info message. + + Returns: + bool: True if the command succeeded, False otherwise. + """ + if not result["success"]: + self.logger.error(error_msg, *error_msg_args, result['error'], result['output']) + return False + self.logger.info(info_msg, *info_msg_args) + return True def remove_mirror(self, mirror_name: str): run_command("bash", "-c", diff --git a/dedal/spack_factory/SpackOperationUseCache.py b/dedal/spack_factory/SpackOperationUseCache.py index efb9af763af58a0f9e44ab79cab13af56947502e..a05b9c40f1e0a9b05e80504b4e887e05d7c69018 100644 --- a/dedal/spack_factory/SpackOperationUseCache.py +++ b/dedal/spack_factory/SpackOperationUseCache.py @@ -1,8 +1,10 @@ import os + from dedal.build_cache.BuildCacheManager import BuildCacheManager +from dedal.configuration.SpackConfig import SpackConfig +from dedal.error_handling.exceptions import NoSpackEnvironmentException from dedal.logger.logger_builder import get_logger from dedal.spack_factory.SpackOperation import SpackOperation -from dedal.configuration.SpackConfig import SpackConfig class SpackOperationUseCache(SpackOperation): @@ -25,8 +27,28 @@ class SpackOperationUseCache(SpackOperation): cache_version=cache_version_build) def setup_spack_env(self): + """Sets up the Spack environment using cached data. + + Downloads the build cache, trusts the cached public key (if available), + and adds the build cache as a local mirror. + """ super().setup_spack_env() - # todo add buildcache to the spack environment + try: + # Download build cache from OCI Registry and add public key to trusted keys + self.build_cache.download(self.spack_config.buildcache_dir) + cached_public_key = self.build_cache.get_public_key_from_cache(self.spack_config.buildcache_dir) + signed = cached_public_key is not None and self.trust_gpg_key(cached_public_key) + if not signed: + self.logger.warning("Public key not found in cache or failed to trust pgp keys!") + # Add build cache mirror + self.add_mirror('local_cache', + str(self.spack_config.buildcache_dir), + signed=signed, + autopush=True, + global_mirror=False) + except (ValueError, NoSpackEnvironmentException) as e: + self.logger.error("Error adding buildcache mirror: %s", e) + raise def concretize_spack_env(self, force=True): pass diff --git a/esd/tests/spack_from_scratch_test.py b/dedal/tests/spack_from_scratch_test.py similarity index 95% rename from esd/tests/spack_from_scratch_test.py rename to dedal/tests/spack_from_scratch_test.py index cdc405e744fb70389ce474fa04a59c973da8e58d..2fec80f743d72190d0b175191660250981f98255 100644 --- a/esd/tests/spack_from_scratch_test.py +++ b/dedal/tests/spack_from_scratch_test.py @@ -1,11 +1,11 @@ from pathlib import Path import pytest -from esd.configuration.SpackConfig import SpackConfig -from esd.error_handling.exceptions import BashCommandException, NoSpackEnvironmentException -from esd.spack_factory.SpackOperationCreator import SpackOperationCreator -from esd.model.SpackDescriptor import SpackDescriptor -from esd.tests.testing_variables import test_spack_env_git, ebrains_spack_builds_git -from esd.utils.utils import file_exists_and_not_empty +from dedal.configuration.SpackConfig import SpackConfig +from dedal.error_handling.exceptions import BashCommandException, NoSpackEnvironmentException +from dedal.spack_factory.SpackOperationCreator import SpackOperationCreator +from dedal.model.SpackDescriptor import SpackDescriptor +from dedal.tests.testing_variables import test_spack_env_git, ebrains_spack_builds_git +from dedal.utils.utils import file_exists_and_not_empty def test_spack_repo_exists_1(): diff --git a/esd/tests/spack_install_test.py b/dedal/tests/spack_install_test.py similarity index 76% rename from esd/tests/spack_install_test.py rename to dedal/tests/spack_install_test.py index 28f8268e668ee2036930b9724ea72cd0320e82d4..564d5c6aa2138e815cd7d092215a4f2eee8816f6 100644 --- a/esd/tests/spack_install_test.py +++ b/dedal/tests/spack_install_test.py @@ -1,6 +1,6 @@ import pytest -from esd.spack_factory.SpackOperation import SpackOperation -from esd.tests.testing_variables import SPACK_VERSION +from dedal.spack_factory.SpackOperation import SpackOperation +from dedal.tests.testing_variables import SPACK_VERSION # run this test first so that spack is installed only once for all the tests diff --git a/esd/tests/unit_tests/test_bash_command_executor.py b/dedal/tests/unit_tests/test_bash_command_executor.py similarity index 84% rename from esd/tests/unit_tests/test_bash_command_executor.py rename to dedal/tests/unit_tests/test_bash_command_executor.py index 70633fa2121e3bd3bddaed9055bd1afdd56aaa67..f3624960306972c8a04f717e65407c4761e62ea0 100644 --- a/esd/tests/unit_tests/test_bash_command_executor.py +++ b/dedal/tests/unit_tests/test_bash_command_executor.py @@ -11,8 +11,8 @@ from unittest.mock import patch import pytest -from esd.commands.bash_command_executor import BashCommandExecutor -from esd.commands.command import Command +from dedal.commands.bash_command_executor import BashCommandExecutor +from dedal.commands.command import Command class MockCommand(Command): @@ -34,8 +34,8 @@ class TestBashCommandExecutor: def test_init_success_path(self, mocker, test_id, os_name, expected_bash_command): # Arrange original_os = os.name - mock_get_logger = mocker.patch("esd.commands.bash_command_executor.logging.getLogger") - mocker.patch("esd.commands.bash_command_executor.os.name", os_name) + mock_get_logger = mocker.patch("dedal.commands.bash_command_executor.logging.getLogger") + mocker.patch("dedal.commands.bash_command_executor.os.name", os_name) # mock_os_name.return_value = os_name # Act @@ -44,7 +44,7 @@ class TestBashCommandExecutor: # Assert assert executor.bash_command == expected_bash_command - mock_get_logger.assert_called_once_with('esd.commands.bash_command_executor') + mock_get_logger.assert_called_once_with('dedal.commands.bash_command_executor') @pytest.mark.parametrize( "test_id, num_commands", [(1, 1), (5, 5), (0, 0)] @@ -76,7 +76,7 @@ class TestBashCommandExecutor: # Assert assert str(except_info.value) == "Invalid command type. Use Command." - @patch("esd.commands.bash_command_executor.os.name", "unknown") + @patch("dedal.commands.bash_command_executor.os.name", "unknown") def test_init_unknown_os(self): # Act @@ -94,7 +94,7 @@ class TestBashCommandExecutor: ], ) - @patch("esd.commands.bash_command_executor.subprocess.run") + @patch("dedal.commands.bash_command_executor.subprocess.run") def test_execute_success_path_posix(self, mock_subprocess_run, test_id, commands, expected_output, mocker): # Arrange executor = BashCommandExecutor() @@ -114,7 +114,7 @@ class TestBashCommandExecutor: [mocker.call("Successfully executed command sequence, output: %s", mock_subprocess_run.return_value.stdout)]) - @patch("esd.commands.bash_command_executor.subprocess.run", + @patch("dedal.commands.bash_command_executor.subprocess.run", side_effect=FileNotFoundError("Mock file not found error")) def test_execute_file_not_found_error(self, mock_subprocess_run): # Arrange @@ -131,12 +131,12 @@ class TestBashCommandExecutor: mock_subprocess_run.assert_called_once_with(['bash', '-c', 'some_command'], capture_output=True, text=True, check=True, timeout=172800) - @patch("esd.commands.bash_command_executor.subprocess.run", + @patch("dedal.commands.bash_command_executor.subprocess.run", side_effect=subprocess.CalledProcessError(1, "some_command", stderr="Mock stderr")) def test_execute_called_process_error(self, mock_subprocess_run, mocker): # Arrange original_os = os.name - mocker.patch("esd.commands.bash_command_executor.os.name", "nt") + mocker.patch("dedal.commands.bash_command_executor.os.name", "nt") executor = BashCommandExecutor() executor.add_command(MockCommand("failing_command")) @@ -170,8 +170,8 @@ class TestBashCommandExecutor: def test_execute_other_errors(self, test_id, exception, expected_error_message, mocker): # Arrange original_os = os.name - mocker.patch("esd.commands.bash_command_executor.os.name", "nt") - with patch("esd.commands.bash_command_executor.subprocess.run", side_effect=exception) as mock_subprocess_run: + mocker.patch("dedal.commands.bash_command_executor.os.name", "nt") + with patch("dedal.commands.bash_command_executor.subprocess.run", side_effect=exception) as mock_subprocess_run: executor = BashCommandExecutor() executor.add_command(MockCommand("some_command")) @@ -197,12 +197,12 @@ class TestBashCommandExecutor: # Assert assert str(except_info.value) == "No commands to execute." - @patch("esd.commands.bash_command_executor.subprocess.run") + @patch("dedal.commands.bash_command_executor.subprocess.run") def test_execute_happy_path_nt(self, mock_subprocess_run, mocker): # Arrange original_os = os.name - mocker.patch("esd.commands.bash_command_executor.os.name", "nt") + mocker.patch("dedal.commands.bash_command_executor.os.name", "nt") executor = BashCommandExecutor() executor.add_command(MockCommand("echo hello")) mock_subprocess_run.return_value.stdout = "hello\n" @@ -220,7 +220,7 @@ class TestBashCommandExecutor: # Cleanup os.name = original_os - @patch("esd.commands.bash_command_executor.os.name", "unknown") + @patch("dedal.commands.bash_command_executor.os.name", "unknown") def test_execute_unknown_os(self): # Arrange executor = BashCommandExecutor() @@ -230,3 +230,17 @@ class TestBashCommandExecutor: assert executor.execute() == (None, "Error: Bash Command: ['undefined'] not found: [WinError 2] The system cannot " 'find the file specified') + + def test_reset(self, mocker): + # Test ID: reset + + # Arrange + executor = BashCommandExecutor() + mock_sequence = mocker.MagicMock() + executor.sequence = mock_sequence + + # Act + executor.reset() + + # Assert + mock_sequence.clear.assert_called_once() diff --git a/esd/tests/unit_tests/test_build_cache_manager.py b/dedal/tests/unit_tests/test_build_cache_manager.py similarity index 91% rename from esd/tests/unit_tests/test_build_cache_manager.py rename to dedal/tests/unit_tests/test_build_cache_manager.py index 687570eb40ef8d088b999d45ab37c07724b2c274..29054b1ac567501268ffc0bc1df7193af624e1f4 100644 --- a/esd/tests/unit_tests/test_build_cache_manager.py +++ b/dedal/tests/unit_tests/test_build_cache_manager.py @@ -8,14 +8,14 @@ import pytest from _pytest.fixtures import fixture -from esd.build_cache.BuildCacheManager import BuildCacheManager +from dedal.build_cache.BuildCacheManager import BuildCacheManager class TestBuildCacheManager: @fixture(scope="function") def mock_build_cache_manager(self, mocker): - mocker.patch("esd.build_cache.BuildCacheManager.get_logger") + mocker.patch("dedal.build_cache.BuildCacheManager.get_logger") return BuildCacheManager("TEST_HOST", "TEST_PROJECT", "TEST_USERNAME", "TEST_PASSWORD", "TEST_VERSION") def test_get_public_key_from_cache_success_path(self, mock_build_cache_manager, tmp_path): @@ -61,8 +61,8 @@ class TestBuildCacheManager: # Assert assert result == str(build_cache_dir / "project0" / "_pgp" / "key0.pub") - log = (expected_log_message, *pgp_folders, pgp_folders[0]) if test_id == "more_than_one_gpg_folder" else ( - expected_log_message, *key_files, key_files[0]) + log = (expected_log_message, pgp_folders, pgp_folders[0]) if test_id == "more_than_one_gpg_folder" else ( + expected_log_message, key_files, key_files[0]) mock_build_cache_manager._logger.warning.assert_called_once_with(*log) @pytest.mark.parametrize("build_cache_dir, expected_log_message", [ @@ -133,10 +133,10 @@ class TestBuildCacheManager: warn_message = "test message" # Act - mock_build_cache_manager.log_warning_if_needed(warn_message, items) + mock_build_cache_manager._BuildCacheManager__log_warning_if_needed(warn_message, items) # Assert - mock_build_cache_manager._logger.warning.assert_called_once_with(warn_message, *items, items[0]) + mock_build_cache_manager._logger.warning.assert_called_once_with(warn_message, items, items[0]) @pytest.mark.parametrize("items", [ [], @@ -149,7 +149,7 @@ class TestBuildCacheManager: warn_message = "test message" # Act - mock_build_cache_manager.log_warning_if_needed(warn_message, items) + mock_build_cache_manager._BuildCacheManager__log_warning_if_needed(warn_message, items) # Assert mock_build_cache_manager._logger.warning.assert_not_called() diff --git a/esd/tests/unit_tests/test_command.py b/dedal/tests/unit_tests/test_command.py similarity index 96% rename from esd/tests/unit_tests/test_command.py rename to dedal/tests/unit_tests/test_command.py index 8f957a35adef61b48b3cf91e6280499bdc09d9f3..3c86404199262dec907c983620f7c95a7adf0bc6 100644 --- a/esd/tests/unit_tests/test_command.py +++ b/dedal/tests/unit_tests/test_command.py @@ -8,7 +8,7 @@ import pytest -from esd.commands.command import Command +from dedal.commands.command import Command class ConcreteCommand(Command): def __init__(self, return_value: str): diff --git a/esd/tests/unit_tests/test_command_enum.py b/dedal/tests/unit_tests/test_command_enum.py similarity index 94% rename from esd/tests/unit_tests/test_command_enum.py rename to dedal/tests/unit_tests/test_command_enum.py index 57c4ec3427836247dc97f693fa00188e0afcfb7c..f29e2b4c707a318100fa3d90c9f62d54d8b64ebb 100644 --- a/esd/tests/unit_tests/test_command_enum.py +++ b/dedal/tests/unit_tests/test_command_enum.py @@ -8,7 +8,7 @@ import pytest -from esd.commands.command_enum import CommandEnum +from dedal.commands.command_enum import CommandEnum class TestCommandEnum: diff --git a/esd/tests/unit_tests/test_command_runner.py b/dedal/tests/unit_tests/test_command_runner.py similarity index 87% rename from esd/tests/unit_tests/test_command_runner.py rename to dedal/tests/unit_tests/test_command_runner.py index 24ec4a93a0e000c4098ac9450ded0f576915c0f1..ac30fa083b82a1dc8684c33dfcf91c19ea0d2a3e 100644 --- a/esd/tests/unit_tests/test_command_runner.py +++ b/dedal/tests/unit_tests/test_command_runner.py @@ -8,10 +8,10 @@ import pytest -from esd.commands.command_enum import CommandEnum -from esd.commands.command_runner import CommandRunner -from esd.commands.command_sequence import CommandSequence -from esd.commands.preconfigured_command_enum import PreconfiguredCommandEnum +from dedal.commands.command_enum import CommandEnum +from dedal.commands.command_runner import CommandRunner +from dedal.commands.command_sequence import CommandSequence +from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum class MockCommandSequence(CommandSequence): @@ -25,8 +25,8 @@ class MockCommandSequence(CommandSequence): class TestCommandRunner: @pytest.fixture(scope="function") def mock_command_runner(self, mocker): - mocker.patch("esd.commands.command_runner.BashCommandExecutor") - mocker.patch("esd.commands.command_runner.logging.getLogger") + mocker.patch("dedal.commands.command_runner.BashCommandExecutor") + mocker.patch("dedal.commands.command_runner.logging.getLogger") return CommandRunner() @pytest.mark.parametrize( @@ -51,6 +51,8 @@ class TestCommandRunner: # Assert assert result == expected_output + mock_command_runner.executor.execute.assert_called_once() + mock_command_runner.executor.reset.assert_called_once() @pytest.mark.parametrize( "test_id, command_type, args, expected_result", @@ -111,7 +113,7 @@ class TestCommandRunner: # Arrange mock_command_runner.execute_command = mocker.MagicMock(return_value=expected_result) mock_create_custom_command_sequence = mocker.patch( - "esd.commands.command_runner.CommandSequenceFactory.create_custom_command_sequence") + "dedal.commands.command_runner.CommandSequenceFactory.create_custom_command_sequence") mock_create_custom_command_sequence.return_value = MockCommandSequence("mock_command") # Act diff --git a/esd/tests/unit_tests/test_command_sequence.py b/dedal/tests/unit_tests/test_command_sequence.py similarity index 80% rename from esd/tests/unit_tests/test_command_sequence.py rename to dedal/tests/unit_tests/test_command_sequence.py index 8b68e10747507b98106e22540eaa8ca96caf8f2c..e663c06b57e32630666510e0b6ed55313012fe26 100644 --- a/esd/tests/unit_tests/test_command_sequence.py +++ b/dedal/tests/unit_tests/test_command_sequence.py @@ -5,11 +5,12 @@ # Description: Brief description of the file. # Created by: Murugan, Jithu <j.murugan@fz-juelich.de> # Created on: 2025-02-19 +from unittest.mock import Mock import pytest -from esd.commands.command import Command -from esd.commands.command_sequence import CommandSequence +from dedal.commands.command import Command +from dedal.commands.command_sequence import CommandSequence class MockCommand(Command): @@ -30,7 +31,8 @@ class TestCommandSequence: ("single_command_with_spaces at end", [" pwd "], "pwd"), ("multiple_commands_with_spaces", [" echo hello", " ls -l /tmp "], "echo hello && ls -l /tmp"), ("multiple_commands", ["echo hello", "ls -l /tmp"], "echo hello && ls -l /tmp"), - ("multiple_commands_with_spaces_in_between_and_end", ["echo hello ", "ls -l /tmp "], "echo hello && ls -l /tmp"), + ("multiple_commands_with_spaces_in_between_and_end", ["echo hello ", "ls -l /tmp "], + "echo hello && ls -l /tmp"), ("empty_command", [""], ""), ("commands_with_spaces", ["command with spaces", "another command"], "command with spaces && another command"), @@ -89,3 +91,18 @@ class TestCommandSequence: # Assert assert str(except_info.value) == "Command must be an instance of Command" + + @pytest.mark.parametrize("initial_commands", [[], [Mock(spec=Command), Mock(spec=Command)]]) + def test_clear(self, mocker, initial_commands): + # Test ID: clear + + # Arrange + sequence = CommandSequence() + for command in initial_commands: + sequence.add_command(command) + + # Act + sequence.clear() + + # Assert + assert len(sequence.commands) == 0 diff --git a/esd/tests/unit_tests/test_command_sequence_builder.py b/dedal/tests/unit_tests/test_command_sequence_builder.py similarity index 91% rename from esd/tests/unit_tests/test_command_sequence_builder.py rename to dedal/tests/unit_tests/test_command_sequence_builder.py index 6c4062a4d15dd6bc5ebd5288abfb092fc5c50e2c..e4004255128836a4643665bf6acf338cd9497155 100644 --- a/esd/tests/unit_tests/test_command_sequence_builder.py +++ b/dedal/tests/unit_tests/test_command_sequence_builder.py @@ -10,12 +10,12 @@ from unittest.mock import Mock, patch import pytest -from esd.commands.command import Command -from esd.commands.command_enum import CommandEnum -from esd.commands.command_registry import CommandRegistry -from esd.commands.command_sequence import CommandSequence -from esd.commands.command_sequence_builder import CommandSequenceBuilder -from esd.commands.shell_command_factory import ShellCommandFactory +from dedal.commands.command import Command +from dedal.commands.command_enum import CommandEnum +from dedal.commands.command_registry import CommandRegistry +from dedal.commands.command_sequence import CommandSequence +from dedal.commands.command_sequence_builder import CommandSequenceBuilder +from dedal.commands.shell_command_factory import ShellCommandFactory class TestCommandSequenceBuilder: diff --git a/esd/tests/unit_tests/test_command_sequence_factory.py b/dedal/tests/unit_tests/test_command_sequence_factory.py similarity index 82% rename from esd/tests/unit_tests/test_command_sequence_factory.py rename to dedal/tests/unit_tests/test_command_sequence_factory.py index 7515f14af5cc65d27779e9b8cd1e886aee6646ed..7048690aac222786ae5b8794c07969e8e8e91544 100644 --- a/esd/tests/unit_tests/test_command_sequence_factory.py +++ b/dedal/tests/unit_tests/test_command_sequence_factory.py @@ -10,10 +10,10 @@ from unittest.mock import Mock, patch import pytest -from esd.commands.command_enum import CommandEnum -from esd.commands.command_sequence import CommandSequence -from esd.commands.command_sequence_builder import CommandSequenceBuilder -from esd.commands.command_sequence_factory import CommandSequenceFactory +from dedal.commands.command_enum import CommandEnum +from dedal.commands.command_sequence import CommandSequence +from dedal.commands.command_sequence_builder import CommandSequenceBuilder +from dedal.commands.command_sequence_factory import CommandSequenceFactory class TestCommandSequenceFactory: @@ -37,7 +37,7 @@ class TestCommandSequenceFactory: mock_builder.add_generic_command = mock_add_generic_command mock_add_generic_command.return_value = mock_builder - with patch("esd.commands.command_sequence_factory.CommandSequenceBuilder", return_value=mock_builder): + with patch("dedal.commands.command_sequence_factory.CommandSequenceBuilder", return_value=mock_builder): # Act CommandSequenceFactory.create_custom_command_sequence(command_placeholders_map) diff --git a/esd/tests/unit_tests/test_generic_shell_command.py b/dedal/tests/unit_tests/test_generic_shell_command.py similarity index 95% rename from esd/tests/unit_tests/test_generic_shell_command.py rename to dedal/tests/unit_tests/test_generic_shell_command.py index e9287e0494bb60645cf32b9acf305f12c36b22b6..7ed779aee2faf531e2472d9219551d1792aa7bf1 100644 --- a/esd/tests/unit_tests/test_generic_shell_command.py +++ b/dedal/tests/unit_tests/test_generic_shell_command.py @@ -8,8 +8,8 @@ import pytest -from esd.commands.command import Command -from esd.commands.generic_shell_command import GenericShellCommand +from dedal.commands.command import Command +from dedal.commands.generic_shell_command import GenericShellCommand class TestGenericShellCommand: diff --git a/esd/tests/unit_tests/test_preconfigured_command_enum.py b/dedal/tests/unit_tests/test_preconfigured_command_enum.py similarity index 93% rename from esd/tests/unit_tests/test_preconfigured_command_enum.py rename to dedal/tests/unit_tests/test_preconfigured_command_enum.py index 02539d5529851b8a7beb223da518801129833e2d..820c4d4175903e12c4cd125cf327371fa08145f4 100644 --- a/esd/tests/unit_tests/test_preconfigured_command_enum.py +++ b/dedal/tests/unit_tests/test_preconfigured_command_enum.py @@ -8,7 +8,7 @@ import pytest -from esd.commands.preconfigured_command_enum import PreconfiguredCommandEnum +from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum class TestPreconfiguredCommandEnum: diff --git a/esd/tests/unit_tests/test_shell_command_factory.py b/dedal/tests/unit_tests/test_shell_command_factory.py similarity index 94% rename from esd/tests/unit_tests/test_shell_command_factory.py rename to dedal/tests/unit_tests/test_shell_command_factory.py index f2f95b908839d868ed73b8403ad057cd292dddb1..bfd2b2db3f98d45419cf2f6e3246cf5d2d4f4c00 100644 --- a/esd/tests/unit_tests/test_shell_command_factory.py +++ b/dedal/tests/unit_tests/test_shell_command_factory.py @@ -8,8 +8,8 @@ import pytest -from esd.commands.generic_shell_command import GenericShellCommand -from esd.commands.shell_command_factory import ShellCommandFactory +from dedal.commands.generic_shell_command import GenericShellCommand +from dedal.commands.shell_command_factory import ShellCommandFactory class TestShellCommandFactory: diff --git a/esd/tests/unit_tests/test_spack_command_sequence_factory.py b/dedal/tests/unit_tests/test_spack_command_sequence_factory.py similarity index 96% rename from esd/tests/unit_tests/test_spack_command_sequence_factory.py rename to dedal/tests/unit_tests/test_spack_command_sequence_factory.py index 9b6bc572b93ff8a36dd96848a6f77fcaf4c8ac84..0ffebcf3034236b5940ffbd529d5cb23ed9dc3c4 100644 --- a/esd/tests/unit_tests/test_spack_command_sequence_factory.py +++ b/dedal/tests/unit_tests/test_spack_command_sequence_factory.py @@ -8,10 +8,10 @@ import pytest -from esd.commands.command_enum import CommandEnum -from esd.commands.command_sequence import CommandSequence -from esd.commands.command_sequence_builder import CommandSequenceBuilder -from esd.commands.spack_command_sequence_factory import SpackCommandSequenceFactory +from dedal.commands.command_enum import CommandEnum +from dedal.commands.command_sequence import CommandSequence +from dedal.commands.command_sequence_builder import CommandSequenceBuilder +from dedal.commands.spack_command_sequence_factory import SpackCommandSequenceFactory class TestSpackCommandSequenceFactory: diff --git a/esd/tests/unit_tests/test_spack_operation.py b/dedal/tests/unit_tests/test_spack_operation.py similarity index 95% rename from esd/tests/unit_tests/test_spack_operation.py rename to dedal/tests/unit_tests/test_spack_operation.py index 4e2c520ee4105b9613dcee4b8b1e56963e2d8ec4..f053459c04e2f8f10206d570cbf3857f1ec1de3c 100644 --- a/esd/tests/unit_tests/test_spack_operation.py +++ b/dedal/tests/unit_tests/test_spack_operation.py @@ -9,10 +9,10 @@ import pytest from _pytest.fixtures import fixture -from esd.build_cache.BuildCacheManager import BuildCacheManager -from esd.commands.preconfigured_command_enum import PreconfiguredCommandEnum -from esd.error_handling.exceptions import NoSpackEnvironmentException -from esd.spack_factory.SpackOperation import SpackOperation +from dedal.build_cache.BuildCacheManager import BuildCacheManager +from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum +from dedal.error_handling.exceptions import NoSpackEnvironmentException +from dedal.spack_factory.SpackOperation import SpackOperation class TestSpackOperationAddMirrorWithComposite: @@ -20,8 +20,8 @@ class TestSpackOperationAddMirrorWithComposite: @fixture def mock_spack_operation(self, mocker): mocker.resetall() - mocker.patch("esd.spack_factory.SpackOperation.CommandRunner") - mocker.patch("esd.spack_factory.SpackOperation.get_logger") + mocker.patch("dedal.spack_factory.SpackOperation.CommandRunner") + mocker.patch("dedal.spack_factory.SpackOperation.get_logger") mock_object = SpackOperation() mock_object.logger = mocker.MagicMock() return mock_object diff --git a/esd/tests/unit_tests/test_spack_operation_use_cache.py b/dedal/tests/unit_tests/test_spack_operation_use_cache.py similarity index 88% rename from esd/tests/unit_tests/test_spack_operation_use_cache.py rename to dedal/tests/unit_tests/test_spack_operation_use_cache.py index fd4e277823d95fbcfc4b45ef426ee2721e891d09..e6e96f467e7c1ac3ce93cc5e7502e2826fe4002a 100644 --- a/esd/tests/unit_tests/test_spack_operation_use_cache.py +++ b/dedal/tests/unit_tests/test_spack_operation_use_cache.py @@ -5,20 +5,21 @@ # Description: Brief description of the file. # Created by: Murugan, Jithu <j.murugan@fz-juelich.de> # Created on: 2025-02-20 - +import logging from pathlib import Path import pytest -from esd.error_handling.exceptions import NoSpackEnvironmentException -from esd.spack_factory.SpackOperationUseCache import SpackOperationUseCache +from dedal.commands.command_runner import CommandRunner +from dedal.error_handling.exceptions import NoSpackEnvironmentException +from dedal.spack_factory.SpackOperationUseCache import SpackOperationUseCache @pytest.fixture def spack_operation_use_cache_mock(mocker): - super_mock = mocker.patch("esd.spack_factory.SpackOperationUseCache.super") + super_mock = mocker.patch("dedal.spack_factory.SpackOperationUseCache.super") super_mock.return_value.setup_spack_env = mocker.MagicMock() - mocker.patch("esd.spack_factory.SpackOperationUseCache.BuildCacheManager") + mocker.patch("dedal.spack_factory.SpackOperationUseCache.BuildCacheManager") mock_spack_operation_use_cache = SpackOperationUseCache() mock_spack_operation_use_cache.build_cache = mocker.MagicMock() mock_spack_operation_use_cache.spack_config = mocker.MagicMock() @@ -34,7 +35,7 @@ class TestSpackOperationUseCache: ("key_path_does_not_exist", False, None)]) def test_setup_spack_env(self, mocker, spack_operation_use_cache_mock, test_id, signed, key_path): # Test ID: setup_spack_env_success - super_mock = mocker.patch("esd.spack_factory.SpackOperationUseCache.super") + super_mock = mocker.patch("dedal.spack_factory.SpackOperationUseCache.super") spack_operation_use_cache_mock.trust_gpg_key = mocker.MagicMock() spack_operation_use_cache_mock.add_mirror = mocker.MagicMock() diff --git a/dedal/utils/bootstrap.sh b/dedal/utils/bootstrap.sh index 9b7d0131e95a3be9b0f2cfc4dc82492517fb22dc..58be94b97dd188f07ce0da8abfc4e2c57ec1210d 100644 --- a/dedal/utils/bootstrap.sh +++ b/dedal/utils/bootstrap.sh @@ -1,4 +1,4 @@ -# Minimal prerequisites for installing the esd_library +# Minimal prerequisites for installing the dedal_library # pip must be installed on the OS echo "Bootstrapping..." apt update diff --git a/esd/configuration/SpackConfig.py b/esd/configuration/SpackConfig.py deleted file mode 100644 index 26c6617fafad7c05c9374b2b84a8b3aff447a325..0000000000000000000000000000000000000000 --- a/esd/configuration/SpackConfig.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -from pathlib import Path -from esd.model import SpackDescriptor - - -class SpackConfig: - def __init__(self, env: SpackDescriptor = None, repos: list[SpackDescriptor] = None, - install_dir=Path(os.getcwd()).resolve(), upstream_instance=None, system_name=None, - concretization_dir: Path = None, buildcache_dir: Path = None): - self.env = env - self.repos = [] if repos is None else repos - self.install_dir = install_dir - self.upstream_instance = upstream_instance - self.system_name = system_name - self.concretization_dir = concretization_dir - self.buildcache_dir = buildcache_dir - - def add_repo(self, repo: SpackDescriptor): - if self.repos is None: - self.repos = [] - else: - self.repos.append(repo) diff --git a/esd/configuration/__init__.py b/esd/configuration/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/esd/error_handling/__init__.py b/esd/error_handling/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/esd/error_handling/exceptions.py b/esd/error_handling/exceptions.py deleted file mode 100644 index 0256f886ab0cf4b958ac12d59d6fcea2d5f568ec..0000000000000000000000000000000000000000 --- a/esd/error_handling/exceptions.py +++ /dev/null @@ -1,41 +0,0 @@ -class SpackException(Exception): - - def __init__(self, message): - super().__init__(message) - self.message = str(message) - - def __str__(self): - return self.message - - -class BashCommandException(SpackException): - """ - To be thrown when a bash command has failed - """ - - -class NoSpackEnvironmentException(BashCommandException): - """ - To be thrown when an operation on a spack environment is executed without the environment being activated or existent - """ - - -class SpackConcertizeException(BashCommandException): - """ - To be thrown when the spack concretization step fails - """ - -class SpackInstallPackagesException(BashCommandException): - """ - To be thrown when the spack fails to install spack packages - """ - -class SpackMirrorException(BashCommandException): - """ - To be thrown when the spack add mirror command fails - """ - -class SpackGpgException(BashCommandException): - """ - To be thrown when the spack fails to create gpg keys - """ diff --git a/esd/model/SpackDescriptor.py b/esd/model/SpackDescriptor.py deleted file mode 100644 index 70e484fb3d39e4333389682d14a32ac46c08a912..0000000000000000000000000000000000000000 --- a/esd/model/SpackDescriptor.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from pathlib import Path - - -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 - self.git_path = git_path diff --git a/esd/model/__init__.py b/esd/model/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/esd/spack_factory/SpackOperation.py b/esd/spack_factory/SpackOperation.py deleted file mode 100644 index 5a026283a36da8707bd1b33d524bcb7fd1961979..0000000000000000000000000000000000000000 --- a/esd/spack_factory/SpackOperation.py +++ /dev/null @@ -1,346 +0,0 @@ -import os -import re -import subprocess -from pathlib import Path - -from esd.commands.command_runner import CommandRunner -from esd.commands.preconfigured_command_enum import PreconfiguredCommandEnum -from esd.configuration.SpackConfig import SpackConfig -from esd.error_handling.exceptions import BashCommandException, NoSpackEnvironmentException, \ - SpackInstallPackagesException, SpackConcertizeException, SpackMirrorException, SpackGpgException -from esd.logger.logger_builder import get_logger -from esd.tests.testing_variables import SPACK_VERSION -from esd.utils.utils import run_command, git_clone_repo, log_command, set_bashrc_variable -from esd.wrapper.spack_wrapper import check_spack_env - - -class SpackOperation: - """ - This class should implement the methods necessary for installing spack, set up an environment, concretize and install packages. - Factory design pattern is used because there are 2 cases: creating an environment from scratch or creating an environment from the buildcache. - - Attributes: - ----------- - env : SpackDescriptor - spack environment details - repos : list[SpackDescriptor] - upstream_instance : str - path to Spack instance to use as upstream (optional) - """ - - def __init__(self, spack_config: SpackConfig = SpackConfig(), logger=get_logger(__name__)): - self.spack_config = spack_config - self.spack_config.install_dir.mkdir(parents=True, exist_ok=True) - self.spack_dir = self.spack_config.install_dir / 'spack' - self.spack_setup_script = self.spack_dir / 'share' / 'spack' / 'setup-env.sh' - self.logger = logger - if self.spack_config.env and spack_config.env.path: - self.spack_config.env.path = spack_config.env.path.resolve() - self.spack_config.env.path.mkdir(parents=True, exist_ok=True) - self.env_path = spack_config.env.path / spack_config.env.env_name - self.spack_command_on_env = f'source {self.spack_setup_script} && spack env activate -p {self.env_path}' - self.command_runner = CommandRunner() - - 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, - 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) - run_command("bash", "-c", - f'source {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", - exception=BashCommandException) - - def setup_spack_env(self): - """ - This method prepares a spack environment by fetching/creating the spack environment and adding the necessary repos - """ - bashrc_path = os.path.expanduser("~/.bashrc") - if self.spack_config.system_name: - set_bashrc_variable('SYSTEMNAME', self.spack_config.system_name, bashrc_path, logger=self.logger) - os.environ['SYSTEMNAME'] = self.spack_config.system_name - if self.spack_dir.exists() and self.spack_dir.is_dir(): - set_bashrc_variable('SPACK_USER_CACHE_PATH', str(self.spack_dir / ".spack"), bashrc_path, - logger=self.logger) - set_bashrc_variable('SPACK_USER_CONFIG_PATH', str(self.spack_dir / ".spack"), bashrc_path, - logger=self.logger) - self.logger.debug('Added env variables SPACK_USER_CACHE_PATH and SPACK_USER_CONFIG_PATH') - else: - self.logger.error(f'Invalid installation path: {self.spack_dir}') - # Restart the bash after adding environment variables - 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}') - else: - self.logger.debug(f'Spack repository {repo.env_name} already added') - - def spack_repo_exists(self, repo_name: str) -> bool | None: - """Check if the given Spack repository exists.""" - if self.spack_config.env is None: - result = run_command("bash", "-c", - f'source {self.spack_setup_script} && spack repo list', - check=True, - capture_output=True, text=True, logger=self.logger, - info_msg=f'Checking if {repo_name} exists') - if result is None: - return False - else: - if self.spack_env_exists(): - result = run_command("bash", "-c", - f'{self.spack_command_on_env} && spack repo list', - check=True, - capture_output=True, text=True, logger=self.logger, - info_msg=f'Checking if repository {repo_name} was added') - else: - self.logger.debug('No spack environment defined') - raise NoSpackEnvironmentException('No spack environment defined') - if result is None: - return False - return any(line.strip().endswith(repo_name) for line in result.stdout.splitlines()) - - def spack_env_exists(self): - result = run_command("bash", "-c", - 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') - return result is not None - - @check_spack_env - def add_spack_repo(self, repo_path: Path, repo_name: str): - """Add the Spack repository if it does not exist.""" - 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}", - exception=BashCommandException) - - @check_spack_env - def get_compiler_version(self): - result = run_command("bash", "-c", - 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}", - exception=BashCommandException) - # todo add error handling and tests - if result.stdout is None: - self.logger.debug('No gcc found for {self.env.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}') - return gcc_version - - def get_spack_installed_version(self): - spack_version = run_command("bash", "-c", f'source {self.spack_setup_script} && spack --version', - capture_output=True, text=True, check=True, - logger=self.logger, - info_msg=f"Getting spack version", - exception_msg=f"Error retrieving Spack version") - if spack_version: - return spack_version.stdout.strip().split()[0] - return None - - @check_spack_env - def concretize_spack_env(self, force=True): - force = '--force' if force else '' - run_command("bash", "-c", - 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}', - exception=SpackConcertizeException) - - def create_gpg_keys(self, gpg_name='example', gpg_mail='example@example.com'): - run_command("bash", "-c", - f'source {self.spack_setup_script} && spack gpg init && spack gpg create {gpg_name} {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}', - exception=SpackGpgException) - - def add_mirror(self, - mirror_name: str, - mirror_path: str, - signed=False, - autopush=False, - global_mirror=False) -> bool: - """Adds a Spack mirror. - - Adds a new mirror to the Spack configuration, either globally or to a specific environment. - - Args: - mirror_name (str): The name of the mirror. - mirror_path (str): The path or URL of the mirror. - signed (bool): Whether to require signed packages from the mirror. - autopush (bool): Whether to enable autopush for the mirror. - global_mirror (bool): Whether to add the mirror globally (True) or to the current environment (False). - - Returns: - True if the mirror was added successfully, False otherwise. - - Raises: - ValueError: If mirror_name or mirror_path are empty. - NoSpackEnvironmentException: If global_mirror is False and no environment is defined. - """ - if not mirror_name or not mirror_path: - raise ValueError("mirror_name and mirror_path are required") - if not global_mirror and not self.env_path: - raise NoSpackEnvironmentException('No spack environment defined') - result = self.command_runner.run_preconfigured_command_sequence( - PreconfiguredCommandEnum.SPACK_MIRROR_ADD, - self.spack_setup_script, - "" if global_mirror else self.env_path, - mirror_name, - mirror_path, - autopush, - signed) - return self.handle_result( - result, - "Failed to add mirror %s, reason: %s, output: %s", - (mirror_name,), - "Added mirror %s", - (mirror_name,) - ) - - def trust_gpg_key(self, public_key_path: str): - """Adds a GPG public key to the trusted keyring. - - This method attempts to add the provided GPG public key to the - Spack trusted keyring. - - Args: - public_key_path (str): Path to the GPG public key file. - - Returns: - bool: True if the key was added successfully, False otherwise. - - Raises: - ValueError: If public_key_path is empty. - """ - if not public_key_path: - raise ValueError("public_key_path is required") - result = self.command_runner.run_preconfigured_command_sequence( - PreconfiguredCommandEnum.SPACK_GPG_TRUST, - self.spack_setup_script, - public_key_path) - return self.handle_result( - result, - "Failed to add public key %s as trusted, reason: %s, output: %s", - (public_key_path,), - "Added public key %s as trusted", - (public_key_path,), - ) - - def handle_result(self, - result: dict[str, str | bool | None], - error_msg: str, - error_msg_args: tuple[str, ...], - info_msg: str, - info_msg_args: tuple[str, ...]): - """Handles the result of a command execution. - - Checks the success status of the result and logs either an error or an info message accordingly. - - Args: - result (dict[str, str | bool | None]): A dictionary containing the result of the command execution. - error_msg (str): The error message to log if the command failed. - error_msg_args (tuple[str, ...]): Arguments to format the error message. - info_msg (str): The info message to log if the command succeeded. - info_msg_args (tuple[str, ...]): Arguments to format the info message. - - Returns: - bool: True if the command succeeded, False otherwise. - """ - if not result["success"]: - self.logger.error(error_msg, *error_msg_args, result['error'], result['output']) - return False - self.logger.info(info_msg, *info_msg_args) - return True - - def remove_mirror(self, mirror_name: str): - run_command("bash", "-c", - f'source {self.spack_setup_script} && spack mirror rm {mirror_name}', - check=True, - logger=self.logger, - info_msg=f'Removing mirror {mirror_name}', - exception_msg=f'Failed to remove mirror {mirror_name}', - exception=SpackMirrorException) - - @check_spack_env - def install_packages(self, jobs: int, signed=True, fresh=False, debug=False): - signed = '' if signed else '--no-check-signature' - fresh = '--fresh' if fresh else '' - debug = '--debug' if debug else '' - install_result = run_command("bash", "-c", - f'{self.spack_command_on_env} && spack {debug} install -v {signed} --j {jobs} {fresh}', - stdout=subprocess.PIPE, - 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}", - exception=SpackInstallPackagesException) - log_command(install_result, str(Path(os.getcwd()).resolve() / ".generate_cache.log")) - return install_result - - def install_spack(self, spack_version=f'v{SPACK_VERSION}', spack_repo='https://github.com/spack/spack'): - try: - user = os.getlogin() - except OSError: - user = None - - self.logger.info(f"Starting to install Spack into {self.spack_dir} from branch {spack_version}") - if not self.spack_dir.exists(): - run_command( - "git", "clone", "--depth", "1", - "-c", "advice.detachedHead=false", - "-c", "feature.manyFiles=true", - "--branch", spack_version, spack_repo, self.spack_dir - , check=True, logger=self.logger) - self.logger.debug("Cloned spack") - else: - self.logger.debug("Spack already cloned.") - - bashrc_path = os.path.expanduser("~/.bashrc") - # ensure the file exists before opening it - if not os.path.exists(bashrc_path): - open(bashrc_path, "w").close() - # add spack setup commands_ to .bashrc - with open(bashrc_path, "a") as bashrc: - bashrc.write(f'export PATH="{self.spack_dir}/bin:$PATH"\n') - bashrc.write(f"source {self.spack_setup_script}\n") - self.logger.info("Added Spack PATH to .bashrc") - if user: - run_command("chown", "-R", f"{user}:{user}", self.spack_dir, check=True, logger=self.logger, - info_msg='Adding permissions to the logged in user') - run_command("bash", "-c", f"source {bashrc_path}", check=True, logger=self.logger, info_msg='Restart bash') - self.logger.info("Spack install completed") - # Restart Bash after the installation ends - os.system("exec bash") - - # Configure upstream Spack instance if specified - if self.spack_config.upstream_instance: - upstreams_yaml_path = os.path.join(self.spack_dir, "etc/spack/defaults/upstreams.yaml") - with open(upstreams_yaml_path, "w") as file: - file.write(f"""upstreams: - upstream-spack-instance: - install_tree: {self.spack_config.upstream_instance}/spack/opt/spack - """) - self.logger.info("Added upstream spack instance") diff --git a/esd/spack_factory/SpackOperationCreator.py b/esd/spack_factory/SpackOperationCreator.py deleted file mode 100644 index 8369c5ca0efb2865e706f1968882fc53a4e4d3d9..0000000000000000000000000000000000000000 --- a/esd/spack_factory/SpackOperationCreator.py +++ /dev/null @@ -1,14 +0,0 @@ -from esd.configuration.SpackConfig import SpackConfig -from esd.spack_factory.SpackOperation import SpackOperation -from esd.spack_factory.SpackOperationUseCache import SpackOperationUseCache - - -class SpackOperationCreator: - @staticmethod - def get_spack_operator(spack_config: SpackConfig = None): - if spack_config is None: - return SpackOperation(SpackConfig()) - elif spack_config.concretization_dir is None and spack_config.buildcache_dir is None: - return SpackOperation(spack_config) - else: - return SpackOperationUseCache(spack_config) diff --git a/esd/spack_factory/SpackOperationUseCache.py b/esd/spack_factory/SpackOperationUseCache.py deleted file mode 100644 index b6e7846cbd5791db5dda84ee3ba1e331b908ea2a..0000000000000000000000000000000000000000 --- a/esd/spack_factory/SpackOperationUseCache.py +++ /dev/null @@ -1,57 +0,0 @@ -import os - -from esd.build_cache.BuildCacheManager import BuildCacheManager -from esd.configuration.SpackConfig import SpackConfig -from esd.error_handling.exceptions import NoSpackEnvironmentException -from esd.logger.logger_builder import get_logger -from esd.spack_factory.SpackOperation import SpackOperation - - -class SpackOperationUseCache(SpackOperation): - """ - This class uses caching for the concretization step and for the installation step. - """ - - def __init__(self, spack_config: SpackConfig = SpackConfig(), cache_version_concretize='cache', - cache_version_build='cache'): - super().__init__(spack_config, logger=get_logger(__name__)) - self.cache_dependency = BuildCacheManager(os.environ.get('CONCRETIZE_OCI_HOST'), - os.environ.get('CONCRETIZE_OCI_PROJECT'), - os.environ.get('CONCRETIZE_OCI_USERNAME'), - os.environ.get('CONCRETIZE_OCI_PASSWORD'), - cache_version=cache_version_concretize) - self.build_cache = BuildCacheManager(os.environ.get('BUILDCACHE_OCI_HOST'), - os.environ.get('BUILDCACHE_OCI_PROJECT'), - os.environ.get('BUILDCACHE_OCI_USERNAME'), - os.environ.get('BUILDCACHE_OCI_PASSWORD'), - cache_version=cache_version_build) - - def setup_spack_env(self): - """Sets up the Spack environment using cached data. - - Downloads the build cache, trusts the cached public key (if available), - and adds the build cache as a local mirror. - """ - super().setup_spack_env() - try: - # Download build cache from OCI Registry and add public key to trusted keys - self.build_cache.download(self.spack_config.buildcache_dir) - cached_public_key = self.build_cache.get_public_key_from_cache(self.spack_config.buildcache_dir) - signed = cached_public_key is not None and self.trust_gpg_key(cached_public_key) - if not signed: - self.logger.warning("Public key not found in cache or failed to trust pgp keys!") - # Add build cache mirror - self.add_mirror('local_cache', - str(self.spack_config.buildcache_dir), - signed=signed, - autopush=True, - global_mirror=False) - except (ValueError, NoSpackEnvironmentException) as e: - self.logger.error("Error adding buildcache mirror: %s", e) - raise - - - - - def concretize_spack_env(self, force=True): - pass diff --git a/esd/spack_factory/__init__.py b/esd/spack_factory/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/esd/tests/testing_variables.py b/esd/tests/testing_variables.py deleted file mode 100644 index ab95bfa1e02658b30cef40bd6654915a51d33ad3..0000000000000000000000000000000000000000 --- a/esd/tests/testing_variables.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -ebrains_spack_builds_git = 'https://gitlab.ebrains.eu/ri/tech-hub/platform/esd/ebrains-spack-builds.git' -SPACK_VERSION = "0.22.0" -SPACK_ENV_ACCESS_TOKEN = os.getenv("SPACK_ENV_ACCESS_TOKEN") -test_spack_env_git = f'https://oauth2:{SPACK_ENV_ACCESS_TOKEN}@gitlab.ebrains.eu/ri/projects-and-initiatives/virtualbraintwin/tools/test-spack-env.git' diff --git a/esd/tests/utils_test.py b/esd/tests/utils_test.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/esd/utils/bootstrap.sh b/esd/utils/bootstrap.sh deleted file mode 100644 index 9b7d0131e95a3be9b0f2cfc4dc82492517fb22dc..0000000000000000000000000000000000000000 --- a/esd/utils/bootstrap.sh +++ /dev/null @@ -1,6 +0,0 @@ -# Minimal prerequisites for installing the esd_library -# pip must be installed on the OS -echo "Bootstrapping..." -apt update -apt install -y bzip2 ca-certificates g++ gcc gfortran git gzip lsb-release patch python3 python3-pip tar unzip xz-utils zstd -python3 -m pip install --upgrade pip setuptools wheel diff --git a/esd/utils/utils.py b/esd/utils/utils.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/esd/wrapper/__init__.py b/esd/wrapper/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/esd/wrapper/spack_wrapper.py b/esd/wrapper/spack_wrapper.py deleted file mode 100644 index c2f9c116abb782eae1c22ebad4b8dae073c5acac..0000000000000000000000000000000000000000 --- a/esd/wrapper/spack_wrapper.py +++ /dev/null @@ -1,15 +0,0 @@ -import functools - -from esd.error_handling.exceptions import NoSpackEnvironmentException - - -def check_spack_env(method): - @functools.wraps(method) - def wrapper(self, *args, **kwargs): - if self.spack_env_exists(): - return method(self, *args, **kwargs) # Call the method with 'self' - else: - self.logger.debug('No spack environment defined') - raise NoSpackEnvironmentException('No spack environment defined') - - return wrapper diff --git a/pyproject.toml b/pyproject.toml index 62d0146c3c48b233ed29040756d2bf76731fb694..6aff098253e79353ccebb64c9cdb3ef2df9d80e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,10 +22,11 @@ dependencies = [ [tool.setuptools.data-files] "dedal" = ["dedal/logger/logging.conf"] -[options.extras_require] +[project.optional-dependencies] test = [ "pytest", "pytest-mock", "pytest-ordering", "coverage" -] \ No newline at end of file +] +dev = ["mypy", "pylint", "black", "flake8"] \ No newline at end of file