diff --git a/src/aiida/cmdline/commands/cmd_code.py b/src/aiida/cmdline/commands/cmd_code.py index 477d2f61ab..9740ed8e02 100644 --- a/src/aiida/cmdline/commands/cmd_code.py +++ b/src/aiida/cmdline/commands/cmd_code.py @@ -8,6 +8,7 @@ ########################################################################### """`verdi code` command.""" +import pathlib from collections import defaultdict from functools import partial @@ -18,6 +19,7 @@ from aiida.cmdline.params import arguments, options, types from aiida.cmdline.params.options.commands import code as options_code from aiida.cmdline.utils import echo, echo_tabulate +from aiida.cmdline.utils.common import generate_validate_output_file from aiida.cmdline.utils.decorators import with_dbenv from aiida.common import exceptions @@ -234,34 +236,35 @@ def show(code): @verdi_code.command() @arguments.CODE() -@arguments.OUTPUT_FILE(type=click.Path(exists=False)) -@click.option( - '--sort/--no-sort', - is_flag=True, - default=True, - help='Sort the keys of the output YAML.', - show_default=True, -) +@arguments.OUTPUT_FILE(type=click.Path(exists=False, path_type=pathlib.Path), required=False) +@options.OVERWRITE() +@options.SORT() @with_dbenv() -def export(code, output_file, sort): +def export(code, output_file, overwrite, sort): """Export code to a yaml file.""" + import yaml code_data = {} for key in code.Model.model_fields.keys(): - if key == 'computer': - value = getattr(code, key).label - else: - value = getattr(code, key) + value = getattr(code, key).label if key == 'computer' else getattr(code, key) # If the attribute is not set, for example ``with_mpi`` do not export it, because the YAML won't be valid for # use in ``verdi code create`` since ``None`` is not a valid value on the CLI. if value is not None: code_data[key] = str(value) - with open(output_file, 'w', encoding='utf-8') as yfhandle: - yaml.dump(code_data, yfhandle, sort_keys=sort) + try: + output_file = generate_validate_output_file( + output_file=output_file, entity_label=code.label, overwrite=overwrite, appendix=f'@{code_data["computer"]}' + ) + except (FileExistsError, IsADirectoryError) as exception: + raise click.BadParameter(str(exception), param_hint='OUTPUT_FILE') from exception + + output_file.write_text(yaml.dump(code_data, sort_keys=sort)) + + echo.echo_success(f'Code<{code.pk}> {code.label} exported to file `{output_file}`.') @verdi_code.command() diff --git a/src/aiida/cmdline/commands/cmd_computer.py b/src/aiida/cmdline/commands/cmd_computer.py index 7f8508b77a..acb9c2da81 100644 --- a/src/aiida/cmdline/commands/cmd_computer.py +++ b/src/aiida/cmdline/commands/cmd_computer.py @@ -20,6 +20,7 @@ from aiida.cmdline.params import arguments, options from aiida.cmdline.params.options.commands import computer as options_computer from aiida.cmdline.utils import echo, echo_tabulate +from aiida.cmdline.utils.common import generate_validate_output_file from aiida.cmdline.utils.decorators import with_dbenv from aiida.common.exceptions import EntryPointError, ValidationError from aiida.plugins.entry_point import get_entry_point_names @@ -741,16 +742,11 @@ def computer_export(): @computer_export.command('setup') @arguments.COMPUTER() -@arguments.OUTPUT_FILE(type=click.Path(exists=False, path_type=pathlib.Path)) -@click.option( - '--sort/--no-sort', - is_flag=True, - default=True, - help='Sort the keys of the output YAML.', - show_default=True, -) +@arguments.OUTPUT_FILE(type=click.Path(exists=False, path_type=pathlib.Path), required=False) +@options.OVERWRITE() +@options.SORT() @with_dbenv() -def computer_export_setup(computer, output_file, sort): +def computer_export_setup(computer, output_file, overwrite, sort): """Export computer setup to a YAML file.""" import yaml @@ -769,6 +765,14 @@ def computer_export_setup(computer, output_file, sort): 'prepend_text': computer.get_prepend_text(), 'append_text': computer.get_append_text(), } + + try: + output_file = generate_validate_output_file( + output_file=output_file, entity_label=computer.label, overwrite=overwrite, appendix='-setup' + ) + except (FileExistsError, IsADirectoryError) as exception: + raise click.BadParameter(str(exception), param_hint='OUTPUT_FILE') from exception + try: output_file.write_text(yaml.dump(computer_setup, sort_keys=sort), 'utf-8') except Exception as e: @@ -783,19 +787,14 @@ def computer_export_setup(computer, output_file, sort): @computer_export.command('config') @arguments.COMPUTER() -@arguments.OUTPUT_FILE(type=click.Path(exists=False, path_type=pathlib.Path)) +@arguments.OUTPUT_FILE(type=click.Path(exists=False, path_type=pathlib.Path), required=False) @options.USER( help='Email address of the AiiDA user from whom to export this computer (if different from default user).' ) -@click.option( - '--sort/--no-sort', - is_flag=True, - default=True, - help='Sort the keys of the output YAML.', - show_default=True, -) +@options.OVERWRITE() +@options.SORT() @with_dbenv() -def computer_export_config(computer, output_file, user, sort): +def computer_export_config(computer, output_file, user, overwrite, sort): """Export computer transport configuration for a user to a YAML file.""" import yaml @@ -804,20 +803,29 @@ def computer_export_config(computer, output_file, user, sort): f'Computer<{computer.pk}> {computer.label} configuration cannot be exported,' ' because computer has not been configured yet.' ) + else: + try: + output_file = generate_validate_output_file( + output_file=output_file, entity_label=computer.label, overwrite=overwrite, appendix='-config' + ) + except (FileExistsError, IsADirectoryError) as exception: + raise click.BadParameter(str(exception), param_hint='OUTPUT_FILE') from exception + try: computer_configuration = computer.get_configuration(user) output_file.write_text(yaml.dump(computer_configuration, sort_keys=sort), 'utf-8') - except Exception as e: + + except Exception as exception: error_traceback = traceback.format_exc() echo.CMDLINE_LOGGER.debug(error_traceback) if user is None: echo.echo_critical( - f'Unexpected error while exporting configuration for Computer<{computer.pk}> {computer.label}: {e!s}.' + f'Unexpected error while exporting configuration for Computer<{computer.pk}> {computer.label}: {exception!s}.' # noqa: E501 ) else: echo.echo_critical( f'Unexpected error while exporting configuration for Computer<{computer.pk}> {computer.label}' - f' and User<{user.pk}> {user.email}: {e!s}.' + f' and User<{user.pk}> {user.email}: {exception!s}.' ) else: - echo.echo_success(f"Computer<{computer.pk}> {computer.label} configuration exported to file '{output_file}'.") + echo.echo_success(f'Computer<{computer.pk}> {computer.label} configuration exported to file `{output_file}`.') diff --git a/src/aiida/cmdline/params/options/__init__.py b/src/aiida/cmdline/params/options/__init__.py index 065efe4223..ea4be61461 100644 --- a/src/aiida/cmdline/params/options/__init__.py +++ b/src/aiida/cmdline/params/options/__init__.py @@ -92,6 +92,7 @@ 'REPOSITORY_PATH', 'SCHEDULER', 'SILENT', + 'SORT', 'TIMEOUT', 'TRAJECTORY_INDEX', 'TRANSPORT', diff --git a/src/aiida/cmdline/params/options/main.py b/src/aiida/cmdline/params/options/main.py index f5eb2d551f..d521828450 100644 --- a/src/aiida/cmdline/params/options/main.py +++ b/src/aiida/cmdline/params/options/main.py @@ -96,6 +96,7 @@ 'REPOSITORY_PATH', 'SCHEDULER', 'SILENT', + 'SORT', 'TIMEOUT', 'TRAJECTORY_INDEX', 'TRANSPORT', @@ -771,3 +772,12 @@ def set_log_level(ctx, _param, value): show_default=True, help='Overwrite file/directory if writing to disk.', ) + +SORT = OverridableOption( + '--sort/--no-sort', + 'sort', + is_flag=True, + default=True, + help='Sort the keys of the output YAML.', + show_default=True, +) diff --git a/src/aiida/cmdline/utils/common.py b/src/aiida/cmdline/utils/common.py index 53420fd33b..d410b33d91 100644 --- a/src/aiida/cmdline/utils/common.py +++ b/src/aiida/cmdline/utils/common.py @@ -8,10 +8,13 @@ ########################################################################### """Common utility functions for command line commands.""" +from __future__ import annotations + import logging import os import sys import textwrap +from pathlib import Path from typing import TYPE_CHECKING from click import style @@ -481,3 +484,22 @@ def build_entries(ports): echo.echo(tabulate(table, tablefmt='plain')) echo.echo(style('\nExit codes that invalidate the cache are marked in bold red.\n', italic=True)) + + +def generate_validate_output_file( + output_file: Path | None, entity_label: str, appendix: str = '', overwrite: bool = False +): + """Generate default output filename for `Code`/`Computer` export and validate.""" + + if output_file is None: + output_file = Path(f'{entity_label}{appendix}.yml') + + if output_file.is_dir(): + raise IsADirectoryError( + f'A directory with the name `{output_file.resolve()}` already exists. Remove manually and try again.' + ) + + if output_file.is_file() and not overwrite: + raise FileExistsError(f'File `{output_file}` already exists, use `--overwrite` to overwrite.') + + return output_file diff --git a/tests/cmdline/commands/test_code.py b/tests/cmdline/commands/test_code.py index 8aeeb5cec8..b7d1c5cf5f 100644 --- a/tests/cmdline/commands/test_code.py +++ b/tests/cmdline/commands/test_code.py @@ -259,8 +259,8 @@ def test_code_duplicate_ignore(run_cli_command, aiida_code_installed, non_intera @pytest.mark.usefixtures('aiida_profile_clean') -@pytest.mark.parametrize('sort', (True, False)) -def test_code_export(run_cli_command, aiida_code_installed, tmp_path, file_regression, sort): +@pytest.mark.parametrize('sort_option', ('--sort', '--no-sort')) +def test_code_export(run_cli_command, aiida_code_installed, tmp_path, file_regression, sort_option): """Test export the code setup to str.""" prepend_text = 'module load something\n some command' code = aiida_code_installed( @@ -271,14 +271,11 @@ def test_code_export(run_cli_command, aiida_code_installed, tmp_path, file_regre ) filepath = tmp_path / 'code.yml' - options = [str(code.pk), str(filepath)] - options.append('--sort' if sort else '--no-sort') - - run_cli_command(cmd_code.export, options) - + options = [str(code.pk), str(filepath), sort_option] + result = run_cli_command(cmd_code.export, options) + assert str(filepath) in result.output, 'Filename should be in terminal output but was not found.' # file regression check - with open(filepath, 'r', encoding='utf-8') as fhandle: - content = fhandle.read() + content = filepath.read_text() file_regression.check(content, extension='.yml') # round trip test by create code from the config file @@ -292,6 +289,65 @@ def test_code_export(run_cli_command, aiida_code_installed, tmp_path, file_regre assert isinstance(new_code, InstalledCode) +@pytest.mark.usefixtures('aiida_profile_clean') +def test_code_export_overwrite(run_cli_command, aiida_code_installed, tmp_path): + prepend_text = 'module load something\n some command' + code = aiida_code_installed( + default_calc_job_plugin='core.arithmetic.add', + filepath_executable='/bin/cat', + label='code', + prepend_text=prepend_text, + ) + filepath = tmp_path / 'code.yml' + + options = [str(code.pk), str(filepath)] + + # Create directory with the same name and check that command fails + filepath.mkdir() + result = run_cli_command(cmd_code.export, options, raises=True) + assert f'A directory with the name `{filepath}` already exists' in result.output + filepath.rmdir() + + # Export fails if file already exists and overwrite set to False + filepath.touch() + result = run_cli_command(cmd_code.export, options, raises=True) + assert f'File `{filepath}` already exists' in result.output + + # Check that overwrite actually overwrites the exported Code config with the new data + code_echo = aiida_code_installed( + default_calc_job_plugin='core.arithmetic.add', + filepath_executable='/bin/echo', + # Need to set different label, therefore manually specify the same output filename + label='code_echo', + prepend_text=prepend_text, + ) + + options = [str(code_echo.pk), str(filepath), '--overwrite'] + run_cli_command(cmd_code.export, options) + + content = filepath.read_text() + assert '/bin/echo' in content + + +@pytest.mark.usefixtures('aiida_profile_clean') +@pytest.mark.usefixtures('chdir_tmp_path') +def test_code_export_default_filename(run_cli_command, aiida_code_installed): + """Test default filename being created if no argument passed.""" + + prepend_text = 'module load something\n some command' + code = aiida_code_installed( + default_calc_job_plugin='core.arithmetic.add', + filepath_executable='/bin/cat', + label='code', + prepend_text=prepend_text, + ) + + options = [str(code.pk)] + run_cli_command(cmd_code.export, options) + + assert pathlib.Path('code@localhost.yml').is_file() + + @pytest.mark.parametrize('non_interactive_editor', ('vim -cwq',), indirect=True) def test_from_config_local_file(non_interactive_editor, run_cli_command, aiida_localhost): """Test setting up a code from a config file on disk.""" diff --git a/tests/cmdline/commands/test_code/test_code_export_True_.yml b/tests/cmdline/commands/test_code/test_code_export_True_.yml deleted file mode 100644 index 640717a1d2..0000000000 --- a/tests/cmdline/commands/test_code/test_code_export_True_.yml +++ /dev/null @@ -1,8 +0,0 @@ -append_text: '' -computer: localhost -default_calc_job_plugin: core.arithmetic.add -description: '' -filepath_executable: /bin/cat -label: code -prepend_text: "module load something\n some command" -use_double_quotes: 'False' diff --git a/tests/cmdline/commands/test_code/test_code_export_False_.yml b/tests/cmdline/commands/test_code/test_code_export___no_sort_.yml similarity index 100% rename from tests/cmdline/commands/test_code/test_code_export_False_.yml rename to tests/cmdline/commands/test_code/test_code_export___no_sort_.yml diff --git a/tests/cmdline/commands/test_code/test_code_export.yml b/tests/cmdline/commands/test_code/test_code_export___sort_.yml similarity index 100% rename from tests/cmdline/commands/test_code/test_code_export.yml rename to tests/cmdline/commands/test_code/test_code_export___sort_.yml diff --git a/tests/cmdline/commands/test_computer.py b/tests/cmdline/commands/test_computer.py index 128a3bd61f..dac1170770 100644 --- a/tests/cmdline/commands/test_computer.py +++ b/tests/cmdline/commands/test_computer.py @@ -9,6 +9,7 @@ """Tests for the 'verdi computer' command.""" import os +import pathlib import tempfile import textwrap from collections import OrderedDict @@ -515,69 +516,167 @@ def test_show(self): assert '--username=' in result.output assert result_cur.output == result.output - @pytest.mark.parametrize('sort', ['--sort', '--no-sort']) - def test_computer_export_setup(self, tmp_path, sort): - """Test if 'verdi computer export setup' command works""" - self.comp_builder.label = 'test_computer_export_setup' + sort + @pytest.mark.parametrize('sort_option', ('--sort', '--no-sort')) + def test_computer_export_setup(self, tmp_path, file_regression, sort_option): + """Test if `verdi computer export setup` command works""" + self.comp_builder.label = f'test_computer_export_setup{sort_option}' + # Label needs to be unique during parametrization self.comp_builder.transport = 'core.ssh' comp = self.comp_builder.new() comp.store() exported_setup_filename = tmp_path / 'computer-setup.yml' - result = self.cli_runner(computer_export_setup, [sort, comp.label, exported_setup_filename]) - assert result.exit_code == 0, 'Command should have run successfull.' + + # Successfull write behavior + result = self.cli_runner(computer_export_setup, [comp.label, exported_setup_filename, sort_option]) assert str(exported_setup_filename) in result.output, 'Filename should be in terminal output but was not found.' assert exported_setup_filename.exists(), f"'{exported_setup_filename}' was not created during export." + + # file regresssion check + content = exported_setup_filename.read_text() + file_regression.check(content, extension='.yml') + # verifying correctness by comparing internal and loaded yml object configure_setup_data = yaml.safe_load(exported_setup_filename.read_text()) assert configure_setup_data == self.comp_builder.get_computer_spec( comp ), 'Internal computer configuration does not agree with exported one.' + def test_computer_export_setup_overwrite(self, tmp_path): + """Test if overwriting behavior of `verdi computer export setup` command works as expected""" + + self.comp_builder.label = 'test_computer_export_setup' + self.comp_builder.transport = 'core.ssh' + comp = self.comp_builder.new() + comp.store() + + exported_setup_filename = tmp_path / 'computer-setup.yml' + # Check that export fails if the file already exists + exported_setup_filename.touch() + result = self.cli_runner(computer_export_setup, [comp.label, exported_setup_filename], raises=True) + # assert 'already exists, use `--overwrite`' in result.output + + # Create new instance and check that change is reflected in new YAML file output + self.comp_builder.label = 'test_computer_export_setup_local' + self.comp_builder.transport = 'core.local' + comp_local = self.comp_builder.new() + comp_local.store() + result = self.cli_runner(computer_export_setup, [comp_local.label, exported_setup_filename, '--overwrite']) + content = exported_setup_filename.read_text() + assert 'core.local' in content + # we create a directory so we raise an error when exporting with the same name - # to test the except part of the function - already_existing_filename = tmp_path / 'tmp_dir' - already_existing_filename.mkdir() - result = self.cli_runner(computer_export_setup, [sort, comp.label, already_existing_filename], raises=True) - assert result.exit_code == ExitCode.CRITICAL + already_existing_directory = tmp_path / 'tmp_dir' + already_existing_directory.mkdir() + result = self.cli_runner(computer_export_setup, [comp.label, already_existing_directory], raises=True) + assert f'A directory with the name `{already_existing_directory}` already exists.' in result.output + + @pytest.mark.usefixtures('chdir_tmp_path') + def test_computer_export_setup_default_filename(self): + """Test that default filename is as expected when not specified for `verdi computer export setup`.""" + comp_label = 'test_computer_export_setup_default' + self.comp_builder.label = comp_label + # Label needs to be unique during parametrization + self.comp_builder.transport = 'core.ssh' + comp = self.comp_builder.new() + comp.store() - @pytest.mark.parametrize('sort', ['--sort', '--no-sort']) - def test_computer_export_config(self, tmp_path, sort): + exported_setup_filename = f'{comp_label}-setup.yml' + + self.cli_runner(computer_export_setup, [comp.label]) + assert pathlib.Path(exported_setup_filename).is_file() + + def test_computer_export_config(self, tmp_path): """Test if 'verdi computer export config' command works""" - self.comp_builder.label = 'test_computer_export_config' + sort + self.comp_builder.label = 'test_computer_export_config' self.comp_builder.transport = 'core.ssh' comp = self.comp_builder.new() comp.store() exported_config_filename = tmp_path / 'computer-configure.yml' + # We have not configured the computer yet so it should exit with an critical error result = self.cli_runner(computer_export_config, [comp.label, exported_config_filename], raises=True) assert result.exit_code == ExitCode.CRITICAL comp.configure(safe_interval=0.0) + comp.configure(username='aiida') + + # Write sorted output file result = self.cli_runner(computer_export_config, [comp.label, exported_config_filename]) assert 'Success' in result.output, 'Command should have run successfull.' assert ( str(exported_config_filename) in result.output ), 'Filename should be in terminal output but was not found.' assert exported_config_filename.exists(), f"'{exported_config_filename}' was not created during export." + + content = exported_config_filename.read_text() + assert content.startswith('safe_interval: 0.0') + # verifying correctness by comparing internal and loaded yml object configure_config_data = yaml.safe_load(exported_config_filename.read_text()) assert ( configure_config_data == comp.get_configuration() ), 'Internal computer configuration does not agree with exported one.' - # we create a directory so we raise an error when exporting with the same name - # to test the except part of the function - already_existing_filename = tmp_path / 'tmp_dir' - already_existing_filename.mkdir() - result = self.cli_runner(computer_export_config, [comp.label, already_existing_filename], raises=True) - assert result.exit_code == ExitCode.CRITICAL + # Check that unsorted output file creation works as expected + exported_config_filename.unlink() + result = self.cli_runner(computer_export_config, [comp.label, exported_config_filename, '--no-sort']) + assert 'Success' in result.output, 'Command should have run successfull.' + assert ( + str(exported_config_filename) in result.output + ), 'Filename should be in terminal output but was not found.' + assert exported_config_filename.exists(), f"'{exported_config_filename}' was not created during export." - result = self.cli_runner( - computer_export_config, ['--user', self.user.email, comp.label, already_existing_filename], raises=True - ) - assert result.exit_code == ExitCode.CRITICAL + # Check contents + content = exported_config_filename.read_text() + assert 'username: aiida' in content, 'username not in output YAML' + assert 'safe_interval: 0.0' in content, 'safe_interval not in output YAML' + + def test_computer_export_config_overwrite(self, tmp_path): + """Test if overwrite behavior of `verdi computer export config` command works""" + self.comp_builder.label = 'test_computer_export_config_overwrite' + self.comp_builder.transport = 'core.ssh' + comp = self.comp_builder.new() + comp.store() + comp.configure(safe_interval=0.0) + + exported_config_filename = tmp_path / 'computer-configure.yml' + + # Create directory with the same name and check that command fails + exported_config_filename.mkdir() + result = self.cli_runner(computer_export_config, [comp.label, exported_config_filename], raises=True) + assert f'A directory with the name `{exported_config_filename}` already exists' in result.output + exported_config_filename.rmdir() + + # Check that export fails if the file already exists + exported_config_filename.touch() + result = self.cli_runner(computer_export_config, [comp.label, exported_config_filename], raises=True) + assert 'already exists, use `--overwrite`' in result.output + + # Create new instance and check that change is reflected in overwritten YAML output file + self.comp_builder.label = 'test_computer_export_config_0' + comp_mod = self.comp_builder.new() + comp_mod.store() + comp_mod.configure(safe_interval=1.0) + self.cli_runner(computer_export_config, [comp_mod.label, exported_config_filename, '--overwrite']) + content = exported_config_filename.read_text() + assert 'safe_interval: 1.0' in content + + @pytest.mark.usefixtures('chdir_tmp_path') + def test_computer_export_config_default_filename(self): + """Test that default filename is as expected when not specified for `verdi computer export config`.""" + comp_label = 'test_computer_export_config_default' + self.comp_builder.label = comp_label + self.comp_builder.transport = 'core.ssh' + comp = self.comp_builder.new() + comp.store() + comp.configure(safe_interval=0.0) + + exported_config_filename = f'{comp_label}-config.yml' + + self.cli_runner(computer_export_config, [comp.label]) + assert pathlib.Path(exported_config_filename).is_file() class TestVerdiComputerCommands: diff --git a/tests/cmdline/commands/test_computer/test_computer_export_setup___no_sort_.yml b/tests/cmdline/commands/test_computer/test_computer_export_setup___no_sort_.yml new file mode 100644 index 0000000000..7fc3ce33fd --- /dev/null +++ b/tests/cmdline/commands/test_computer/test_computer_export_setup___no_sort_.yml @@ -0,0 +1,13 @@ +label: test_computer_export_setup--no-sort +hostname: localhost +description: Test Computer +transport: core.ssh +scheduler: core.direct +shebang: '#!xonsh' +work_dir: /tmp/aiida +mpirun_command: mpirun +mpiprocs_per_machine: 8 +default_memory_per_machine: 100000 +use_double_quotes: false +prepend_text: '' +append_text: '' diff --git a/tests/cmdline/commands/test_computer/test_computer_export_setup___sort_.yml b/tests/cmdline/commands/test_computer/test_computer_export_setup___sort_.yml new file mode 100644 index 0000000000..a1c7f6d9cc --- /dev/null +++ b/tests/cmdline/commands/test_computer/test_computer_export_setup___sort_.yml @@ -0,0 +1,13 @@ +append_text: '' +default_memory_per_machine: 100000 +description: Test Computer +hostname: localhost +label: test_computer_export_setup--sort +mpiprocs_per_machine: 8 +mpirun_command: mpirun +prepend_text: '' +scheduler: core.direct +shebang: '#!xonsh' +transport: core.ssh +use_double_quotes: false +work_dir: /tmp/aiida diff --git a/tests/cmdline/utils/test_common.py b/tests/cmdline/utils/test_common.py index 863f17d7a4..69a01090df 100644 --- a/tests/cmdline/utils/test_common.py +++ b/tests/cmdline/utils/test_common.py @@ -8,7 +8,11 @@ ########################################################################### """Tests for the :mod:`aiida.cmdline.utils.common` module.""" +from pathlib import Path + +import pytest from aiida.cmdline.utils import common +from aiida.cmdline.utils.common import generate_validate_output_file from aiida.common import LinkType from aiida.engine import Process, calcfunction from aiida.orm import CalcFunctionNode, CalculationNode, WorkflowNode @@ -88,3 +92,38 @@ def test_with_docstring(): common.print_process_info(TestProcessWithDocstring) common.print_process_info(test_without_docstring) common.print_process_info(test_with_docstring) + + +@pytest.mark.usefixtures('chdir_tmp_path') +def test_generate_validate_output(): + test_entity_label = 'test_code' + test_appendix = '@test_computer' + + expected_output_file = Path(f'{test_entity_label}{test_appendix}.yml') + + # Test default label creation + obtained_output_file = generate_validate_output_file( + output_file=None, entity_label=test_entity_label, appendix=test_appendix + ) + assert expected_output_file == obtained_output_file, 'Filenames differ' + + # Test failure if file exists, but overwrite False + expected_output_file.touch() + with pytest.raises(FileExistsError, match='.*use `--overwrite` to overwrite.'): + generate_validate_output_file( + output_file=None, entity_label=test_entity_label, appendix=test_appendix, overwrite=False + ) + + # Test that overwrite does the job + obtained_output_file = generate_validate_output_file( + output_file=None, entity_label=test_entity_label, appendix=test_appendix, overwrite=True + ) + assert expected_output_file == obtained_output_file, 'Overwrite unsuccessful' + expected_output_file.unlink() + + # Test failure if directory exists + expected_output_file.mkdir() + with pytest.raises(IsADirectoryError, match='A directory with the name.*'): + generate_validate_output_file( + output_file=None, entity_label=test_entity_label, appendix=test_appendix, overwrite=False + ) diff --git a/tests/tools/dumping/test_processes.py b/tests/tools/dumping/test_processes.py index aab1a48abb..82e704f4e2 100644 --- a/tests/tools/dumping/test_processes.py +++ b/tests/tools/dumping/test_processes.py @@ -302,9 +302,8 @@ def test_dump_calculation_add(tmp_path, generate_calculation_node_add): # Tests for helper methods -def test_validate_make_dump_path(chdir_tmp_path, tmp_path): - chdir_tmp_path - +@pytest.mark.usefixtures('chdir_tmp_path') +def test_validate_make_dump_path(tmp_path): safeguard_file = node_metadata_file # Path must be provided