Skip to content
Snippets Groups Projects
Commit 8bbee9de authored by Martyn Gigg's avatar Martyn Gigg
Browse files

Add elapsed time to script completion status.

Refs #21251
parent 4f0d41e9
No related merge requests found
......@@ -14,7 +14,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
from __future__ import (absolute_import, unicode_literals)
# system imports
import sys
......@@ -93,25 +93,37 @@ class AsyncTask(threading.Thread):
self.finished_cb = finished_cb if finished_cb is not None else lambda: None
def run(self):
def elapsed(start):
return time.time() - start
try:
time_start = time.time()
out = self.target(*self.args, **self.kwargs)
except SyntaxError as exc:
# treat SyntaxErrors as special as the traceback makes no sense
# and the lineno is part of the exception instance
self.error_cb(AsyncTaskFailure(SyntaxError, exc, None))
self.error_cb(AsyncTaskFailure(elapsed(time_start), SyntaxError, exc, None))
except: # noqa
self.error_cb(AsyncTaskFailure.from_excinfo(self.stack_chop))
self.error_cb(AsyncTaskFailure.from_excinfo(elapsed(time_start), self.stack_chop))
else:
self.success_cb(AsyncTaskSuccess(out))
self.success_cb(AsyncTaskSuccess(elapsed(time_start), out))
self.finished_cb()
class AsyncTaskSuccess(object):
class AsyncTaskResult(object):
"""Object describing the execution of an asynchronous task
"""
def __init__(self, elapsed_time):
self.elapsed_time = elapsed_time
class AsyncTaskSuccess(AsyncTaskResult):
"""Object describing the successful execution of an asynchronous task
"""
def __init__(self, output):
def __init__(self, elapsed_time, output):
super(AsyncTaskSuccess, self).__init__(elapsed_time)
self.output = output
@property
......@@ -119,23 +131,26 @@ class AsyncTaskSuccess(object):
return True
class AsyncTaskFailure(object):
class AsyncTaskFailure(AsyncTaskResult):
"""Object describing the failed execution of an asynchronous task
"""
@staticmethod
def from_excinfo(chop=0):
def from_excinfo(elapsed_time, chop=0):
"""
Create an AsyncTaskFailure from the current exception info
:param elapsed_time Time take for task
:param chop: Trim this number of entries from
the top of the stack listing
:return: A new AsyncTaskFailure object
"""
exc_type, exc_value, exc_tb = sys.exc_info()
return AsyncTaskFailure(exc_type, exc_value, traceback.extract_tb(exc_tb)[chop:])
return AsyncTaskFailure(elapsed_time, exc_type, exc_value,
traceback.extract_tb(exc_tb)[chop:])
def __init__(self, exc_type, exc_value, stack):
def __init__(self, elapsed_time, exc_type, exc_value, stack):
super(AsyncTaskFailure, self).__init__(elapsed_time)
self.exc_type = exc_type
self.exc_value = exc_value
self.stack = stack
......
......@@ -97,6 +97,7 @@ class AsyncTaskTest(unittest.TestCase):
def test_unsuccessful_no_arg_operation_calls_error_and_finished_callback(self):
def foo():
# this is a bad operation
# that should appear in the stack trace
raise RuntimeError("Bad operation")
recv = AsyncTaskTest.Receiver()
......@@ -112,9 +113,9 @@ class AsyncTaskTest(unittest.TestCase):
msg="Expected RuntimeError, found " + recv.task_exc.__class__.__name__)
self.assertEqual(2, len(recv.task_exc_stack))
# line number of self.target in async.py
self.assertEqual(97, recv.task_exc_stack[0][1])
self.assertEqual(100, recv.task_exc_stack[0][1])
# line number of raise statement above
self.assertEqual(100, recv.task_exc_stack[1][1])
self.assertEqual(101, recv.task_exc_stack[1][1])
def test_unsuccessful_args_and_kwargs_operation_calls_error_and_finished_callback(self):
def foo(scale, shift):
......@@ -147,8 +148,8 @@ class AsyncTaskTest(unittest.TestCase):
self.assertTrue(recv.error_cb_called)
self.assertTrue(isinstance(recv.task_exc, RuntimeError))
self.assertEqual(2, len(recv.task_exc_stack))
self.assertEqual(139, recv.task_exc_stack[0][1])
self.assertEqual(138, recv.task_exc_stack[1][1])
self.assertEqual(140, recv.task_exc_stack[0][1])
self.assertEqual(139, recv.task_exc_stack[1][1])
# ---------------------------------------------------------------
# Failure cases
......
......@@ -31,7 +31,7 @@ class PythonCodeExecution(QObject):
strings of Python code. It supports
reporting progress updates in asynchronous execution
"""
sig_exec_success = Signal()
sig_exec_success = Signal(object)
sig_exec_error = Signal(object)
sig_exec_progress = Signal(int)
......@@ -89,8 +89,8 @@ class PythonCodeExecution(QObject):
# ---------------------------------------------------------------
# Callbacks
# ---------------------------------------------------------------
def _on_success(self, _):
self.sig_exec_success.emit()
def _on_success(self, task_result):
self.sig_exec_success.emit(task_result)
def _on_error(self, task_error):
self.sig_exec_error.emit(task_error)
......
......@@ -30,7 +30,8 @@ from mantidqt.widgets.codeeditor.errorformatter import ErrorFormatter
from mantidqt.widgets.codeeditor.execution import PythonCodeExecution
# Status messages
IDLE_STATUS_MSG = "Status: Idle"
IDLE_STATUS_MSG = "Status: Idle."
LAST_JOB_MSG_TEMPLATE = "Last job completed {} in {:.3f}s"
RUNNING_STATUS_MSG = "Status: Running"
# Editor
......@@ -128,9 +129,10 @@ class PythonFileInterpreterPresenter(QObject):
line_from = 0
return code_str, line_from
def _on_exec_success(self):
def _on_exec_success(self, task_result):
self.view.set_editor_readonly(False)
self.view.set_status_message(IDLE_STATUS_MSG)
self.view.set_status_message(self._create_status_msg('successfully',
task_result.elapsed_time))
def _on_exec_error(self, task_error):
exc_type, exc_value, exc_stack = task_error.exc_type, task_error.exc_value, \
......@@ -139,11 +141,16 @@ class PythonFileInterpreterPresenter(QObject):
lineno = exc_value.lineno
else:
lineno = exc_stack[-1][1]
self.view.editor.updateProgressMarker(lineno, True)
sys.stderr.write(self._error_formatter.format(exc_type, exc_value, exc_stack) + '\n')
self.view.editor.updateProgressMarker(lineno, True)
self.view.set_editor_readonly(False)
self.view.set_status_message(IDLE_STATUS_MSG)
self.view.set_status_message(self._create_status_msg('with errors',
task_error.elapsed_time))
def _create_status_msg(self, status, elapsed_time):
return IDLE_STATUS_MSG + ' ' + \
LAST_JOB_MSG_TEMPLATE.format(status,
elapsed_time)
def _on_progress_update(self, lineno):
"""Update progress on the view taking into account if a selection of code is
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment