From 8bbee9de00d68233b3104cd67b2e15df856a9434 Mon Sep 17 00:00:00 2001
From: Martyn Gigg <martyn.gigg@gmail.com>
Date: Sun, 14 Jan 2018 22:06:26 +0000
Subject: [PATCH] Add elapsed time to script completion status.

Refs #21251
---
 qt/python/mantidqt/utils/async.py             | 35 +++++++++++++------
 qt/python/mantidqt/utils/test/test_async.py   |  9 ++---
 .../mantidqt/widgets/codeeditor/execution.py  |  6 ++--
 .../widgets/codeeditor/interpreter.py         | 19 ++++++----
 4 files changed, 46 insertions(+), 23 deletions(-)

diff --git a/qt/python/mantidqt/utils/async.py b/qt/python/mantidqt/utils/async.py
index fe629a0edf9..d99b75ee617 100644
--- a/qt/python/mantidqt/utils/async.py
+++ b/qt/python/mantidqt/utils/async.py
@@ -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
diff --git a/qt/python/mantidqt/utils/test/test_async.py b/qt/python/mantidqt/utils/test/test_async.py
index 2d52777fae0..17da882670d 100644
--- a/qt/python/mantidqt/utils/test/test_async.py
+++ b/qt/python/mantidqt/utils/test/test_async.py
@@ -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
diff --git a/qt/python/mantidqt/widgets/codeeditor/execution.py b/qt/python/mantidqt/widgets/codeeditor/execution.py
index 998efd1ef5c..4f60040b586 100644
--- a/qt/python/mantidqt/widgets/codeeditor/execution.py
+++ b/qt/python/mantidqt/widgets/codeeditor/execution.py
@@ -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)
diff --git a/qt/python/mantidqt/widgets/codeeditor/interpreter.py b/qt/python/mantidqt/widgets/codeeditor/interpreter.py
index 6d0c2efe8ed..3c1b07cd1bb 100644
--- a/qt/python/mantidqt/widgets/codeeditor/interpreter.py
+++ b/qt/python/mantidqt/widgets/codeeditor/interpreter.py
@@ -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
-- 
GitLab