diff --git a/dedal/build_cache/BuildCacheManager.py b/dedal/build_cache/BuildCacheManager.py index 62cb9af14bef184c87c4d6ffa81c697a272d7036..3b96cd09ebe89f07c68250f20a525771575eefaa 100644 --- a/dedal/build_cache/BuildCacheManager.py +++ b/dedal/build_cache/BuildCacheManager.py @@ -34,11 +34,11 @@ class BuildCacheManager(BuildCacheManagerInterface): self.cache_version = cache_version self._oci_registry_path = f'{self._registry_host}/{self._registry_project}/{self.cache_version}' - def upload(self, out_dir: Path): + def upload(self, upload_dir: Path): """ This method pushed all the files from the build cache folder into the OCI Registry """ - build_cache_path = out_dir.resolve() + build_cache_path = upload_dir.resolve() # build cache folder must exist before pushing all the artifacts if not build_cache_path.exists(): self._logger.error(f"Path {build_cache_path} not found.") @@ -73,11 +73,11 @@ class BuildCacheManager(BuildCacheManagerInterface): self._logger.error(f"Failed to list tags: {e}") return None - def download(self, in_dir: Path): + def download(self, download_dir: Path): """ This method pulls all the files from the OCI Registry into the build cache folder """ - build_cache_path = in_dir.resolve() + build_cache_path = download_dir.resolve() # create the buildcache dir if it does not exist os.makedirs(build_cache_path, exist_ok=True) tags = self.list_tags() diff --git a/dedal/commands/__init__.py b/dedal/commands/__init__.py deleted file mode 100644 index ea9c384e46ebd5602957ff010a7c2b7c2d7d52e6..0000000000000000000000000000000000000000 --- a/dedal/commands/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: __init__.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 - diff --git a/dedal/commands/bash_command_executor.py b/dedal/commands/bash_command_executor.py deleted file mode 100644 index aef9c57659070d31936ad71967e5ec21e937bdc6..0000000000000000000000000000000000000000 --- a/dedal/commands/bash_command_executor.py +++ /dev/null @@ -1,100 +0,0 @@ -""" Bash Command Executor module. """ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: bash_command_executor.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 -import logging -import subprocess -from logging import Logger -from os import name as os_name - -from dedal.commands.command import Command -from dedal.commands.command_sequence import CommandSequence - - -class BashCommandExecutor: - """Executes commands in a Bash shell. - - Manages a sequence of commands and executes them in a single Bash session, - handling potential errors during execution. - """ - - def __init__(self) -> None: - self.logger: Logger = logging.getLogger(__name__) - self.sequence: CommandSequence = CommandSequence() - command_map: dict[str, list[str]] = { - "nt": ["wsl", "bash", "-c"], # Works only if wsl is installed on windows - "posix": ["bash", "-c"], - } - self.bash_command: list[str] = command_map.get(os_name, ["undefined"]) - - def add_command(self, command: Command) -> None: - """Adds a command to the sequence. - - Appends the given command to the internal sequence of commands to be executed. - - Args: - command (Command): The command to add. - - Raises: - ValueError: If the command is not an instance of the `Command` class. - """ - if not isinstance(command, Command): - raise ValueError("Invalid command type. Use Command.") - self.logger.info("Adding command to the sequence: %s", command) - self.sequence.add_command(command) - - def execute(self) -> tuple[str | None, str | None]: - """Executes all commands in a single Bash session. - - Runs the accumulated commands in a Bash shell and returns the output. - Handles various potential errors during execution. The execution is time - - Returns (tuple[str | None, str | None]): - A tuple containing the output and error message (if any). - output will be None if an error occurred, and the error message will - contain details about the error. - - Raises: - ValueError: If no commands have been added to the sequence. - """ - if not self.sequence.commands: - raise ValueError("No commands to execute.") - try: - result = subprocess.run( - [*self.bash_command, self.sequence.execute()], - capture_output=True, - text=True, - check=True, - timeout=172800 # Given a default timeout of 48 hours - ) - self.logger.info("Successfully executed command sequence, output: %s", result.stdout) - return result.stdout, None - except FileNotFoundError as e: - error = f"Error: Bash Command: {self.bash_command} not found: {e}" - except subprocess.CalledProcessError as e: - error = (f"Error: Command failed with exit code " - f"{e.returncode}, Error Output: {e.stderr}") - except PermissionError as e: - error = f"Error: Permission denied: {e}" - except OSError as e: - error = f"Error: OS error occurred: {e}" - except ValueError as e: - error = f"Error: Invalid argument passed: {e}" - except TypeError as e: - error = f"Error: Invalid type for arguments: {e}" - except subprocess.TimeoutExpired as e: - error = f"Error: Command timed out after {e.timeout} seconds" - 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/dedal/commands/command.py b/dedal/commands/command.py deleted file mode 100644 index 07e9837c142122ff43a5fcd47959a12d7ab0f28f..0000000000000000000000000000000000000000 --- a/dedal/commands/command.py +++ /dev/null @@ -1,29 +0,0 @@ -""" Command module. """ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: command.py -# Description: Abstract base class for executable commands -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 - -from abc import ABC, abstractmethod - - -class Command(ABC): - """Abstract base class for executable commands. - - Provides a common interface for defining and executing commands. - Subclasses must implement the `execute` method. - """ - - @abstractmethod - def execute(self) -> str: - """Executes the command. - - This method must be implemented by subclasses to define the specific - behavior of the command. - - Returns: - str: The result of the command execution. - """ diff --git a/dedal/commands/command_enum.py b/dedal/commands/command_enum.py deleted file mode 100644 index 7ef184cbfab3e193c292f7fcbdbedcb7a5783211..0000000000000000000000000000000000000000 --- a/dedal/commands/command_enum.py +++ /dev/null @@ -1,37 +0,0 @@ -""" Command Enum module. """ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: command_enum.py -# Description: Enumeration of supported commands in command registry -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 - -from enum import Enum - - -class CommandEnum(Enum): - """Enumeration of supported commands. - - Provides a standardized way to refer to different command types, - including Linux commands and Spack commands. - """ - # Linux commands_ - - SOURCE = "source" - LIST_FILES = "list_files" - SHOW_DIRECTORY = "show_directory" - FIND_IN_FILE = "find_in_file" - ECHO_MESSAGE = "echo_message" - CHANGE_DIRECTORY = "change_directory" - - # Spack commands_ - SPACK_COMPILER_FIND = "spack_compiler_find" - SPACK_COMPILERS = "spack_compilers" - SPACK_COMPILER_INFO = "spack_compiler_info" - SPACK_COMPILER_LIST = "spack_compiler_list" - SPACK_ENVIRONMENT_ACTIVATE = "spack_environment_activate" - SPACK_FIND = "spack_find" - SPACK_INSTALL = "spack_install" - SPACK_MIRROR_ADD = "spack_mirror_add" - SPACK_GPG_TRUST = "spack_gpg_trust" diff --git a/dedal/commands/command_registry.py b/dedal/commands/command_registry.py deleted file mode 100644 index adaa3d6eb95119f3e028ea05fe7640f9145cdb5f..0000000000000000000000000000000000000000 --- a/dedal/commands/command_registry.py +++ /dev/null @@ -1,59 +0,0 @@ -""" Command Registry module. """ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: command_registry.py -# Description: Registry for storing and retrieving shell commands with placeholders -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 - -from dedal.commands.command_enum import CommandEnum - - -class CommandRegistry: - """Registry for storing and retrieving commands. - - Holds a dictionary of commands, keyed by `CommandEnum` members, - allowing easy access to command definitions. The generic shell command factory uses this registry for constructing the shell commands. - - The commands are stored in a dictionary where the keys or command names are `CommandEnum` members, and the values corresponding shell commands stored in a list. - The placeholders `{}` in the commands are replaced with the actual values when the command is executed. - - Extend this registry if you want to add more commands and do the necessary mapping in command_runner module. - """ - - COMMANDS: dict[CommandEnum, list[str]] = { - # Linux commands_ - CommandEnum.LIST_FILES: ["ls", "-l", "{folder_location}"], - CommandEnum.SHOW_DIRECTORY: ["pwd"], - CommandEnum.FIND_IN_FILE: ["grep", "-i", "{search_term}", "{file_path}"], - CommandEnum.ECHO_MESSAGE: ["echo", "{message}"], - CommandEnum.SOURCE: ["source", "{file_path}"], - CommandEnum.CHANGE_DIRECTORY: ["cd", "{folder_location}"], - - # Spack commands_ - CommandEnum.SPACK_COMPILER_FIND: ["spack", "compiler", "find", "{compiler_name}"], - CommandEnum.SPACK_COMPILERS: ["spack", "compilers"], - CommandEnum.SPACK_COMPILER_LIST: ["spack", "compiler", "list"], - CommandEnum.SPACK_COMPILER_INFO: ["spack", "compiler", "info", "{compiler_name_with_version}"], - CommandEnum.SPACK_ENVIRONMENT_ACTIVATE: ["spack", "env", "activate", "-p", "{env_path}"], - CommandEnum.SPACK_FIND: ["spack", "find", "{package_name_with_version}"], - CommandEnum.SPACK_INSTALL: ["spack", "install", "{package_name_with_version}"], - CommandEnum.SPACK_MIRROR_ADD: ["spack", "mirror", "add", "{autopush}", "{signed}", "{mirror_name}", - "{mirror_path}"], - CommandEnum.SPACK_GPG_TRUST: ["spack", "gpg", "trust", "{public_key_path}"] - } - - @classmethod - def get_command(cls, command_name: CommandEnum) -> list[str] | None: - """Retrieve a command from the registry. - - Returns the command definition associated with the given `CommandEnum` member. - - Args: - command_name (CommandEnum): The name of the command to retrieve. - - Returns (list[str]): - The shell command in a list format, or None if the command is not found. - """ - return cls.COMMANDS.get(command_name) diff --git a/dedal/commands/command_sequence.py b/dedal/commands/command_sequence.py deleted file mode 100644 index 6115215a86a5b9d8fef5d3ea5af37f25210652ad..0000000000000000000000000000000000000000 --- a/dedal/commands/command_sequence.py +++ /dev/null @@ -1,54 +0,0 @@ -""" Command Sequence module. """ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: command_sequence.py -# Description: Command Sequence abstraction -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 - -from dedal.commands.command import Command - - -class CommandSequence(Command): - """Represents a sequence of executable commands. - - Allows adding commands to a sequence and executing them in order, - combining/chaining their execution strings with "&&". - """ - - def __init__(self) -> None: - """Initializes an empty command sequence.""" - self.commands: list[Command] = [] - - def add_command(self, command: Command) -> None: - """Adds a command to the sequence. - - Appends the given command to the list of commands. - - Args: - command (Command): The command to add. - - Raises: - ValueError: If the provided command is not an instance of `Command`. - """ - if not isinstance(command, Command): - raise ValueError("Command must be an instance of Command") - self.commands.append(command) - - def execute(self) -> str: - """Executes the command sequence. - - Executes each command in the sequence and joins their results with "&&". - - Returns: - 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/dedal/commands/command_sequence_builder.py b/dedal/commands/command_sequence_builder.py deleted file mode 100644 index bd35505772d8d618b17a7e4a81b8d6c7698ef91c..0000000000000000000000000000000000000000 --- a/dedal/commands/command_sequence_builder.py +++ /dev/null @@ -1,71 +0,0 @@ -""" Command Sequence Builder module. """ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: command_sequence_builder.py -# Description: Command sequence builder module for creating command sequences -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-19 -from __future__ import annotations - -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: - """Builds a sequence of commands using the builder pattern. - - Facilitates the creation of CommandSequence objects by adding commands incrementally - and then building the final sequence. - """ - - def __init__(self) -> None: - """Initializes a new CommandSequenceBuilder with an empty sequence.""" - self.sequence: CommandSequence = CommandSequence() - - def add_generic_command(self, - command_name: CommandEnum, - placeholders: dict[str, str]) -> CommandSequenceBuilder: - """Adds a generic command to the sequence. - - Retrieves the command definition from the CommandRegistry, replaces placeholders with - provided values, creates a ShellCommand, and adds it to the sequence. - - Args: - command_name (CommandEnum): The enum representing the command name to add. - placeholders (dict[str, str]): A dictionary of placeholder values to substitute in the command. - - Returns: - The CommandSequenceBuilder instance (self) for method chaining. - - Raises: - ValueError: If the command type is invalid or if the command is unknown. - """ - if not isinstance(command_name, CommandEnum): - raise ValueError("Invalid command type. Use CommandEnum.") - command = CommandRegistry.get_command(command_name) - - if command is None: - raise ValueError(f"Unknown command: {command_name}") - full_command = command[:] # Copy the command list to avoid mutation - # Replace placeholders with actual values - if placeholders: - full_command = [placeholders.get(arg.strip("{}"), arg) for arg in full_command] - full_command = list(filter(None, full_command)) - shell_command = ShellCommandFactory.create_command(*full_command) - self.sequence.add_command(shell_command) - return self - - def build(self) -> CommandSequence: - """Builds and returns the CommandSequence. - - Returns the constructed CommandSequence and resets the builder for creating new sequences. - - Returns: - The built CommandSequence. - """ - sequence = self.sequence - self.sequence = CommandSequence() # Reset for next build - return sequence diff --git a/dedal/commands/command_sequence_factory.py b/dedal/commands/command_sequence_factory.py deleted file mode 100644 index 1c1872451b4ec41950ce7138246996f16018b6e5..0000000000000000000000000000000000000000 --- a/dedal/commands/command_sequence_factory.py +++ /dev/null @@ -1,46 +0,0 @@ -""" Command Sequence Factory module. """ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: command_sequence_factory.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 - -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: - """Creates a custom CommandSequence. - - Builds a CommandSequence from a dictionary mapping CommandEnums to placeholder values. - - For example if the key command is `CommandEnum.FIND_IN_FILE` and the value is `{search_term: 'python', file_path: '/path/to/file.txt'}`, - this corresponds to the command `grep -i {search_term} {file_path}` in CommandRegistry.COMMANDS. So the user can create a sequence of such commands. - - e.g. command_placeholders_map = { - CommandEnum.FIND_IN_FILE: { - "search_term": "python", - "file_path": "/path/to/file.txt" - }, - CommandEnum.SHOW_DIRECTORY: {}, - CommandEnum.LIST_FILES: {"folder_location": "/tmp"}, - CommandEnum.ECHO_MESSAGE: {"message": "Hello, world!"} - } - - Args: - command_placeholders_map: A dictionary mapping CommandEnum members to dictionaries of placeholder values. - Returns: - A CommandSequence object representing the custom command sequence. - """ - builder = CommandSequenceBuilder() - for command_type, placeholders in command_placeholders_map.items(): - builder.add_generic_command(command_type, placeholders) - return builder.build() diff --git a/dedal/commands/generic_shell_command.py b/dedal/commands/generic_shell_command.py deleted file mode 100644 index 0a02b09537722087a93ab43a211b20e1c82750e9..0000000000000000000000000000000000000000 --- a/dedal/commands/generic_shell_command.py +++ /dev/null @@ -1,47 +0,0 @@ -""" Generic Shell Command module. """ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: generic_shell_command.py -# Description: Generic shell command implementation -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 - -from dedal.commands.command import Command - - -class GenericShellCommand(Command): - """Represents a generic shell 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. - - Args: - command_name (str): The name of the command. - *args (str): The arguments for the command. - - Raises: - ValueError: If the command name is empty, not a string, or if any of the arguments are not strings. - """ - if not command_name: - raise ValueError("Command name is required!") - if not isinstance(command_name, str): - raise ValueError("Command name must be a string!") - if not all(isinstance(arg, str) for arg in args): - raise ValueError("All arguments must be strings!") - self.args: tuple[str, ...] = tuple(map(str.strip, args)) - self.command_name: str = command_name.strip() - - def execute(self) -> str: - """Executes the command. - - Constructs and returns the full command string, including the command name and arguments. - - Returns: - The full command string. - """ - return f"{self.command_name} {' '.join(self.args)}" if self.args else self.command_name diff --git a/dedal/commands/shell_command_factory.py b/dedal/commands/shell_command_factory.py deleted file mode 100644 index e63a456e201903a8ebf6a4885a4e230c509e97f6..0000000000000000000000000000000000000000 --- a/dedal/commands/shell_command_factory.py +++ /dev/null @@ -1,33 +0,0 @@ -""" Shell Command Factory module. """ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: shell_command_factory.py -# Description: Shell command factory to create shell command instances -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 - -from dedal.commands.command import Command -from dedal.commands.generic_shell_command import GenericShellCommand - - -class ShellCommandFactory: - """Factory for creating shell command instances. - - Provides a static method for creating GenericShellCommand objects. - """ - - @staticmethod - def create_command(command_name: str, *args: str) -> Command: - """Creates a generic shell command. - - Instantiates and returns a GenericShellCommand object with the given command name and arguments. - - Args: - command_name (str): The name of the command. - *args (str): The arguments for the command. - - Returns: - A GenericShellCommand object. - """ - return GenericShellCommand(command_name, *args) diff --git a/dedal/commands/spack_command_sequence_factory.py b/dedal/commands/spack_command_sequence_factory.py deleted file mode 100644 index ce7afac388e152412842ba448a9f17c126f90513..0000000000000000000000000000000000000000 --- a/dedal/commands/spack_command_sequence_factory.py +++ /dev/null @@ -1,211 +0,0 @@ -"""Factory for generating predefined spack related command sequences.""" -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: spack_command_sequence_factory.py -# Description: Factory for generating predefined spack related command sequences -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-17 - -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: - """Formats a name and version string. - - Returns a string combining the name and version with "@" if both are provided, - otherwise returns the name or an empty string. - - Args: - name (str): The name. - version (str): The version. - - Returns: - The formatted name and version string. - """ - return f"{name}@{version}" if name and version else name or "" - - -class SpackCommandSequenceFactory: - """Factory for creating Spack command sequences. - - Provides methods for building CommandSequence objects for various Spack operations. - """ - - @staticmethod - def create_spack_enabled_command_sequence_builder(spack_setup_script: str) -> CommandSequenceBuilder: - """Creates a CommandSequenceBuilder with Spack setup. - - Initializes a builder with the 'source' command for the given Spack setup script. - - Args: - spack_setup_script (str): Path to the Spack setup script. - - Returns: - A CommandSequenceBuilder pre-configured with the Spack setup command. - """ - return (CommandSequenceBuilder() - .add_generic_command(CommandEnum.SOURCE, {"file_path": spack_setup_script})) - - @staticmethod - def create_spack_compilers_command_sequence(spack_setup_script: str) -> CommandSequence: - """Creates a command sequence for listing Spack compilers. - - Builds a sequence that sources the Spack setup script and then lists available compilers. - - Args: - spack_setup_script (str): Path to the Spack setup script. - - Returns: - A CommandSequence for listing Spack compilers. - """ - return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script) - .add_generic_command(CommandEnum.SPACK_COMPILERS, {}) - .build()) - - @staticmethod - def create_spack_compiler_list_command_sequence(spack_setup_script: str) -> CommandSequence: - """Creates a command sequence for listing Spack compilers. - - Builds a sequence that sources the Spack setup script and then lists available compilers. - - Args: - spack_setup_script (str): Path to the Spack setup script. - - Returns: - A CommandSequence for listing Spack compilers. - """ - return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script) - .add_generic_command(CommandEnum.SPACK_COMPILER_LIST, {}) - .build()) - - @staticmethod - def create_spack_install_package_command_sequence(spack_setup_script: str, - package_name: str | None, - version: str | None) -> CommandSequence: - """Creates a command sequence for installing a Spack package. - - Builds a sequence that sources the Spack setup script and then installs the specified package. - - Args: - spack_setup_script (str): Path to the Spack setup script. - package_name (str | None): The name of the package to install. - version (str | None): The version of the package to install. - - Returns: - A CommandSequence for installing the Spack package. - """ - return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script) - .add_generic_command(CommandEnum.SPACK_INSTALL, { - "package_name_with_version": get_name_version(package_name, version) - }).build()) - - @staticmethod - def create_spack_mirror_add_command_sequence(spack_setup_script: str, - env_name: str, - mirror_name: str, - mirror_path: str, - autopush: bool = False, - signed: bool = False) -> CommandSequence: - """Creates a command sequence for adding a Spack mirror. - - Builds a sequence that sources the Spack setup script, activates an environment (if specified), - and adds the given mirror. - - Args: - spack_setup_script (str): Path to the Spack setup script. - env_name (str): The name of the environment to activate. - mirror_name (str): The name of the mirror. - mirror_path (str): The URL or path of the mirror. - autopush (bool): Whether to enable autopush for the mirror. - signed (bool): Whether to require signed packages from the mirror. - - Returns: - A CommandSequence for adding the Spack mirror. - """ - builder = SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script) - if env_name: - builder = builder.add_generic_command(CommandEnum.SPACK_ENVIRONMENT_ACTIVATE, - {"env_path": env_name}) - place_holders = { - "mirror_name": mirror_name, - "mirror_path": mirror_path, - "autopush": "--autopush" if autopush else "", - "signed": "--signed" if signed else "" - } - builder = builder.add_generic_command(CommandEnum.SPACK_MIRROR_ADD, place_holders) - return builder.build() - - @staticmethod - def create_spack_compiler_info_command_sequence(spack_setup_script: str, - compiler_name: str, - compiler_version: str) -> CommandSequence: - """Creates a command sequence for getting Spack compiler information. - - Builds a sequence that sources the Spack setup script and retrieves information about the specified compiler. - - - Args: - spack_setup_script (str): Path to the Spack setup script. - compiler_name (str): The name of the compiler. - compiler_version (str): The version of the compiler. - - Returns: - A CommandSequence for getting compiler information. - """ - return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script) - .add_generic_command(CommandEnum.SPACK_COMPILER_INFO, - { - "compiler_name_with_version": - get_name_version(compiler_name, compiler_version) - }) - .build()) - - @staticmethod - def create_spack_post_install_find_command_sequence(spack_setup_script: str, - env_path: str, - package_name: str | None, - version: str | None) -> CommandSequence: - """Creates a command sequence for finding installed Spack packages after installation. - - Builds a sequence that sources the Spack setup script, activates the specified environment, - and then searches for the given package. - - Args: - spack_setup_script (str): Path to the Spack setup script. - env_path (str): The path to the Spack environment. - package_name (str | None): The name of the package to find. - version (str | None): The version of the package to find. - - Returns: - A CommandSequence for finding installed Spack packages. - """ - return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script) - .add_generic_command(CommandEnum.SPACK_ENVIRONMENT_ACTIVATE, {"env_path": env_path}) - .add_generic_command(CommandEnum.SPACK_FIND, - { - "package_name_with_version": - get_name_version(package_name, version) - }).build()) - - @staticmethod - def create_spack_gpg_trust_command_sequence(spack_setup_script: str, - public_key_path: str) -> CommandSequence: - """Creates a command sequence for trusting a GPG key in Spack. - - Builds a sequence that sources the Spack setup script and then trusts the given GPG key. - - Args: - spack_setup_script (str): Path to the Spack setup script. - public_key_path (str): Path to the public key file. - - Returns: - A CommandSequence for trusting the GPG key. - """ - return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script) - .add_generic_command(CommandEnum.SPACK_GPG_TRUST, - { - "public_key_path": public_key_path - }).build()) diff --git a/dedal/spack_factory/SpackOperation.py b/dedal/spack_factory/SpackOperation.py index ad4f2389edc1c278cfe91eb166be0dc51f60757a..b9fd6e791b031c88cd072509a53fbfcb8db96e63 100644 --- a/dedal/spack_factory/SpackOperation.py +++ b/dedal/spack_factory/SpackOperation.py @@ -2,12 +2,10 @@ 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, SpackRepoException + SpackInstallPackagesException, SpackConcertizeException, SpackMirrorException, SpackGpgException, \ + SpackRepoException from dedal.logger.logger_builder import get_logger from dedal.tests.testing_variables import SPACK_VERSION from dedal.utils.utils import run_command, git_clone_repo, log_command, set_bashrc_variable @@ -50,7 +48,6 @@ class SpackOperation: 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.command_runner = CommandRunner() def create_fetch_spack_environment(self): if self.spack_config.env.git_path: @@ -189,106 +186,80 @@ class SpackOperation: else: raise SpackGpgException('No GPG configuration was defined is spack configuration') - def add_mirror(self, - mirror_name: str, - mirror_path: str, - signed=False, - autopush=False, - global_mirror=False) -> bool: + def add_mirror(self, mirror_name: str, mirror_path: Path, signed=False, autopush=False, global_mirror=False): """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 str(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,) - ) + autopush = '--autopush' if autopush else '' + signed = '--signed' if signed else '' + spack_add_mirror = f'spack mirror add {autopush} {signed} {mirror_name} {mirror_path}' + if global_mirror: + run_command("bash", "-c", + f'{self.spack_setup_script} {spack_add_mirror}', + 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_add_mirror}', + check=True, + logger=self.logger, + info_msg=f'Added mirror {mirror_name}', + exception_msg=f'Failed to add mirror {mirror_name}', + exception=SpackMirrorException)) 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. + run_command("bash", "-c", + f'{self.spack_command_on_env} && spack gpg trust {public_key_path}', + check=True, + logger=self.logger, + info_msg=f'Trusted GPG key for {self.spack_config.env.name}', + exception_msg=f'Failed to trust GPG key for {self.spack_config.env.name}', + exception=SpackGpgException) - 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 list_mirrors(self): + mirrors = run_command("bash", "-c", + f'{self.spack_setup_script} spack mirror list', + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + logger=self.logger, + info_msg=f'Listing mirrors', + exception_msg=f'Failed list mirrors', + exception=SpackMirrorException) + return list(mirrors.stdout.strip().splitlines()) def remove_mirror(self, mirror_name: str): + if not mirror_name: + raise ValueError("mirror_name is required") run_command("bash", "-c", f'{self.spack_setup_script} spack mirror rm {mirror_name}', check=True, diff --git a/dedal/spack_factory/SpackOperationUseCache.py b/dedal/spack_factory/SpackOperationUseCache.py index 16312e7fd3af19e4ded58eb606f2b1861dedde09..f1feb046a7ab2220a22fbeff8a2d6886f0722978 100644 --- a/dedal/spack_factory/SpackOperationUseCache.py +++ b/dedal/spack_factory/SpackOperationUseCache.py @@ -60,6 +60,13 @@ class SpackOperationUseCache(SpackOperation): @check_spack_env def concretize_spack_env(self): + """Concretization step for spack environment for using the concretization cache (spack.lock file). + + Downloads the concretization cache and moves it to the spack environment's folder + + Raises: + NoSpackEnvironmentException: If the spack environment is not set up. + """ concretization_redo = False self.cache_dependency.download(self.spack_config.concretization_dir) if file_exists_and_not_empty(self.spack_config.concretization_dir / 'spack.lock'): @@ -77,6 +84,11 @@ class SpackOperationUseCache(SpackOperation): @check_spack_env def install_packages(self, jobs: int, signed=True, debug=False): + """Installation step for spack environment for using the binary caches. + + Raises: + NoSpackEnvironmentException: If the spack environment is not set up. + """ signed = '' if signed else '--no-check-signature' debug = '--debug' if debug else '' install_result = run_command("bash", "-c", diff --git a/dedal/tests/integration_tests/spack_from_cache_test.py b/dedal/tests/integration_tests/spack_from_cache_test.py index 33f4483314b04e894c0bca3c75f259d58031d4f4..d0e390de575c34e4f220d0e6ed3642bfde13c0e5 100644 --- a/dedal/tests/integration_tests/spack_from_cache_test.py +++ b/dedal/tests/integration_tests/spack_from_cache_test.py @@ -1,12 +1,30 @@ -import pytest from dedal.configuration.SpackConfig import SpackConfig from dedal.model.SpackDescriptor import SpackDescriptor from dedal.spack_factory.SpackOperationCreator import SpackOperationCreator from dedal.spack_factory.SpackOperationUseCache import SpackOperationUseCache -from dedal.utils.utils import file_exists_and_not_empty +from dedal.utils.utils import file_exists_and_not_empty, count_files_in_folder from dedal.utils.variables import test_spack_env_git, ebrains_spack_builds_git +def test_spack_from_cache_setup(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) + concretization_dir = install_dir / 'concretize' + buildcache_dir = install_dir / 'buildcache' + spack_config = SpackConfig(env, install_dir=install_dir, concretization_dir=concretization_dir, + buildcache_dir=buildcache_dir) + spack_config.add_repo(repo) + spack_operation = SpackOperationCreator.get_spack_operator(spack_config, use_cache=True) + assert isinstance(spack_operation, SpackOperationUseCache) + spack_operation.install_spack() + spack_operation.setup_spack_env() + num_tags = len(spack_operation.build_cache.list_tags()) + assert file_exists_and_not_empty(concretization_dir) == True + assert count_files_in_folder(buildcache_dir) == num_tags + + + def test_spack_from_cache_concretize(tmp_path): install_dir = tmp_path env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git) @@ -23,7 +41,6 @@ def test_spack_from_cache_concretize(tmp_path): assert file_exists_and_not_empty(concretization_file_path) == True -@pytest.mark.skip(reason="Skipping test::test_spack_from_cache_install until all the functionalities in SpackOperationUseCache") def test_spack_from_cache_install(tmp_path): install_dir = tmp_path env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git) diff --git a/dedal/tests/spack_from_scratch_test.py b/dedal/tests/spack_from_scratch_test.py deleted file mode 100644 index 2fec80f743d72190d0b175191660250981f98255..0000000000000000000000000000000000000000 --- a/dedal/tests/spack_from_scratch_test.py +++ /dev/null @@ -1,204 +0,0 @@ -from pathlib import Path -import pytest -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(): - spack_operation = SpackOperationCreator.get_spack_operator() - spack_operation.install_spack() - assert spack_operation.spack_repo_exists('ebrains-spack-builds') == False - - -def test_spack_repo_exists_2(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('ebrains-spack-builds', install_dir) - config = SpackConfig(env=env, install_dir=install_dir) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - with pytest.raises(NoSpackEnvironmentException): - spack_operation.spack_repo_exists(env.env_name) - - -def test_spack_repo_exists_3(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('ebrains-spack-builds', install_dir) - config = SpackConfig(env=env, install_dir=install_dir) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - print(spack_operation.get_spack_installed_version()) - spack_operation.setup_spack_env() - assert spack_operation.spack_repo_exists(env.env_name) == False - - -def test_spack_from_scratch_setup_1(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git) - config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - assert spack_operation.spack_repo_exists(env.env_name) == False - - -def test_spack_from_scratch_setup_2(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git) - repo = env - config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir) - config.add_repo(repo) - config.add_repo(repo) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - assert spack_operation.spack_repo_exists(env.env_name) == True - - -def test_spack_from_scratch_setup_3(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('new_env1', install_dir) - repo = env - config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir) - config.add_repo(repo) - config.add_repo(repo) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - with pytest.raises(BashCommandException): - spack_operation.setup_spack_env() - - -def test_spack_from_scratch_setup_4(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('new_env2', install_dir) - config = SpackConfig(env=env, install_dir=install_dir) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - assert spack_operation.spack_env_exists() == True - - -def test_spack_not_a_valid_repo(): - env = SpackDescriptor('ebrains-spack-builds', Path(), None) - repo = env - config = SpackConfig(env=env, system_name='ebrainslab') - config.add_repo(repo) - spack_operation = SpackOperationCreator.get_spack_operator(config) - with pytest.raises(BashCommandException): - spack_operation.add_spack_repo(repo.path, repo.env_name) - - -def test_spack_from_scratch_concretize_1(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git) - repo = env - config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir) - config.add_repo(repo) - config.add_repo(repo) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.install_spack() - spack_operation.setup_spack_env() - spack_operation.concretize_spack_env(force=True) - concretization_file_path = spack_operation.env_path / 'spack.lock' - assert file_exists_and_not_empty(concretization_file_path) == True - - -def test_spack_from_scratch_concretize_2(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git) - repo = env - config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir) - config.add_repo(repo) - config.add_repo(repo) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - spack_operation.concretize_spack_env(force=False) - concretization_file_path = spack_operation.env_path / 'spack.lock' - assert file_exists_and_not_empty(concretization_file_path) == True - - -def test_spack_from_scratch_concretize_3(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git) - repo = env - config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir) - config.add_repo(repo) - config.add_repo(repo) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - concretization_file_path = spack_operation.env_path / 'spack.lock' - assert file_exists_and_not_empty(concretization_file_path) == False - - -def test_spack_from_scratch_concretize_4(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git) - config = SpackConfig(env=env, install_dir=install_dir) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - spack_operation.concretize_spack_env(force=False) - concretization_file_path = spack_operation.env_path / 'spack.lock' - assert file_exists_and_not_empty(concretization_file_path) == True - - -def test_spack_from_scratch_concretize_5(tmp_path): - install_dir = tmp_path - env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git) - config = SpackConfig(env=env, install_dir=install_dir) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - spack_operation.concretize_spack_env(force=True) - concretization_file_path = spack_operation.env_path / 'spack.lock' - assert file_exists_and_not_empty(concretization_file_path) == True - - -def test_spack_from_scratch_concretize_6(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) - config = SpackConfig(env=env, install_dir=install_dir) - config.add_repo(repo) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - spack_operation.concretize_spack_env(force=False) - concretization_file_path = spack_operation.env_path / 'spack.lock' - assert file_exists_and_not_empty(concretization_file_path) == True - - -def test_spack_from_scratch_concretize_7(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) - config = SpackConfig(env=env) - config.add_repo(repo) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - spack_operation.concretize_spack_env(force=True) - concretization_file_path = spack_operation.env_path / 'spack.lock' - assert file_exists_and_not_empty(concretization_file_path) == True - - -def test_spack_from_scratch_install(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) - config = SpackConfig(env=env) - config.add_repo(repo) - spack_operation = SpackOperationCreator.get_spack_operator(config) - spack_operation.install_spack() - spack_operation.setup_spack_env() - spack_operation.concretize_spack_env(force=True) - concretization_file_path = spack_operation.env_path / 'spack.lock' - assert file_exists_and_not_empty(concretization_file_path) == True - install_result = spack_operation.install_packages(jobs=2, signed=False, fresh=True, debug=False) - assert install_result.returncode == 0 diff --git a/dedal/tests/spack_install_test.py b/dedal/tests/spack_install_test.py deleted file mode 100644 index 564d5c6aa2138e815cd7d092215a4f2eee8816f6..0000000000000000000000000000000000000000 --- a/dedal/tests/spack_install_test.py +++ /dev/null @@ -1,12 +0,0 @@ -import pytest -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 -@pytest.mark.run(order=1) -def test_spack_install_scratch(): - spack_operation = SpackOperation() - spack_operation.install_spack(spack_version=f'v{SPACK_VERSION}') - installed_spack_version = spack_operation.get_spack_installed_version() - assert SPACK_VERSION == installed_spack_version diff --git a/dedal/tests/unit_tests/test_bash_command_executor.py b/dedal/tests/unit_tests/test_bash_command_executor.py deleted file mode 100644 index e216cc0f85dd22841d3c956b1714e8f444f46c7f..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_bash_command_executor.py +++ /dev/null @@ -1,236 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_bash_command_executor.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-18 -import os -import subprocess -from unittest.mock import patch - -import pytest - -from dedal.commands.bash_command_executor import BashCommandExecutor -from dedal.commands.command import Command - - -class MockCommand(Command): - def __init__(self, cmd: str): - self._cmd = cmd - - def execute(self) -> str: - return self._cmd - - -class TestBashCommandExecutor: - @pytest.mark.parametrize( - "test_id, os_name, expected_bash_command", - [ - ("posix_system", "posix", ["bash", "-c"]), - ("nt_system", "nt", ["wsl", "bash", "-c"]), - ], - ) - def test_init_success_path(self, mocker, test_id, os_name, expected_bash_command): - # Arrange - mock_get_logger = mocker.patch("dedal.commands.bash_command_executor.logging.getLogger") - mocker.patch("dedal.commands.bash_command_executor.os_name", os_name) - - # Act - executor = BashCommandExecutor() - - # Assert - assert executor.bash_command == expected_bash_command - 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)] - ) - def test_add_command_success_path(self, mocker, test_id: int, num_commands: int): - # Arrange - executor = BashCommandExecutor() - executor.logger = mocker.MagicMock() - commands = [MockCommand("some_command") for _ in range(num_commands)] - - # Act - for command in commands: - executor.add_command(command) - - # Assert - assert len(executor.sequence.commands) == num_commands - executor.logger.info.assert_has_calls( - [mocker.call("Adding command to the sequence: %s", command) for command in commands]) - - def test_add_command_invalid_type(self): - # Arrange - executor = BashCommandExecutor() - invalid_command = "not a command object" # type: ignore - - # Act - with pytest.raises(ValueError) as except_info: - executor.add_command(invalid_command) - - # Assert - assert str(except_info.value) == "Invalid command type. Use Command." - - @patch("dedal.commands.bash_command_executor.os_name", "unknown") - def test_init_unknown_os(self): - - # Act - executor = BashCommandExecutor() - - # Assert - assert executor.bash_command == ["undefined"] - - @pytest.mark.parametrize( - "test_id, commands, expected_output", - [ - ("single_command", [MockCommand("echo hello")], "hello\n"), - ("multiple_commands", [MockCommand("echo hello"), MockCommand("echo world")], "hello\nworld\n"), - ("command_with_pipe", [MockCommand("echo hello | grep hello")], "hello\n"), - - ], - ) - @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() - executor.logger = mocker.MagicMock() - for cmd in commands: - executor.add_command(cmd) - mock_subprocess_run.return_value.stdout = expected_output - - # Act - stdout, err = executor.execute() - - # Assert - assert stdout == expected_output - assert err is None - executor.logger.info.assert_has_calls( - [mocker.call('Adding command to the sequence: %s', cmd) for cmd in commands] + - [mocker.call("Successfully executed command sequence, output: %s", - mock_subprocess_run.return_value.stdout)]) - - @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 - executor = BashCommandExecutor() - executor.bash_command = ["bash", "-c"] - executor.add_command(MockCommand("some_command")) - - # Act - stdout, err = executor.execute() - - # Assert - assert stdout is None - assert err == "Error: Bash Command: ['bash', '-c'] not found: Mock file not found error" - mock_subprocess_run.assert_called_once_with(['bash', '-c', 'some_command'], capture_output=True, text=True, - check=True, timeout=172800) - - @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 - mocker.patch("dedal.commands.bash_command_executor.os_name", "nt") - executor = BashCommandExecutor() - executor.add_command(MockCommand("failing_command")) - - # Act - stdout, err = executor.execute() - - # Assert - assert stdout is None - assert err == "Error: Command failed with exit code 1, Error Output: Mock stderr" - mock_subprocess_run.assert_called_once_with(['wsl', 'bash', '-c', 'failing_command'], capture_output=True, - text=True, check=True, timeout=172800) - - @pytest.mark.parametrize( - "test_id, exception, expected_error_message", - [ - ("permission_error", PermissionError("Mock permission denied"), - "Error: Permission denied: Mock permission denied"), - ("os_error", OSError("Mock OS error"), "Error: OS error occurred: Mock OS error"), - ("value_error", ValueError("Mock invalid argument"), - "Error: Invalid argument passed: Mock invalid argument"), - ("type_error", TypeError("Mock invalid type"), "Error: Invalid type for arguments: Mock invalid type"), - ("timeout_expired", subprocess.TimeoutExpired("some_command", 10), - "Error: Command timed out after 10 seconds"), - ("subprocess_error", subprocess.SubprocessError("Mock subprocess error"), - "Subprocess error occurred: Mock subprocess error"), - ], - ) - def test_execute_other_errors(self, test_id, exception, expected_error_message, mocker): - # Arrange - 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")) - - # Act - stdout, err = executor.execute() - - # Assert - assert stdout is None - assert err == expected_error_message - mock_subprocess_run.assert_called_once_with(['wsl', 'bash', '-c', 'some_command'], capture_output=True, - text=True, check=True, timeout=172800) - - def test_execute_no_commands(self): - # Arrange - executor = BashCommandExecutor() - - # Act - with pytest.raises(ValueError) as except_info: - executor.execute() - - # Assert - assert str(except_info.value) == "No commands to execute." - - @patch("dedal.commands.bash_command_executor.subprocess.run") - def test_execute_happy_path_nt(self, mock_subprocess_run, mocker): - # Arrange - 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" - - # Act - stdout, err = executor.execute() - - # Assert - assert stdout == "hello\n" - assert err is None - assert executor.bash_command == ['wsl', 'bash', '-c'] - mock_subprocess_run.assert_called_once_with(['wsl', 'bash', '-c', 'echo hello'], capture_output=True, text=True, - check=True, timeout=172800) - - def test_execute_unknown_os(self, mocker): - # Arrange - errors = { - "posix": "Error: Bash Command: ['undefined'] not found: [Errno 2] No such file or directory: 'undefined'", - "nt": "Error: Bash Command: ['undefined'] not found: [WinError 2] The system cannot find the file " - 'specified' - } - original_os = os.name - expected_error = errors.get(original_os, "Error: Unknown OS") - mocker.patch("dedal.commands.bash_command_executor.os_name") - executor = BashCommandExecutor() - executor.add_command(MockCommand("echo hello")) - - # Act - assert executor.execute() == (None, expected_error) - - 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/dedal/tests/unit_tests/test_build_cache_manager.py b/dedal/tests/unit_tests/test_build_cache_manager.py index af5690ebb2c0bc94ef9701e4bad6dde7bde61dd5..6bce09480c4c70f41f6435291bdcbede8bd5bd6b 100644 --- a/dedal/tests/unit_tests/test_build_cache_manager.py +++ b/dedal/tests/unit_tests/test_build_cache_manager.py @@ -1,10 +1,3 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_build_cache_manager.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-20 import pytest from _pytest.fixtures import fixture diff --git a/dedal/tests/unit_tests/test_command.py b/dedal/tests/unit_tests/test_command.py deleted file mode 100644 index 3c86404199262dec907c983620f7c95a7adf0bc6..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_command.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_command.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-18 - -import pytest - -from dedal.commands.command import Command - -class ConcreteCommand(Command): - def __init__(self, return_value: str): - self._return_value = return_value - - def execute(self) -> str: - return self._return_value - - -class TestCommand: - def test_execute_abstract_method(self): - # Act - with pytest.raises(TypeError): - Command() # type: ignore - - - @pytest.mark.parametrize( - "test_id, return_value", - [ - ("empty_string", ""), - ("non_empty_string", "some_command"), - ("string_with_spaces", "command with spaces"), - ("non_ascii_chars", "αβγδ"), - ], - ) - def test_execute_concrete_command(self, test_id, return_value): - # Arrange - command = ConcreteCommand(return_value) - - # Act - result = command.execute() - - # Assert - assert result == return_value diff --git a/dedal/tests/unit_tests/test_command_enum.py b/dedal/tests/unit_tests/test_command_enum.py deleted file mode 100644 index f29e2b4c707a318100fa3d90c9f62d54d8b64ebb..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_command_enum.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_command_enum.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-18 - -import pytest - -from dedal.commands.command_enum import CommandEnum - -class TestCommandEnum: - - @pytest.mark.parametrize( - "test_id, command_name, expected_value", - [ - ("source", "SOURCE", "source"), - ("list_files", "LIST_FILES", "list_files"), - ("spack_install", "SPACK_INSTALL", "spack_install"), - ], - ) - def test_command_enum_values(self, test_id, command_name, expected_value): - - # Act - command = CommandEnum[command_name] - - # Assert - assert command.value == expected_value - assert command.name == command_name - - - def test_command_enum_invalid_name(self): - - # Act - with pytest.raises(KeyError): - CommandEnum["INVALID_COMMAND"] # type: ignore - diff --git a/dedal/tests/unit_tests/test_command_runner.py b/dedal/tests/unit_tests/test_command_runner.py deleted file mode 100644 index ac30fa083b82a1dc8684c33dfcf91c19ea0d2a3e..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_command_runner.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_command_runner.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-18 - -import pytest - -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): - def __init__(self, cmd_str: str): - self._cmd_str = cmd_str - - def execute(self) -> str: - return self._cmd_str - - -class TestCommandRunner: - @pytest.fixture(scope="function") - def mock_command_runner(self, mocker): - mocker.patch("dedal.commands.command_runner.BashCommandExecutor") - mocker.patch("dedal.commands.command_runner.logging.getLogger") - return CommandRunner() - - @pytest.mark.parametrize( - "test_id, command_str, mock_output, expected_output", - [ - ("simple_command", "echo hello", "hello\n", {"success": True, "output": "hello", "error": None}), - ("complex_command", "ls -l /tmp", "mock_output\n", - {"success": True, "output": "mock_output", "error": None}), - ("empty_output", "ls -l /tmp", "", {"success": True, "output": None, "error": None}), - ("command_with_error", "failing_command", "", {"success": False, "output": None, "error": "mock_error"}), - - ], - ) - def test_execute_command(self, mock_command_runner, test_id, command_str, mock_output, expected_output): - # Arrange - mock_command_runner.executor.execute.return_value = ( - mock_output, "mock_error" if "failing" in command_str else None) - command_sequence = MockCommandSequence(command_str) - - # Act - result = mock_command_runner.execute_command(command_sequence) - - # 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", - [ - ( - "valid_command", - PreconfiguredCommandEnum.SPACK_COMPILER_FIND, - ["gcc"], - {"success": True, "output": "mock_output", "error": None}, - ), - ], - ) - def test_run_predefined_command_success_path(self, mock_command_runner, test_id, command_type, args, - expected_result): - # Arrange - mock_command_runner.executor.execute.return_value = ("mock_output\n", None) - - # Act - result = mock_command_runner.run_preconfigured_command_sequence(command_type, *args) - - # Assert - assert result == expected_result - - def test_run_predefined_command_invalid_type(self, mock_command_runner): - # Arrange - invalid_command_type = "INVALID_COMMAND" # type: ignore - - # Act - result = mock_command_runner.run_preconfigured_command_sequence(invalid_command_type) - - # Assert - assert result == {"success": False, "error": "Invalid command name: INVALID_COMMAND"} - - @pytest.mark.parametrize( - "test_id, command_placeholders_map, expected_result", - [ - ( - "single_command", - {CommandEnum.LIST_FILES: {"folder_location": "/tmp"}}, - {"success": True, "output": "mock_output", "error": None}, - ), - ( - "multiple_commands", - { - CommandEnum.LIST_FILES: {"folder_location": "/tmp"}, - CommandEnum.SHOW_DIRECTORY: {}, - }, - {"success": True, "output": "mock_output", "error": None}, - ), - ( - "empty_placeholders", - {CommandEnum.SHOW_DIRECTORY: {}}, - {"success": True, "output": "mock_output", "error": None}, - ), - ], - ) - def test_run_custom_command(self, mocker, mock_command_runner, test_id, command_placeholders_map, expected_result): - # Arrange - mock_command_runner.execute_command = mocker.MagicMock(return_value=expected_result) - mock_create_custom_command_sequence = mocker.patch( - "dedal.commands.command_runner.CommandSequenceFactory.create_custom_command_sequence") - mock_create_custom_command_sequence.return_value = MockCommandSequence("mock_command") - - # Act - result = mock_command_runner.run_custom_command_sequence(command_placeholders_map) - - # Assert - assert result == expected_result - mock_create_custom_command_sequence.assert_called_once_with(command_placeholders_map) - mock_command_runner.execute_command.assert_called_once_with(mock_create_custom_command_sequence.return_value) diff --git a/dedal/tests/unit_tests/test_command_sequence.py b/dedal/tests/unit_tests/test_command_sequence.py deleted file mode 100644 index e663c06b57e32630666510e0b6ed55313012fe26..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_command_sequence.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_command_sequence.py -# 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 dedal.commands.command import Command -from dedal.commands.command_sequence import CommandSequence - - -class MockCommand(Command): - def __init__(self, cmd_str: str): - self._cmd_str = cmd_str - - def execute(self) -> str: - return self._cmd_str - - -class TestCommandSequence: - - @pytest.mark.parametrize( - "test_id, commands, expected_output", - [ - ("single_command", ["echo hello"], "echo hello"), - ("single_command_with_spaces at_beginning", [" pwd"], "pwd"), - ("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"), - ("empty_command", [""], ""), - ("commands_with_spaces", ["command with spaces", "another command"], - "command with spaces && another command"), - ], - ) - def test_execute_success_path(self, test_id, commands, expected_output): - # Arrange - sequence = CommandSequence() - for cmd in commands: - sequence.add_command(MockCommand(cmd)) - - # Act - result = sequence.execute() - - # Assert - assert result == expected_output - - def test_execute_no_commands(self): - # Arrange - sequence = CommandSequence() - - # Act - result = sequence.execute() - - # Assert - assert result == "" - - @pytest.mark.parametrize( - "test_id, num_commands", - [ - ("one_command", 1), - ("multiple_commands", 5), - ("no_commands", 0), - ], - ) - def test_add_command_success_path(self, test_id, num_commands): - # Arrange - sequence = CommandSequence() - commands = [MockCommand(f"command_{i}") for i in range(num_commands)] - - # Act - for command in commands: - sequence.add_command(command) - - # Assert - assert len(sequence.commands) == num_commands - - def test_add_command_invalid_type(self): - # Arrange - sequence = CommandSequence() - invalid_command = "not a command object" # type: ignore - - # Act - with pytest.raises(ValueError) as except_info: - sequence.add_command(invalid_command) - - # 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/dedal/tests/unit_tests/test_command_sequence_builder.py b/dedal/tests/unit_tests/test_command_sequence_builder.py deleted file mode 100644 index e4004255128836a4643665bf6acf338cd9497155..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_command_sequence_builder.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_command_sequence_builder.py -# 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, patch - -import pytest - -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: - - @pytest.mark.parametrize( - "test_id, command_name, placeholders, expected_command", - [ - ("no_placeholders", CommandEnum.SHOW_DIRECTORY, {}, ["pwd"]), - ("with_placeholders", CommandEnum.LIST_FILES, {"folder_location": "/tmp "}, ["ls", "-l", "/tmp"]), - ("missing_placeholders", CommandEnum.FIND_IN_FILE, {"search_term": "error"}, - ["grep", "-i", "error", "{file_path}"]), - ("missing_placeholders_values_1", CommandEnum.SPACK_MIRROR_ADD, {"autopush": ""}, - ["spack", "mirror", "add", "{signed}", "{mirror_name}","{mirror_path}"]), - ("missing_placeholders_values_2", CommandEnum.SPACK_MIRROR_ADD, {"autopush": "", "signed": ""}, - ["spack", "mirror", "add", "{mirror_name}", "{mirror_path}"]), - ("missing_placeholders_values_3", CommandEnum.SPACK_MIRROR_ADD, {"autopush": "", "signed": "", "mirror_name": "test", "mirror_path": "test_path"}, - ["spack", "mirror", "add", "test", "test_path"]), - ("extra_placeholders", CommandEnum.ECHO_MESSAGE, {"message": "hello", "extra": "world"}, ["echo", "hello"]), - ], - ) - def test_add_generic_command_success_path(self, mocker, test_id, command_name, - placeholders, expected_command): - # Arrange - mock_get_command = mocker.patch.object(CommandRegistry, "get_command") - mock_create_command = mocker.patch.object(ShellCommandFactory, "create_command") - builder = CommandSequenceBuilder() - mock_get_command.return_value = expected_command - mock_create_command.return_value = Mock(spec=Command, - execute=lambda: " ".join(expected_command) if expected_command else "") - - # Act - builder.add_generic_command(command_name, placeholders) - - # Assert - mock_get_command.assert_called_once_with(command_name) - mock_create_command.assert_called_once_with(*expected_command) - assert len(builder.sequence.commands) == 1 - assert builder.sequence.commands[0].execute() == " ".join(expected_command) if expected_command else "" - - def test_add_generic_command_invalid_command_type(self): - # Arrange - builder = CommandSequenceBuilder() - invalid_command_name = "invalid" # type: ignore - - # Act - with pytest.raises(ValueError) as except_info: - builder.add_generic_command(invalid_command_name, {}) - - # Assert - assert str(except_info.value) == "Invalid command type. Use CommandEnum." - - def test_add_generic_command_unknown_command(self, mocker): - # Arrange - mock_get_command = mocker.patch.object(CommandRegistry, "get_command") - builder = CommandSequenceBuilder() - mock_get_command.return_value = None - - # Act - with pytest.raises(ValueError) as except_info: - builder.add_generic_command(CommandEnum.LIST_FILES, {}) - - # Assert - assert str(except_info.value) == "Unknown command: CommandEnum.LIST_FILES" - - def test_build(self): - # Arrange - builder = CommandSequenceBuilder() - builder.add_generic_command(CommandEnum.SHOW_DIRECTORY, {}) - - # Act - sequence = builder.build() - - # Assert - assert isinstance(sequence, CommandSequence) - assert sequence.execute() == "pwd" - assert len(sequence.commands) == 1 - assert len(builder.sequence.commands) == 0 # Check if the builder's sequence is reset diff --git a/dedal/tests/unit_tests/test_command_sequence_factory.py b/dedal/tests/unit_tests/test_command_sequence_factory.py deleted file mode 100644 index 7048690aac222786ae5b8794c07969e8e8e91544..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_command_sequence_factory.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_command_sequence_factory.py -# 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, patch - -import pytest - -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: - - @pytest.mark.parametrize( - "test_id, command_placeholders_map, expected_calls", - [ - ("single_command", {CommandEnum.SHOW_DIRECTORY: {}}, [(CommandEnum.SHOW_DIRECTORY, {})]), - ( - "multiple_commands", - {CommandEnum.LIST_FILES: {"folder_location": "/tmp"}, CommandEnum.SHOW_DIRECTORY: {}}, - [(CommandEnum.LIST_FILES, {"folder_location": "/tmp"}), (CommandEnum.SHOW_DIRECTORY, {})], - ), - ("no_commands", {}, []), - ], - ) - def test_create_custom_command_sequence(self, mocker, test_id, command_placeholders_map, expected_calls): - # Arrange - mock_add_generic_command = mocker.patch.object(CommandSequenceBuilder, "add_generic_command") - mock_builder = Mock(spec=CommandSequenceBuilder, build=Mock(return_value=CommandSequence())) - mock_builder.add_generic_command = mock_add_generic_command - mock_add_generic_command.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) - - # Assert - assert mock_add_generic_command.call_count == len(expected_calls) - mock_add_generic_command.assert_has_calls( - [mocker.call(args, kwargs) for args, kwargs in expected_calls], any_order=True - ) - mock_builder.build.assert_called_once() diff --git a/dedal/tests/unit_tests/test_generic_shell_command.py b/dedal/tests/unit_tests/test_generic_shell_command.py deleted file mode 100644 index 7ed779aee2faf531e2472d9219551d1792aa7bf1..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_generic_shell_command.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_generic_shell_command.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-19 - -import pytest - -from dedal.commands.command import Command -from dedal.commands.generic_shell_command import GenericShellCommand - -class TestGenericShellCommand: - - @pytest.mark.parametrize( - "test_id, command_name, args, expected_output", - [ - ("no_args", "ls", [], "ls"), - ("single_arg", "echo", ["hello"], "echo hello"), - ("multiple_args", "grep", ["-i", "error", "file.txt"], "grep -i error file.txt"), - ("command_with_spaces", " ls ", ["-l", "/tmp"], "ls -l /tmp"), - ("args_with_spaces", "ls", [" -l ", " /tmp "], "ls -l /tmp"), - ("command_and_args_with_spaces", " ls ", [" -l ", " /tmp "], "ls -l /tmp"), - ("empty_args", "ls", [""], "ls "), # Empty arguments are preserved, but stripped - ], - ) - def test_execute_success_path(self, test_id, command_name, args, expected_output): - - # Act - command = GenericShellCommand(command_name, *args) - result = command.execute() - - # Assert - assert result == expected_output - - - @pytest.mark.parametrize( - "test_id, command_name, args, expected_error", - [ - ("empty_command_name", "", [], ValueError), - ("none_command_name", None, [], ValueError), # type: ignore - ("int_command_name", 123, [], ValueError), # type: ignore - ("invalid_arg_type", "ls", [123], ValueError), # type: ignore - ("mixed_arg_types", "ls", ["valid", 456], ValueError), # type: ignore - ], - ) - def test_init_error_cases(self, test_id, command_name, args, expected_error): - - # Act - with pytest.raises(expected_error) as except_info: - GenericShellCommand(command_name, *args) - - # Assert - if expected_error is ValueError: - if not command_name: - assert str(except_info.value) == "Command name is required!" - elif not isinstance(command_name, str): - assert str(except_info.value) == "Command name must be a string!" - else: - assert str(except_info.value) == "All arguments must be strings!" - - diff --git a/dedal/tests/unit_tests/test_preconfigured_command_enum.py b/dedal/tests/unit_tests/test_preconfigured_command_enum.py deleted file mode 100644 index 820c4d4175903e12c4cd125cf327371fa08145f4..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_preconfigured_command_enum.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_preconfigured_command_enum.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-19 - -import pytest - -from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum - -class TestPreconfiguredCommandEnum: - - @pytest.mark.parametrize( - "test_id, command_name, expected_value", - [ - ("spack_compiler_find", "SPACK_COMPILER_FIND", "spack_compiler_find"), - ("spack_compilers", "SPACK_COMPILERS", "spack_compilers"), - ("spack_install", "SPACK_INSTALL", "spack_install"), - ], - ) - def test_preconfigured_command_enum_values(self, test_id, command_name, expected_value): - - # Act - command = PreconfiguredCommandEnum[command_name] - - # Assert - assert command.value == expected_value - assert command.name == command_name - - - def test_preconfigured_command_enum_invalid_name(self): - - # Act - with pytest.raises(KeyError): - PreconfiguredCommandEnum["INVALID_COMMAND"] # type: ignore diff --git a/dedal/tests/unit_tests/test_shell_command_factory.py b/dedal/tests/unit_tests/test_shell_command_factory.py deleted file mode 100644 index bfd2b2db3f98d45419cf2f6e3246cf5d2d4f4c00..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_shell_command_factory.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_shell_command_factory.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-19 - -import pytest - -from dedal.commands.generic_shell_command import GenericShellCommand -from dedal.commands.shell_command_factory import ShellCommandFactory - - -class TestShellCommandFactory: - - @pytest.mark.parametrize( - "test_id, command_name, args", - [ - ("no_args", "ls", []), - ("single_arg", "echo", ["hello"]), - ("multiple_args", "grep", ["-i", "error", "file.txt"]), - ("command_with_spaces", " ls ", ["-l", "/tmp"]), - ("args_with_spaces", "ls", [" -l ", " /tmp "]), - ("command_and_args_with_spaces", " ls ", [" -l ", " /tmp "]), - ], - ) - def test_create_command_happy_path(self, test_id, command_name, args): - - # Act - command = ShellCommandFactory.create_command(command_name, *args) - - # Assert - assert isinstance(command, GenericShellCommand) - assert command.command_name == command_name.strip() - assert command.args == tuple(map(str.strip, args)) - - @pytest.mark.parametrize( - "test_id, command_name, args, expected_error", - [ - ("empty_command_name", "", [], ValueError), - ("none_command_name", None, [], ValueError), # type: ignore - ("int_command_name", 123, [], ValueError), # type: ignore - ("invalid_arg_type", "ls", [123], ValueError), # type: ignore - ("mixed_arg_types", "ls", ["valid", 456], ValueError), # type: ignore - ], - ) - def test_create_command_error_cases(self, test_id, command_name, args, expected_error): - - # Act - with pytest.raises(expected_error) as except_info: - ShellCommandFactory.create_command(command_name, *args) - - # Assert - if expected_error is ValueError: - if not command_name: - assert str(except_info.value) == "Command name is required!" - elif not isinstance(command_name, str): - assert str(except_info.value) == "Command name must be a string!" - else: - assert str(except_info.value) == "All arguments must be strings!" diff --git a/dedal/tests/unit_tests/test_spack_command_sequence_factory.py b/dedal/tests/unit_tests/test_spack_command_sequence_factory.py deleted file mode 100644 index 0ffebcf3034236b5940ffbd529d5cb23ed9dc3c4..0000000000000000000000000000000000000000 --- a/dedal/tests/unit_tests/test_spack_command_sequence_factory.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_spack_command_sequence_factory.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-19 - -import pytest - -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: - - @pytest.mark.parametrize( - "test_id, spack_setup_script", - [ - ("with_setup_script", "/path/to/setup.sh"), - ("empty_setup_script", "")], - ) - def test_create_spack_enabled_command_sequence_builder(self, test_id, spack_setup_script): - # Act - builder = SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script) - - # Assert - assert isinstance(builder, CommandSequenceBuilder) - assert len(builder.sequence.commands) == 1 - assert builder.sequence.commands[0].command_name == "source" - if spack_setup_script: - assert builder.sequence.commands[0].args == ("/path/to/setup.sh",) - else: - assert len(builder.sequence.commands[0].args) == 0 - - @pytest.mark.parametrize( - "test_id, method_name, args, kwargs, expected_command, expected_args", - [ - ( - "spack_compilers", "create_spack_compilers_command_sequence", ["/path/to/setup.sh"], {}, "spack", - ["compilers"] - ), - ( - "spack_compiler_list", "create_spack_compiler_list_command_sequence", ["/path/to/setup.sh"], {}, - "spack", ["compiler", "list"] - ), - ( - "spack_install_with_version", "create_spack_install_package_command_sequence", - ["/path/to/setup.sh", "package", "1.0"], {}, "spack", ["install", "package@1.0"] - ), - ( - "spack_install_no_version", "create_spack_install_package_command_sequence", - ["/path/to/setup.sh", "package", None], {}, "spack", ["install", "package"] - ), - ( - "spack_install_no_package", "create_spack_install_package_command_sequence", - ["/path/to/setup.sh", None, "1.0"], {}, "spack", ["install"] - ), - ( - "spack_compiler_info", "create_spack_compiler_info_command_sequence", - ["/path/to/setup.sh", "gcc", "10.2"], {}, "spack", ["compiler", "info", "gcc@10.2"] - ), - ], - ) - def test_create_command_sequence(self, test_id, method_name, args, kwargs, expected_command, expected_args): - # Arrange - method = getattr(SpackCommandSequenceFactory, method_name) - - # Act - sequence = method(*args, **kwargs) - - # Assert - assert isinstance(sequence, CommandSequence) - assert len(sequence.commands) == 2 - assert sequence.commands[1].command_name == expected_command - assert sequence.commands[1].args == tuple(expected_args) - - @pytest.mark.parametrize( - "test_id, env_name, mirror_name, mirror_path, autopush, signed, expected_length, expected_autopush, expected_signed, expected_output", - [ - ("no_env", "", "mymirror", "/path/to/mirror", False, False, 2, "", "", - ("mirror", "add", "mymirror", "/path/to/mirror")), - ("with_env", "myenv", "mymirror", "/path/to/mirror", True, True, 3, "--autopush", "--signed", - ("mirror", "add", "--autopush", "--signed", "mymirror", "/path/to/mirror")), - ], - ) - def test_create_spack_mirror_add_command_sequence(self, test_id, env_name, mirror_name, mirror_path, autopush, - signed, expected_length, expected_autopush, expected_signed, - expected_output): - # Arrange - spack_setup_script = "/path/to/setup.sh" - - # Act - sequence = SpackCommandSequenceFactory.create_spack_mirror_add_command_sequence( - spack_setup_script, env_name, mirror_name, mirror_path, autopush, signed - ) - - # Assert - assert isinstance(sequence, CommandSequence) - assert len(sequence.commands) == expected_length - assert sequence.commands[-1].command_name == "spack" - assert sequence.commands[-1].args == expected_output - - @pytest.mark.parametrize( - "test_id, package_name, version, expected_package_arg", - [ - ("with_package_and_version", "mypackage", "1.2.3", "mypackage@1.2.3"), - ("only_package_name", "mypackage", None, "mypackage"), - ("no_package", None, "1.2.3", ""), - ], - ) - def test_create_spack_post_install_find_command_sequence(self, test_id, package_name, version, - expected_package_arg): - # Arrange - spack_setup_script = "/path/to/setup.sh" - env_path = "/path/to/env" - - # Act - sequence = SpackCommandSequenceFactory.create_spack_post_install_find_command_sequence( - spack_setup_script, env_path, package_name, version - ) - - # Assert - assert isinstance(sequence, CommandSequence) - assert len(sequence.commands) == 3 - assert sequence.commands[1].command_name == "spack" - assert sequence.commands[1].args == ("env", "activate", "-p", env_path) - assert sequence.commands[2].command_name == "spack" - assert sequence.commands[2].args == ("find", expected_package_arg) if expected_package_arg else ("find",) - - @pytest.mark.parametrize("spack_setup_script, public_key_path", [ - ("/path/to/setup-env.sh", "key.gpg"), - ("./setup-env.sh", "path/to/key.gpg"), - ]) - def test_create_spack_gpg_trust_command_sequence(self, mocker, spack_setup_script, public_key_path): - # Test ID: create_spack_gpg_trust_command_sequence - - # Arrange - mock_command_sequence_builder = mocker.MagicMock() - mocker.patch.object(SpackCommandSequenceFactory, "create_spack_enabled_command_sequence_builder", - return_value=mock_command_sequence_builder) - - mock_command_sequence = mocker.MagicMock() - mock_command_sequence_builder.add_generic_command.return_value = mock_command_sequence_builder - mock_command_sequence_builder.build.return_value = mock_command_sequence - - # Act - result = SpackCommandSequenceFactory.create_spack_gpg_trust_command_sequence(spack_setup_script, - public_key_path) - - # Assert - assert result == mock_command_sequence - SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder.assert_called_once_with( - spack_setup_script) - mock_command_sequence_builder.add_generic_command.assert_called_once_with(CommandEnum.SPACK_GPG_TRUST, - {"public_key_path": public_key_path}) - mock_command_sequence_builder.build.assert_called_once() diff --git a/dedal/tests/unit_tests/test_spack_operation.py b/dedal/tests/unit_tests/test_spack_operation.py index f053459c04e2f8f10206d570cbf3857f1ec1de3c..fa322c7baac40335723cd67255933b908b1e4232 100644 --- a/dedal/tests/unit_tests/test_spack_operation.py +++ b/dedal/tests/unit_tests/test_spack_operation.py @@ -1,11 +1,3 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_spack_operation.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-19 - import pytest from _pytest.fixtures import fixture diff --git a/dedal/tests/unit_tests/test_spack_operation_use_cache.py b/dedal/tests/unit_tests/test_spack_operation_use_cache.py index fe5d9da3d9831990266b4b380837b246724d2489..f0eaf7961be767b2cc00183e3418fc1f90bca9d2 100644 --- a/dedal/tests/unit_tests/test_spack_operation_use_cache.py +++ b/dedal/tests/unit_tests/test_spack_operation_use_cache.py @@ -1,10 +1,3 @@ -# Copyright (c) 2025 -# License Information: To be decided! -# -# File: test_spack_operation_use_cache.py -# Description: Brief description of the file. -# Created by: Murugan, Jithu <j.murugan@fz-juelich.de> -# Created on: 2025-02-20 from pathlib import Path import pytest diff --git a/dedal/utils/utils.py b/dedal/utils/utils.py index f7fe6620ae29a175e1825e5cb3c0820043a9b554..e37bce846947525eb9396af421586f04557025b8 100644 --- a/dedal/utils/utils.py +++ b/dedal/utils/utils.py @@ -155,3 +155,9 @@ def resolve_path(path: str): else: path = Path(path).resolve() return path + + +def count_files_in_folder(folder_path: str) -> int: + if not os.path.isdir(folder_path): + raise ValueError(f"{folder_path} is not a valid directory") + return sum(1 for file in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, file))) diff --git a/pyproject.toml b/pyproject.toml index c7ea27622c32e267ecf0559f1f457656a2f60216..7b82909769b161d8777cadd560899ba827cea941 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,5 +28,4 @@ dedal = "dedal.cli.spack_manager_api:cli" "dedal" = ["dedal/logger/logging.conf"] [project.optional-dependencies] -test = ["pytest", "pytest-mock", "pytest-ordering", "coverage"] -dev = ["mypy", "pylint", "black", "flake8"] \ No newline at end of file +test = ["pytest", "pytest-mock", "pytest-ordering", "coverage"] \ No newline at end of file