Merge "Maintain virtual MuranoPL stack trace"
This commit is contained in:
@@ -35,7 +35,7 @@ def serialize(value, memo=None):
|
||||
result[d_key] = serialize(d_value, memo)
|
||||
return result
|
||||
elif isinstance(value, murano.dsl.murano_object.MuranoObject):
|
||||
if not value.object_id in memo:
|
||||
if value.object_id not in memo:
|
||||
memo.add(value.object_id)
|
||||
return serialize(value.to_dictionary(), memo)
|
||||
else:
|
||||
@@ -47,13 +47,19 @@ def serialize(value, memo=None):
|
||||
|
||||
|
||||
def evaluate(value, context, max_depth=sys.maxint):
|
||||
if isinstance(value, (yaql_expression.YaqlExpression,
|
||||
yaql.expressions.Expression)):
|
||||
if isinstance(value, yaql.expressions.Expression):
|
||||
value = yaql_expression.YaqlExpression(value)
|
||||
|
||||
if isinstance(value, yaql_expression.YaqlExpression):
|
||||
func = lambda: evaluate(value.evaluate(context), context, 1)
|
||||
if max_depth <= 0:
|
||||
return func
|
||||
else:
|
||||
return func()
|
||||
try:
|
||||
context.set_data(value, '?currentInstruction')
|
||||
return func()
|
||||
finally:
|
||||
context.set_data(None, '?currentInstruction')
|
||||
|
||||
elif isinstance(value, types.DictionaryType):
|
||||
result = {}
|
||||
@@ -178,3 +184,11 @@ def get_caller_context(context):
|
||||
|
||||
def get_attribute_store(context):
|
||||
return context.get_data('$?attributeStore')
|
||||
|
||||
|
||||
def get_current_instruction(context):
|
||||
return context.get_data('$?currentInstruction')
|
||||
|
||||
|
||||
def get_current_method(context):
|
||||
return context.get_data('$?currentMethod')
|
||||
|
@@ -42,14 +42,21 @@ class CodeBlock(expressions.DslExpression):
|
||||
|
||||
|
||||
class MethodBlock(CodeBlock):
|
||||
def __init__(self, body, name=None):
|
||||
super(MethodBlock, self).__init__(body)
|
||||
self._name = name
|
||||
|
||||
def execute(self, context, murano_class):
|
||||
try:
|
||||
context.set_data(self._name, '?currentMethod')
|
||||
super(MethodBlock, self).execute(
|
||||
context, murano_class)
|
||||
except exceptions.ReturnException as e:
|
||||
return e.value
|
||||
else:
|
||||
return None
|
||||
finally:
|
||||
context.set_data(None, '?currentMethod')
|
||||
|
||||
|
||||
class ReturnMacro(expressions.DslExpression):
|
||||
|
@@ -101,6 +101,15 @@ class MuranoClass(object):
|
||||
if initial and not yielded:
|
||||
yield initial
|
||||
|
||||
def find_method(self, name):
|
||||
if name in self._methods:
|
||||
return [(self, name)]
|
||||
if not self._parents:
|
||||
return []
|
||||
return list(set(reduce(
|
||||
lambda x, y: x + y,
|
||||
[p.find_method(name) for p in self._parents])))
|
||||
|
||||
def find_single_method(self, name):
|
||||
chains = sorted(self._find_method_chains(name), key=lambda t: len(t))
|
||||
result = []
|
||||
|
@@ -51,7 +51,7 @@ class MuranoMethod(object):
|
||||
MethodUsages.Runtime)
|
||||
else:
|
||||
payload = payload or {}
|
||||
self._body = self._prepare_body(payload.get('Body') or [])
|
||||
self._body = self._prepare_body(payload.get('Body') or [], name)
|
||||
self._usage = payload.get('Usage') or MethodUsages.Runtime
|
||||
arguments_scheme = payload.get('Arguments') or []
|
||||
if isinstance(arguments_scheme, types.DictionaryType):
|
||||
@@ -105,8 +105,8 @@ class MuranoMethod(object):
|
||||
del result['_context']
|
||||
return result
|
||||
|
||||
def _prepare_body(self, body):
|
||||
return macros.MethodBlock(body)
|
||||
def _prepare_body(self, body, name):
|
||||
return macros.MethodBlock(body, name)
|
||||
|
||||
def __repr__(self):
|
||||
return 'MuranoMethod({0}::{1})'.format(
|
||||
|
66
murano/dsl/virtual_stack.py
Normal file
66
murano/dsl/virtual_stack.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# Copyright (c) 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os.path
|
||||
|
||||
import yaql.context
|
||||
|
||||
import murano.dsl.helpers as helpers
|
||||
|
||||
|
||||
@yaql.context.ContextAware()
|
||||
def stack_trace(context):
|
||||
frames = []
|
||||
while True:
|
||||
context = helpers.get_caller_context(context)
|
||||
if not context:
|
||||
break
|
||||
instruction = helpers.get_current_instruction(context)
|
||||
frames.append({
|
||||
'instruction': None if instruction is None else str(instruction),
|
||||
'location': None if instruction is None
|
||||
else instruction.file_position,
|
||||
'method': helpers.get_current_method(context),
|
||||
'class': helpers.get_type(context)
|
||||
})
|
||||
frames.pop()
|
||||
return frames
|
||||
|
||||
|
||||
def format_stack_trace_yaql(trace):
|
||||
return format_stack_trace(trace, '')
|
||||
|
||||
|
||||
def format_stack_trace(trace, prefix=''):
|
||||
def format_frame(frame):
|
||||
instruction = frame['instruction']
|
||||
method = frame['method']
|
||||
murano_class = frame['class']
|
||||
location = frame['location']
|
||||
|
||||
if location:
|
||||
return '{5}File "{0}" at {1}:{2} in method {3} of class {6}\n' \
|
||||
'{5} {4}'.format(
|
||||
os.path.abspath(location.file_path),
|
||||
location.start_line,
|
||||
location.start_column,
|
||||
method,
|
||||
instruction,
|
||||
prefix,
|
||||
murano_class.name)
|
||||
else:
|
||||
return '{2}File <unknown> in method {0}\n{2} {1}'.format(
|
||||
method, instruction, prefix)
|
||||
|
||||
return '\n'.join([format_frame(t)for t in trace()])
|
@@ -17,16 +17,38 @@ import types
|
||||
|
||||
import yaql
|
||||
import yaql.exceptions
|
||||
import yaql.expressions
|
||||
|
||||
|
||||
class YaqlExpression(object):
|
||||
def __init__(self, expression):
|
||||
self._expression = str(expression)
|
||||
self._parsed_expression = yaql.parse(self._expression)
|
||||
if isinstance(expression, types.StringTypes):
|
||||
self._expression = str(expression)
|
||||
self._parsed_expression = yaql.parse(self._expression)
|
||||
self._file_position = None
|
||||
elif isinstance(expression, YaqlExpression):
|
||||
self._expression = expression._expression
|
||||
self._parsed_expression = expression._parsed_expression
|
||||
self._file_position = expression._file_position
|
||||
elif isinstance(expression, yaql.expressions.Expression):
|
||||
self._expression = str(expression)
|
||||
self._parsed_expression = expression
|
||||
self._file_position = None
|
||||
else:
|
||||
raise TypeError('expression is not of supported types')
|
||||
|
||||
@property
|
||||
def expression(self):
|
||||
return self._expression
|
||||
|
||||
@property
|
||||
def file_position(self):
|
||||
return self._file_position
|
||||
|
||||
@file_position.setter
|
||||
def file_position(self, value):
|
||||
self._file_position = value
|
||||
|
||||
def __repr__(self):
|
||||
return 'YAQL(%s)' % self._expression
|
||||
|
||||
@@ -49,3 +71,43 @@ class YaqlExpression(object):
|
||||
|
||||
def evaluate(self, context=None):
|
||||
return self._parsed_expression.evaluate(context=context)
|
||||
|
||||
|
||||
class YaqlExpressionFilePosition(object):
|
||||
def __init__(self, file_path, start_line, start_column, start_index,
|
||||
end_line, end_column, length):
|
||||
self._file_path = file_path
|
||||
self._start_line = start_line
|
||||
self._start_column = start_column
|
||||
self._start_index = start_index
|
||||
self._end_line = end_line
|
||||
self._end_column = end_column
|
||||
self._length = length
|
||||
|
||||
@property
|
||||
def file_path(self):
|
||||
return self._file_path
|
||||
|
||||
@property
|
||||
def start_line(self):
|
||||
return self._start_line
|
||||
|
||||
@property
|
||||
def start_column(self):
|
||||
return self._start_column
|
||||
|
||||
@property
|
||||
def start_index(self):
|
||||
return self._start_index
|
||||
|
||||
@property
|
||||
def end_line(self):
|
||||
return self._end_line
|
||||
|
||||
@property
|
||||
def end_column(self):
|
||||
return self._end_column
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
return self._length
|
||||
|
@@ -30,7 +30,17 @@ YaqlYamlLoader.yaml_implicit_resolvers = resolvers
|
||||
|
||||
def yaql_constructor(loader, node):
|
||||
value = loader.construct_scalar(node)
|
||||
return yaql_expression.YaqlExpression(value)
|
||||
result = yaql_expression.YaqlExpression(value)
|
||||
position = yaql_expression.YaqlExpressionFilePosition(
|
||||
node.start_mark.name,
|
||||
node.start_mark.line + 1,
|
||||
node.start_mark.column + 1,
|
||||
node.start_mark.index,
|
||||
node.end_mark.line + 1,
|
||||
node.end_mark.column + 1,
|
||||
node.end_mark.index - node.start_mark.index)
|
||||
result.file_position = position
|
||||
return result
|
||||
|
||||
yaml.add_constructor(u'!yaql', yaql_constructor, YaqlYamlLoader)
|
||||
yaml.add_implicit_resolver(u'!yaql', yaql_expression.YaqlExpression,
|
||||
|
@@ -453,11 +453,12 @@ class TestHelperFunctions(base.MuranoTestCase):
|
||||
# tuple(evaluate(list)) transformation adds + 1
|
||||
complex_literal_depth = 3 + 1
|
||||
|
||||
evaluated_value = helpers.evaluate(yaql_value, None, 1)
|
||||
non_evaluated_value = helpers.evaluate(yaql_value, None, 0)
|
||||
evaluated_complex_value = helpers.evaluate(complex_value, None)
|
||||
context = yaql.create_context(False)
|
||||
evaluated_value = helpers.evaluate(yaql_value, context, 1)
|
||||
non_evaluated_value = helpers.evaluate(yaql_value, context, 0)
|
||||
evaluated_complex_value = helpers.evaluate(complex_value, context)
|
||||
non_evaluated_complex_value = helpers.evaluate(
|
||||
complex_value, None, complex_literal_depth)
|
||||
complex_value, context, complex_literal_depth)
|
||||
|
||||
self.assertEqual('atom', evaluated_value)
|
||||
self.assertNotEqual('atom', non_evaluated_value)
|
||||
@@ -484,7 +485,7 @@ class TestYaqlExpression(base.MuranoTestCase):
|
||||
def test_expression(self):
|
||||
yaql_expr = yaql_expression.YaqlExpression('string')
|
||||
|
||||
self.assertEqual('string', yaql_expr.expression())
|
||||
self.assertEqual('string', yaql_expr.expression)
|
||||
|
||||
def test_evaluate_calls(self):
|
||||
string = 'string'
|
||||
|
Reference in New Issue
Block a user