From 705a0f583811077410f3089e4a23bde0b2d577b4 Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Tue, 17 Jun 2014 22:26:57 +0400 Subject: [PATCH] Fixed incorrect information on Python frames in MuranoPL stack traces Python frames in mixed stack traces were missing file name and pointed to a line below correct position Change-Id: I335292f40b3b6ea3dbca80b84f1d8dbed9a6581d Fixes: bug #1331113 --- murano/common/engine.py | 2 +- murano/dsl/dsl_exception.py | 4 +- murano/dsl/principal_objects/stack_trace.py | 32 ++++++---------- murano/tests/dsl/meta/ExceptionHandling.yaml | 4 ++ murano/tests/dsl/test_exceptions.py | 40 ++++++++++++++++++++ 5 files changed, 58 insertions(+), 24 deletions(-) diff --git a/murano/common/engine.py b/murano/common/engine.py index 2023ce9a..b21bad7e 100644 --- a/murano/common/engine.py +++ b/murano/common/engine.py @@ -123,7 +123,7 @@ class TaskExecutor(object): self._invoke(exc) except Exception as e: if isinstance(e, dsl_exception.MuranoPlException): - LOG.error(e.format()) + LOG.error('\n' + e.format(prefix=' ')) else: LOG.exception(e) reporter = status_reporter.StatusReporter() diff --git a/murano/dsl/dsl_exception.py b/murano/dsl/dsl_exception.py index c4ec4acf..256875c3 100644 --- a/murano/dsl/dsl_exception.py +++ b/murano/dsl/dsl_exception.py @@ -71,8 +71,8 @@ class MuranoPlException(Exception): else: return self._names - def format(self, prefix=' '): - text = '\n{3}{0}: {1}\n' \ + def format(self, prefix=''): + text = '{3}{0}: {1}\n' \ '{3}Traceback (most recent call last):\n' \ '{2}'.format(self._format_name(), self.message, self.stacktrace.toString(prefix + ' '), prefix) diff --git a/murano/dsl/principal_objects/stack_trace.py b/murano/dsl/principal_objects/stack_trace.py index 9f01821b..90f4e341 100644 --- a/murano/dsl/principal_objects/stack_trace.py +++ b/murano/dsl/principal_objects/stack_trace.py @@ -45,25 +45,15 @@ class StackTrace(murano_object.MuranoObject): frames.reverse() if includeNativeFrames: - class InstructionStub(object): - def __init__(self, title, position): - self._title = title - self.source_file_position = position - - def __str__(self): - return self._title - native_frames = [] for frame in inspect.trace()[1:]: - info = inspect.getframeinfo(frame[0]) - position = yaql_expression.YaqlExpressionFilePosition( - os.path.abspath(info.filename), info.lineno, + location = yaql_expression.YaqlExpressionFilePosition( + os.path.abspath(frame[1]), frame[2], -1, -1, -1, -1, -1) - instruction = InstructionStub( - info.code_context[0].strip(), position) - method = info.function + method = frame[3] native_frames.append({ - 'instruction': instruction, + 'instruction': frame[4][0].strip(), + 'location': location, 'method': method, 'class': None }) @@ -77,6 +67,8 @@ class StackTrace(murano_object.MuranoObject): method = frame['method'] murano_class = frame['class'] location = frame['location'] + if murano_class: + method += ' of class ' + murano_class.name if location: args = ( @@ -86,14 +78,12 @@ class StackTrace(murano_object.MuranoObject): if location.start_column >= 0 else '', method, instruction, - prefix, - '' if not murano_class else murano_class.name + '::' + prefix ) - return '{5}File "{0}", line {1}{2} in method {6}{3}\n' \ + return '{5}File "{0}", line {1}{2} in method {3}\n' \ '{5} {4}'.format(*args) else: - return '{2}File in method {3}{0}\n{2} {1}'.format( - method, instruction, prefix, - '' if not murano_class else murano_class.name + '::') + return '{2}File in method {0}\n{2} {1}'.format( + method, instruction, prefix) return '\n'.join([format_frame(t)for t in self.get_property('frames')]) diff --git a/murano/tests/dsl/meta/ExceptionHandling.yaml b/murano/tests/dsl/meta/ExceptionHandling.yaml index 95799314..46b43c48 100644 --- a/murano/tests/dsl/meta/ExceptionHandling.yaml +++ b/murano/tests/dsl/meta/ExceptionHandling.yaml @@ -49,3 +49,7 @@ Methods: - Return: Value: $enum + testStackTrace: + Body: + raisePythonException() + diff --git a/murano/tests/dsl/test_exceptions.py b/murano/tests/dsl/test_exceptions.py index 8de91ebc..5d1f31fb 100644 --- a/murano/tests/dsl/test_exceptions.py +++ b/murano/tests/dsl/test_exceptions.py @@ -12,6 +12,12 @@ # License for the specific language governing permissions and limitations # under the License. +import inspect +import os.path +import re + +from testtools import matchers + from murano.dsl import dsl_exception from murano.tests.dsl.foundation import object_model as om from murano.tests.dsl.foundation import test_case @@ -20,6 +26,16 @@ from murano.tests.dsl.foundation import test_case class TestExceptions(test_case.DslTestCase): def setUp(self): super(TestExceptions, self).setUp() + + def exception_func(): + exc = LookupError('just random Python exception') + frameinfo = inspect.getframeinfo(inspect.currentframe()) + exc._position = \ + os.path.basename(frameinfo.filename), frameinfo.lineno + 4 + # line below must be exactly 4 lines after currentframe() + raise exc + + self.register_function(exception_func, 'raisePythonException') self._runner = self.new_runner(om.Object('ExceptionHandling')) def test_throw_catch(self): @@ -54,3 +70,27 @@ class TestExceptions(test_case.DslTestCase): self.assertEqual( ['enter try', 'exit try', 'else section', 'finally section'], self.traces) + + def test_stack_trace(self): + self._runner.preserve_exception = True + e = self.assertRaises( + dsl_exception.MuranoPlException, + self._runner.testStackTrace) + call_stack = e.format() + self.assertThat( + call_stack, + matchers.StartsWith( + 'exceptions.LookupError: just random Python exception')) + + self.assertIsInstance(e.original_exception, LookupError) + + filename, line = e.original_exception._position + self.assertThat( + call_stack, + matchers.MatchesRegex( + r'.*^ File \".*ExceptionHandling\.yaml\", ' + r'line \d+:\d+ in method testStackTrace .*' + r'of class ExceptionHandling$.*' + r'^ File \".*{0}\", line {1} ' + r'in method exception_func$.*'.format(filename, line), + re.MULTILINE | re.DOTALL))