From e232f946e2b5c1c55c8f8b2c903f05355fe9deea Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Fri, 28 Jun 2019 09:30:17 +0200 Subject: [PATCH] Add the `verdi process call-root` command (#3091) This command will go up the call stack for a given process node and return the root caller, if there is one. --- .../tests/cmdline/commands/test_process.py | 51 +++++++++++++++++++ aiida/cmdline/commands/cmd_process.py | 24 +++++++++ aiida/orm/nodes/process/process.py | 12 ++--- docs/source/verdi/verdi_user_guide.rst | 17 ++++--- 4 files changed, 90 insertions(+), 14 deletions(-) diff --git a/aiida/backends/tests/cmdline/commands/test_process.py b/aiida/backends/tests/cmdline/commands/test_process.py index 75565ea7db..56c0771e93 100644 --- a/aiida/backends/tests/cmdline/commands/test_process.py +++ b/aiida/backends/tests/cmdline/commands/test_process.py @@ -411,6 +411,57 @@ def test_list_worker_slot_warning(self): self.assertTrue(any([warning_phrase in line for line in get_result_lines(result)])) +class TestVerdiProcessCallRoot(AiidaTestCase): + """Tests for `verdi process call-root`.""" + + @classmethod + def setUpClass(cls, *args, **kwargs): + super(TestVerdiProcessCallRoot, cls).setUpClass(*args, **kwargs) + cls.node_root = WorkflowNode() + cls.node_middle = WorkflowNode() + cls.node_terminal = WorkflowNode() + + cls.node_root.store() + + cls.node_middle.add_incoming(cls.node_root, link_type=LinkType.CALL_WORK, link_label='call_middle') + cls.node_middle.store() + + cls.node_terminal.add_incoming(cls.node_middle, link_type=LinkType.CALL_WORK, link_label='call_terminal') + cls.node_terminal.store() + + def setUp(self): + super(TestVerdiProcessCallRoot, self).setUp() + self.cli_runner = CliRunner() + + def test_no_caller(self): + """Test `verdi process call-root` when passing single process without caller.""" + options = [str(self.node_root.pk)] + result = self.cli_runner.invoke(cmd_process.process_call_root, options) + self.assertClickResultNoException(result) + self.assertTrue(len(get_result_lines(result)) == 1) + self.assertIn('No callers found', get_result_lines(result)[0]) + + def test_single_caller(self): + """Test `verdi process call-root` when passing single process with call root.""" + # Both the middle and terminal node should have the `root` node as call root. + for node in [self.node_middle, self.node_terminal]: + options = [str(node.pk)] + result = self.cli_runner.invoke(cmd_process.process_call_root, options) + self.assertClickResultNoException(result) + self.assertTrue(len(get_result_lines(result)) == 1) + self.assertIn(str(self.node_root.pk), get_result_lines(result)[0]) + + def test_multiple_processes(self): + """Test `verdi process call-root` when passing multiple processes.""" + options = [str(self.node_root.pk), str(self.node_middle.pk), str(self.node_terminal.pk)] + result = self.cli_runner.invoke(cmd_process.process_call_root, options) + self.assertClickResultNoException(result) + self.assertTrue(len(get_result_lines(result)) == 3) + self.assertIn('No callers found', get_result_lines(result)[0]) + self.assertIn(str(self.node_root.pk), get_result_lines(result)[1]) + self.assertIn(str(self.node_root.pk), get_result_lines(result)[2]) + + @gen.coroutine def with_timeout(what, timeout=5.0): raise gen.Return((yield gen.with_timeout(datetime.timedelta(seconds=timeout), what))) diff --git a/aiida/cmdline/commands/cmd_process.py b/aiida/cmdline/commands/cmd_process.py index 85d12e763f..5b4dac1940 100644 --- a/aiida/cmdline/commands/cmd_process.py +++ b/aiida/cmdline/commands/cmd_process.py @@ -94,6 +94,30 @@ def process_show(processes): echo.echo(get_node_info(process)) +@verdi_process.command('call-root') +@arguments.PROCESSES() +@decorators.with_dbenv() +def process_call_root(processes): + """Show the root process of the call stack for the given processes.""" + for process in processes: + + caller = process.caller + + if caller is None: + echo.echo('No callers found for Process<{}>'.format(process.pk)) + continue + + while True: + next_caller = caller.caller + + if next_caller is None: + break + + caller = next_caller + + echo.echo('{}'.format(caller.pk)) + + @verdi_process.command('report') @arguments.PROCESSES() @click.option('-i', '--indent-size', type=int, default=2, help='Set the number of spaces to indent each level by.') diff --git a/aiida/orm/nodes/process/process.py b/aiida/orm/nodes/process/process.py index 0b599015c3..3bc2cd90e6 100644 --- a/aiida/orm/nodes/process/process.py +++ b/aiida/orm/nodes/process/process.py @@ -446,11 +446,12 @@ def caller(self): :returns: process node that called this process node instance or None """ - caller = self.get_incoming(link_type=(LinkType.CALL_CALC, LinkType.CALL_WORK)) - if caller: - return caller.first().node - - return None + try: + caller = self.get_incoming(link_type=(LinkType.CALL_CALC, LinkType.CALL_WORK)).one().node + except ValueError: + return None + else: + return caller def validate_incoming(self, source, link_type, link_label): """Validate adding a link of the given type from a given node to ourself. @@ -466,7 +467,6 @@ def validate_incoming(self, source, link_type, link_label): :raise ValueError: if the proposed link is invalid """ super(ProcessNode, self).validate_incoming(source, link_type, link_label) - # if self.is_stored and link_type in [LinkType.INPUT_CALC, LinkType.INPUT_WORK]: if self.is_stored: raise ValueError('attempted to add an input link after the process node was already stored.') diff --git a/docs/source/verdi/verdi_user_guide.rst b/docs/source/verdi/verdi_user_guide.rst index d89f330ed3..d2962f5cbd 100644 --- a/docs/source/verdi/verdi_user_guide.rst +++ b/docs/source/verdi/verdi_user_guide.rst @@ -583,14 +583,15 @@ Below is a list with all available subcommands. --help Show this message and exit. Commands: - kill Kill running processes. - list Show a list of processes that are still running. - pause Pause running processes. - play Play paused processes. - report Show the log report for one or multiple processes. - show Show a summary for one or multiple processes. - status Print the status of the process. - watch Watch the state transitions for a process. + call-root Show the root process of the call stack for the given... + kill Kill running processes. + list Show a list of processes that are still running. + pause Pause running processes. + play Play paused processes. + report Show the log report for one or multiple processes. + show Show a summary for one or multiple processes. + status Print the status of the process. + watch Watch the state transitions for a process. .. _verdi_profile: