Skip to content

FIX: Provide more runtime information when node execution fails #3505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions nipype/pipeline/engine/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,9 +750,25 @@ def _run_command(self, execute, copyfiles=True):
)

if exc_tb:
raise NodeExecutionError(
f"Exception raised while executing Node {self.name}.\n\n{result.runtime.traceback}"
)
runtime = result.runtime

def _tab(text):
from textwrap import indent

if not text:
return ""
return indent(text, '\t')

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

Expand Down
43 changes: 43 additions & 0 deletions nipype/pipeline/engine/tests/test_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -334,3 +335,45 @@ 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(tmp_path)

# 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