diff --git a/src/aiida_shell/launch.py b/src/aiida_shell/launch.py index 8b53865..15e8d47 100644 --- a/src/aiida_shell/launch.py +++ b/src/aiida_shell/launch.py @@ -32,6 +32,7 @@ def launch_shell_job( # noqa: PLR0913 metadata: dict[str, t.Any] | None = None, submit: bool = False, resolve_command: bool = True, + monitors: dict[str, Data] | None = None, ) -> tuple[dict[str, Data], ProcessNode]: """Launch a :class:`aiida_shell.ShellJob` job for the given command. @@ -52,6 +53,8 @@ def launch_shell_job( # noqa: PLR0913 :param resolve_command: Whether to resolve the command to the absolute path of the executable. If set to ``True``, the ``which`` command is executed on the target computer to attempt and determine the absolute path. Otherwise, the command is set as the ``filepath_executable`` attribute of the created ``AbstractCode`` instance. + :param monitors: Optional dictionary of ``Data`` nodes to be used as monitors for the job (see AiiDA + documentation on how to define monitors). :raises TypeError: If the value specified for ``metadata.options.computer`` is not a ``Computer``. :raises ValueError: If ``resolve_command=True`` and the absolute path of the command on the computer could not be determined. @@ -69,6 +72,7 @@ def launch_shell_job( # noqa: PLR0913 parser=parser, metadata=metadata, resolve_command=resolve_command, + monitors=monitors, ) if submit: @@ -91,6 +95,7 @@ def prepare_shell_job_inputs( # noqa: PLR0913 parser: ParserFunctionType | str | None = None, metadata: dict[str, t.Any] | None = None, resolve_command: bool = True, + monitors: dict[str, Data] | None = None, ) -> dict[str, t.Any]: """Prepare inputs for the ShellJob based on the provided parameters. @@ -110,6 +115,8 @@ def prepare_shell_job_inputs( # noqa: PLR0913 :param resolve_command: Whether to resolve the command to the absolute path of the executable. If set to ``True``, the ``which`` command is executed on the target computer to attempt and determine the absolute path. Otherwise, the command is set as the ``filepath_executable`` attribute of the created ``AbstractCode`` instance. + :param monitors: Optional dictionary of ``Data`` nodes to be used as monitors for the job (see AiiDA + documentation on how to define monitors). :raises TypeError: If the value specified for ``metadata.options.computer`` is not a ``Computer``. :raises ValueError: If ``resolve_command=True`` and the absolute path of the command on the computer could not be determined. @@ -148,6 +155,8 @@ def prepare_shell_job_inputs( # noqa: PLR0913 'parser': parser, 'metadata': metadata or {}, } + if monitors: + inputs['monitors'] = monitors return inputs diff --git a/src/aiida_shell/parsers/shell.py b/src/aiida_shell/parsers/shell.py index 4b701e7..fef695e 100644 --- a/src/aiida_shell/parsers/shell.py +++ b/src/aiida_shell/parsers/shell.py @@ -84,7 +84,7 @@ def parse_default_outputs(self, dirpath: pathlib.Path) -> ExitCode: except FileNotFoundError: stderr = '' else: - stderr = node_stderr.get_content() # type: ignore[assignment] + stderr = node_stderr.get_content(mode='r') self.out(ShellJob.FILENAME_STDERR, node_stderr) filename_stdout = self.node.get_option('output_filename') or ShellJob.FILENAME_STDOUT diff --git a/tests/calculations/test_shell.py b/tests/calculations/test_shell.py index c1b6531..96318c5 100644 --- a/tests/calculations/test_shell.py +++ b/tests/calculations/test_shell.py @@ -286,7 +286,11 @@ def test_output_filename(generate_calc_job, generate_code, file_regression): def test_filename_stdin(generate_calc_job, generate_code, file_regression): """Test the ``metadata.options.filename_stdin`` input.""" inputs = { - 'code': generate_code('cat'), + # even if 'cat' would be more natural, we use `diff` to avoid issues + # in this specific test, because the exact path of the `cat` binary + # may differ across systems (Linux vs. MacOS), while `diff` seems + # to be (by default) more consistent (in /usr/bin). + 'code': generate_code('diff'), 'arguments': List(['{filename}']), 'nodes': {'filename': SinglefileData.from_string('content')}, 'metadata': {'options': {'filename_stdin': 'filename'}}, diff --git a/tests/calculations/test_shell/test_filename_stdin.txt b/tests/calculations/test_shell/test_filename_stdin.txt index 6fdccec..18521f0 100644 --- a/tests/calculations/test_shell/test_filename_stdin.txt +++ b/tests/calculations/test_shell/test_filename_stdin.txt @@ -3,6 +3,6 @@ exec > _scheduler-stdout.txt exec 2> _scheduler-stderr.txt -'/usr/bin/cat' < 'filename' > 'stdout' 2> 'stderr' +'/usr/bin/diff' < 'filename' > 'stdout' 2> 'stderr' echo $? > status diff --git a/tests/conftest.py b/tests/conftest.py index 46a8ac0..5dd2df4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ import collections import pathlib +import shutil import tempfile import typing as t import uuid @@ -170,8 +171,10 @@ def factory(label='localhost', hostname='localhost', scheduler_type='core.direct @pytest.fixture def generate_code(generate_computer): """Return a :class:`aiida_shell.data.code.ShellCode` instance, either already existing or created.""" + default_command = shutil.which('true') # /bin/true on Linux, /usr/bin/true on macOS + assert default_command is not None, 'The `true` command must be available on the system for the tests to run.' - def factory(command='/bin/true', computer_label='localhost', label=None, entry_point_name='core.shell'): + def factory(command=default_command, computer_label='localhost', label=None, entry_point_name='core.shell'): """Return a :class:`aiida_shell.data.code.ShellCode` instance, either already existing or created.""" label = label or str(uuid.uuid4()) computer = generate_computer(computer_label) diff --git a/tests/test_launch.py b/tests/test_launch.py index dfbe236..974aae7 100644 --- a/tests/test_launch.py +++ b/tests/test_launch.py @@ -10,6 +10,9 @@ from aiida_shell.calculations.shell import ShellJob from aiida_shell.launch import launch_shell_job, prepare_computer +DATE_COMMAND = shutil.which('date') +assert DATE_COMMAND is not None, 'The `date` command must be available in order to run the tests.' + class ShellWorkChain(WorkChain): """Implementation of :class:`aiida.engine.processes.workchains.workchain.WorkChain` that submits a ``ShellJob``.""" @@ -86,7 +89,7 @@ def test_arguments(): shellfunction runs just before midnight and the comparison ``datetime`` call runs in the next day causing the test to fail, but that seems extremely unlikely. """ - arguments = ['--iso-8601'] + arguments = ['-I'] # equivalent to --iso-8601, but supported also on MacOS results, node = launch_shell_job('date', arguments=arguments) assert node.is_finished_ok @@ -273,7 +276,7 @@ def job_function(): @pytest.mark.parametrize( 'resolve_command, executable', ( - (True, '/usr/bin/date'), + (True, DATE_COMMAND), (False, 'date'), ), )