From 634afcf8410c50902f0701a4230ec7efaf04e51d Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 14 Sep 2022 11:51:05 -0400 Subject: [PATCH 1/5] FIX: Provide more runtime information when node execution fails --- nipype/pipeline/engine/nodes.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index 20fe360504..9e1a349295 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -750,8 +750,17 @@ def _run_command(self, execute, copyfiles=True): ) if exc_tb: + runtime = result.runtime + def _tab(field): + from textwrap import indent + return indent(field, '\t') + raise NodeExecutionError( - f"Exception raised while executing Node {self.name}.\n\n{result.runtime.traceback}" + f"Exception raised while executing Node {self.name}.\n\n" + f"Cmdline:\n{_tab(runtime.cmdline)}\n" + f"Stdout:\n{_tab(runtime.stdout)}\n" + f"Stderr:\n{_tab(runtime.stderr)}\n" + f"Traceback:\n{_tab(runtime.traceback)}" ) return result From d5e0a51a0749f17a4fd9dd2b78e8f6dd400b2118 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 14 Sep 2022 12:24:15 -0400 Subject: [PATCH 2/5] FIX: Only pass along cmdline if available --- nipype/pipeline/engine/nodes.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index 9e1a349295..b5ae47cc0b 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -751,17 +751,22 @@ def _run_command(self, execute, copyfiles=True): if exc_tb: runtime = result.runtime + def _tab(field): from textwrap import indent + return indent(field, '\t') - raise NodeExecutionError( - f"Exception raised while executing Node {self.name}.\n\n" - f"Cmdline:\n{_tab(runtime.cmdline)}\n" - f"Stdout:\n{_tab(runtime.stdout)}\n" - f"Stderr:\n{_tab(runtime.stderr)}\n" - f"Traceback:\n{_tab(runtime.traceback)}" - ) + msg = f"Exception raised while executing Node {self.name}.\n\n" + if hasattr(runtime, 'cmdline'): + msg += ( + f"Cmdline:\n{_tab(runtime.cmdline)}\n" + f"Stdout:\n{_tab(runtime.stdout)}\n" + f"Stderr:\n{_tab(runtime.stderr)}\n" + ) + # Always pass along the traceback + msg += f"Traceback:\n{_tab(runtime.traceback)}" + raise NodeExecutionError(msg) return result From 171c4875539ef42082d7b265adde5c3ed9d9518c Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 14 Sep 2022 14:06:07 -0400 Subject: [PATCH 3/5] FIX: Do nothing if no text is present --- nipype/pipeline/engine/nodes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index b5ae47cc0b..f897336519 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -752,10 +752,11 @@ def _run_command(self, execute, copyfiles=True): if exc_tb: runtime = result.runtime - def _tab(field): + def _tab(text): from textwrap import indent - - return indent(field, '\t') + if not text: + return "" + return indent(text, '\t') msg = f"Exception raised while executing Node {self.name}.\n\n" if hasattr(runtime, 'cmdline'): From 203dd896f43c1129f8ebda6dd0324772f18fb14f Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 14 Sep 2022 14:06:35 -0400 Subject: [PATCH 4/5] TST: Add test --- nipype/pipeline/engine/tests/test_nodes.py | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/nipype/pipeline/engine/tests/test_nodes.py b/nipype/pipeline/engine/tests/test_nodes.py index f5e2d5016c..c29caa85a5 100644 --- a/nipype/pipeline/engine/tests/test_nodes.py +++ b/nipype/pipeline/engine/tests/test_nodes.py @@ -7,6 +7,7 @@ from .... import config from ....interfaces import utility as niu +from ....interfaces import base as nib from ... import engine as pe from ..utils import merge_dict from .test_base import EngineTestInterface @@ -334,3 +335,44 @@ def _producer(num=1, deadly_num=7): wf.base_dir = os.path.abspath("./test_output") with pytest.raises(RuntimeError): wf.run(plugin="MultiProc") + + +class FailCommandLine(nib.CommandLine): + input_spec = nib.CommandLineInputSpec + output_spec = nib.TraitedSpec + _cmd = 'nipype-node-execution-fail' + + +def test_NodeExecutionError(tmp_path, monkeypatch): + import stat + + monkeypatch.chdir('.') + + # create basic executable and add to PATH + exebin = tmp_path / 'bin' + exebin.mkdir() + exe = exebin / 'nipype-node-execution-fail' + exe.write_text('#!/bin/bash\necho "Running"\necho "This should fail" >&2\nexit 1') + exe.chmod(exe.stat().st_mode | stat.S_IEXEC) + monkeypatch.setenv("PATH", str(exe.parent.absolute()), prepend=os.pathsep) + + # Test with cmdline interface + cmd = pe.Node(FailCommandLine(), name="cmd-fail", base_dir='cmd') + with pytest.raises(pe.nodes.NodeExecutionError) as exc: + cmd.run() + error_msg = str(exc.value) + + for attr in ("Cmdline:", "Stdout:", "Stderr:", "Traceback:"): + assert attr in error_msg + assert "This should fail" in error_msg + + # Test with function interface + def fail(): + raise Exception("Functions can fail too") + func = pe.Node(niu.Function(function=fail), name='func-fail', base_dir='func') + with pytest.raises(pe.nodes.NodeExecutionError) as exc: + func.run() + error_msg = str(exc.value) + assert "Traceback:" in error_msg + assert "Cmdline:" not in error_msg + assert "Functions can fail too" in error_msg From f4a779223c6b0dffa47138d24ec9ef378c7164a9 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 14 Sep 2022 14:12:07 -0400 Subject: [PATCH 5/5] STY: Black --- nipype/pipeline/engine/nodes.py | 1 + nipype/pipeline/engine/tests/test_nodes.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index f897336519..b1f2a662b2 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -754,6 +754,7 @@ def _run_command(self, execute, copyfiles=True): def _tab(text): from textwrap import indent + if not text: return "" return indent(text, '\t') diff --git a/nipype/pipeline/engine/tests/test_nodes.py b/nipype/pipeline/engine/tests/test_nodes.py index c29caa85a5..44da004620 100644 --- a/nipype/pipeline/engine/tests/test_nodes.py +++ b/nipype/pipeline/engine/tests/test_nodes.py @@ -346,7 +346,7 @@ class FailCommandLine(nib.CommandLine): def test_NodeExecutionError(tmp_path, monkeypatch): import stat - monkeypatch.chdir('.') + monkeypatch.chdir(tmp_path) # create basic executable and add to PATH exebin = tmp_path / 'bin' @@ -369,6 +369,7 @@ def test_NodeExecutionError(tmp_path, monkeypatch): # Test with function interface def fail(): raise Exception("Functions can fail too") + func = pe.Node(niu.Function(function=fail), name='func-fail', base_dir='func') with pytest.raises(pe.nodes.NodeExecutionError) as exc: func.run()