Merge "Adds ability to throw/catch/rethrow exceptions in MuranoPL"
This commit is contained in:
commit
5369506800
|
@ -0,0 +1,29 @@
|
|||
Namespaces:
|
||||
=: io.murano
|
||||
|
||||
Name: Exception
|
||||
|
||||
Properties:
|
||||
name:
|
||||
Contract: $.string()
|
||||
Usage: Runtime
|
||||
|
||||
message:
|
||||
Contract: $.string()
|
||||
Usage: Runtime
|
||||
|
||||
stackTrace:
|
||||
Contract: $
|
||||
Usage: Runtime
|
||||
|
||||
extra:
|
||||
Contract: {}
|
||||
Usage: Runtime
|
||||
|
||||
nativeException:
|
||||
Contract: $
|
||||
Usage: Runtime
|
||||
|
||||
cause:
|
||||
Contract: $.class(Exception)
|
||||
Usage: Runtime
|
|
@ -0,0 +1,12 @@
|
|||
Namespaces:
|
||||
=: io.murano
|
||||
|
||||
Name: StackTrace
|
||||
|
||||
Properties:
|
||||
frames:
|
||||
Contract:
|
||||
- instruction: $.string()
|
||||
location: $
|
||||
method: $
|
||||
class: $
|
|
@ -17,6 +17,8 @@ Classes:
|
|||
io.murano.Object: Object.yaml
|
||||
io.murano.Environment: Environment.yaml
|
||||
io.murano.Application: Application.yaml
|
||||
io.murano.Exception: Exception.yaml
|
||||
io.murano.StackTrace: StackTrace.yaml
|
||||
io.murano.SharedIp: SharedIp.yaml
|
||||
|
||||
io.murano.system.SecurityGroupManager: SecurityGroupManager.yaml
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import uuid
|
||||
|
||||
import anyjson
|
||||
import eventlet.debug
|
||||
from oslo import messaging
|
||||
from oslo.messaging import target
|
||||
|
||||
|
@ -24,6 +25,7 @@ from murano.common.helpers import token_sanitizer
|
|||
from murano.common import rpc
|
||||
from murano.dsl import executor
|
||||
from murano.dsl import results_serializer
|
||||
from murano.dsl import virtual_exceptions
|
||||
from murano.engine import environment
|
||||
from murano.engine import package_class_loader
|
||||
from murano.engine import package_loader
|
||||
|
@ -36,6 +38,8 @@ RPC_SERVICE = None
|
|||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
eventlet.debug.hub_exceptions(False)
|
||||
|
||||
|
||||
class TaskProcessingEndpoint(object):
|
||||
@staticmethod
|
||||
|
@ -118,7 +122,10 @@ class TaskExecutor(object):
|
|||
if self.action:
|
||||
self._invoke(exc)
|
||||
except Exception as e:
|
||||
LOG.warn(e, exc_info=1)
|
||||
if isinstance(e, virtual_exceptions.MuranoPlException):
|
||||
LOG.error(e.format())
|
||||
else:
|
||||
LOG.exception(e)
|
||||
reporter = status_reporter.StatusReporter()
|
||||
reporter.initialize(obj)
|
||||
reporter.report_error(obj, str(e))
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# 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 murano.dsl.yaql_functions as yaql_functions
|
||||
|
||||
|
||||
class MuranoPlException(Exception):
|
||||
def __init__(self, names, message, stacktrace, extra=None, cause=None):
|
||||
super(MuranoPlException, self).__init__(
|
||||
'{0}: {1}'.format(names, message))
|
||||
if not isinstance(names, list):
|
||||
names = [names]
|
||||
self._names = names
|
||||
self._message = message
|
||||
self._stacktrace = stacktrace
|
||||
self._extra = extra or {}
|
||||
self._cause = cause
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
return self._names
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
return self._message
|
||||
|
||||
@property
|
||||
def stacktrace(self):
|
||||
return self._stacktrace
|
||||
|
||||
@property
|
||||
def extra(self):
|
||||
return self._extra
|
||||
|
||||
@property
|
||||
def cause(self):
|
||||
return self._cause
|
||||
|
||||
@staticmethod
|
||||
def from_python_exception(exception, context):
|
||||
stacktrace = yaql_functions.new('io.murano.StackTrace', context)
|
||||
exception_type = type(exception)
|
||||
names = ['{0}.{1}'.format(exception_type.__module__,
|
||||
exception_type.__name__)]
|
||||
|
||||
return MuranoPlException(
|
||||
names, exception.message, stacktrace)
|
||||
|
||||
def _format_name(self):
|
||||
if not self._names:
|
||||
return ''
|
||||
elif len(self._names) == 1:
|
||||
return self._names[0]
|
||||
else:
|
||||
return self._names
|
||||
|
||||
def format(self, prefix=' '):
|
||||
text = '\n{3}{0}: {1}\n' \
|
||||
'{3}Traceback (most recent call last):\n' \
|
||||
'{2}'.format(self._format_name(), self.message,
|
||||
self.stacktrace.toString(prefix + ' '), prefix)
|
||||
if self._cause is not None:
|
||||
text += '\n\n{0} Caused by {1}'.format(
|
||||
prefix, self._cause.format(prefix + ' ').lstrip())
|
||||
return text
|
|
@ -21,7 +21,9 @@ import eventlet
|
|||
import eventlet.event
|
||||
import yaql.context
|
||||
|
||||
|
||||
import murano.dsl.attribute_store as attribute_store
|
||||
import murano.dsl.dsl_exception as dsl_exception
|
||||
import murano.dsl.expressions as expressions
|
||||
import murano.dsl.helpers as helpers
|
||||
import murano.dsl.murano_method as murano_method
|
||||
|
@ -150,12 +152,18 @@ class MuranoDslExecutor(object):
|
|||
if '_context' in inspect.getargspec(body).args:
|
||||
params['_context'] = self._create_context(
|
||||
this, murano_class, context, **params)
|
||||
if inspect.ismethod(body) and not body.__self__:
|
||||
return body(this, **params)
|
||||
else:
|
||||
return body(**params)
|
||||
try:
|
||||
if inspect.ismethod(body) and not body.__self__:
|
||||
return body(this, **params)
|
||||
else:
|
||||
return body(**params)
|
||||
except Exception as e:
|
||||
raise dsl_exception.MuranoPlException.from_python_exception(
|
||||
e, context)
|
||||
elif isinstance(body, expressions.DslExpression):
|
||||
return self.execute(body, murano_class, this, context, **params)
|
||||
return self.execute(
|
||||
body, murano_class, this, context, **params)
|
||||
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import types
|
||||
|
||||
import murano.dsl.dsl_exception as dsl_exception
|
||||
import murano.dsl.helpers as helpers
|
||||
import murano.dsl.lhs_expression as lhs_expression
|
||||
import murano.dsl.yaql_expression as yaql_expression
|
||||
|
@ -21,6 +22,15 @@ import murano.dsl.yaql_expression as yaql_expression
|
|||
_macros = []
|
||||
|
||||
|
||||
class InstructionStub(object):
|
||||
def __init__(self, title, position):
|
||||
self._title = title
|
||||
self.source_file_position = position
|
||||
|
||||
def __str__(self):
|
||||
return self._title
|
||||
|
||||
|
||||
def register_macro(cls):
|
||||
_macros.append(cls)
|
||||
|
||||
|
@ -55,11 +65,16 @@ class Statement(DslExpression):
|
|||
return self._expression
|
||||
|
||||
def execute(self, context, murano_class):
|
||||
result = helpers.evaluate(self.expression, context)
|
||||
if self.destination:
|
||||
self.destination(result, context, murano_class)
|
||||
|
||||
return result
|
||||
try:
|
||||
result = helpers.evaluate(self.expression, context)
|
||||
if self.destination:
|
||||
self.destination(result, context, murano_class)
|
||||
return result
|
||||
except dsl_exception.MuranoPlException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise dsl_exception.MuranoPlException.from_python_exception(
|
||||
e, context)
|
||||
|
||||
|
||||
def parse_expression(expr):
|
||||
|
@ -79,7 +94,16 @@ def parse_expression(expr):
|
|||
if result is None:
|
||||
for cls in _macros:
|
||||
try:
|
||||
return cls(**kwds)
|
||||
macro = cls(**kwds)
|
||||
position = None
|
||||
title = 'block construct'
|
||||
if hasattr(expr, 'source_file_position'):
|
||||
position = expr.source_file_position
|
||||
if '__str__' in cls.__dict__:
|
||||
title = str(macro)
|
||||
macro.virtual_instruction = InstructionStub(
|
||||
title, position)
|
||||
return macro
|
||||
except TypeError:
|
||||
continue
|
||||
|
||||
|
|
|
@ -46,6 +46,14 @@ def serialize(value, memo=None):
|
|||
return value
|
||||
|
||||
|
||||
def execute_instruction(instruction, action, context):
|
||||
old_instruction = context.get_data('$?currentInstruction')
|
||||
context.set_data(instruction, '?currentInstruction')
|
||||
result = action()
|
||||
context.set_data(old_instruction, '?currentInstruction')
|
||||
return result
|
||||
|
||||
|
||||
def evaluate(value, context, max_depth=sys.maxint):
|
||||
if isinstance(value, yaql.expressions.Expression):
|
||||
value = yaql_expression.YaqlExpression(value)
|
||||
|
@ -55,11 +63,7 @@ def evaluate(value, context, max_depth=sys.maxint):
|
|||
if max_depth <= 0:
|
||||
return func
|
||||
else:
|
||||
try:
|
||||
context.set_data(value, '?currentInstruction')
|
||||
return func()
|
||||
finally:
|
||||
context.set_data(None, '?currentInstruction')
|
||||
return execute_instruction(value, func, context)
|
||||
|
||||
elif isinstance(value, types.DictionaryType):
|
||||
result = {}
|
||||
|
@ -192,3 +196,7 @@ def get_current_instruction(context):
|
|||
|
||||
def get_current_method(context):
|
||||
return context.get_data('$?currentMethod')
|
||||
|
||||
|
||||
def get_current_exception(context):
|
||||
return context.get_data('$?currentException')
|
||||
|
|
|
@ -17,6 +17,7 @@ import types
|
|||
import eventlet.greenpool as greenpool
|
||||
import yaql.context
|
||||
|
||||
import murano.dsl.dsl_exception as dsl_exception
|
||||
import murano.dsl.exceptions as exceptions
|
||||
import murano.dsl.expressions as expressions
|
||||
import murano.dsl.helpers as helpers
|
||||
|
@ -33,7 +34,20 @@ class CodeBlock(expressions.DslExpression):
|
|||
def execute(self, context, murano_class):
|
||||
try:
|
||||
for expr in self.code_block:
|
||||
expr.execute(context, murano_class)
|
||||
def action():
|
||||
try:
|
||||
expr.execute(context, murano_class)
|
||||
except dsl_exception.MuranoPlException:
|
||||
raise
|
||||
except Exception as ex:
|
||||
raise dsl_exception.MuranoPlException.\
|
||||
from_python_exception(ex, context)
|
||||
|
||||
if hasattr(expr, 'virtual_instruction'):
|
||||
instruction = expr.virtual_instruction
|
||||
helpers.execute_instruction(instruction, action, context)
|
||||
else:
|
||||
action()
|
||||
except exceptions.BreakException as e:
|
||||
if self._breakable:
|
||||
raise e
|
||||
|
@ -47,16 +61,14 @@ class MethodBlock(CodeBlock):
|
|||
self._name = name
|
||||
|
||||
def execute(self, context, murano_class):
|
||||
new_context = yaql.context.Context(context)
|
||||
new_context.set_data(self._name, '?currentMethod')
|
||||
try:
|
||||
context.set_data(self._name, '?currentMethod')
|
||||
super(MethodBlock, self).execute(
|
||||
context, murano_class)
|
||||
super(MethodBlock, self).execute(new_context, murano_class)
|
||||
except exceptions.ReturnException as e:
|
||||
return e.value
|
||||
else:
|
||||
return None
|
||||
finally:
|
||||
context.set_data(None, '?currentMethod')
|
||||
|
||||
|
||||
class ReturnMacro(expressions.DslExpression):
|
||||
|
@ -220,13 +232,14 @@ class DoMacro(expressions.DslExpression):
|
|||
self._code.execute(child_context, murano_class)
|
||||
|
||||
|
||||
expressions.register_macro(DoMacro)
|
||||
expressions.register_macro(ReturnMacro)
|
||||
expressions.register_macro(BreakMacro)
|
||||
expressions.register_macro(ParallelMacro)
|
||||
expressions.register_macro(IfMacro)
|
||||
expressions.register_macro(WhileDoMacro)
|
||||
expressions.register_macro(ForMacro)
|
||||
expressions.register_macro(RepeatMacro)
|
||||
expressions.register_macro(MatchMacro)
|
||||
expressions.register_macro(SwitchMacro)
|
||||
def register():
|
||||
expressions.register_macro(DoMacro)
|
||||
expressions.register_macro(ReturnMacro)
|
||||
expressions.register_macro(BreakMacro)
|
||||
expressions.register_macro(ParallelMacro)
|
||||
expressions.register_macro(IfMacro)
|
||||
expressions.register_macro(WhileDoMacro)
|
||||
expressions.register_macro(ForMacro)
|
||||
expressions.register_macro(RepeatMacro)
|
||||
expressions.register_macro(MatchMacro)
|
||||
expressions.register_macro(SwitchMacro)
|
||||
|
|
|
@ -17,6 +17,7 @@ import types
|
|||
|
||||
import murano.dsl.macros as macros
|
||||
import murano.dsl.typespec as typespec
|
||||
import murano.dsl.virtual_exceptions as virtual_exceptions
|
||||
import murano.dsl.yaql_expression as yaql_expression
|
||||
|
||||
try:
|
||||
|
@ -25,6 +26,10 @@ except ImportError: # python2.6
|
|||
from ordereddict import OrderedDict # noqa
|
||||
|
||||
|
||||
macros.register()
|
||||
virtual_exceptions.register()
|
||||
|
||||
|
||||
class MethodUsages(object):
|
||||
Action = 'Action'
|
||||
Runtime = 'Runtime'
|
||||
|
|
|
@ -1,6 +1,28 @@
|
|||
# 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 murano.dsl.principal_objects.exception
|
||||
import murano.dsl.principal_objects.stack_trace
|
||||
import murano.dsl.principal_objects.sys_object
|
||||
|
||||
|
||||
def register(class_loader):
|
||||
sys_object = murano.dsl.principal_objects.sys_object
|
||||
class_loader.import_class(sys_object.SysObject)
|
||||
|
||||
stack_trace = murano.dsl.principal_objects.stack_trace
|
||||
class_loader.import_class(stack_trace.StackTrace)
|
||||
|
||||
exception = murano.dsl.principal_objects.exception
|
||||
class_loader.import_class(exception.DslException)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# 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.
|
||||
|
||||
from murano.dsl import murano_class
|
||||
from murano.dsl import murano_object
|
||||
|
||||
|
||||
@murano_class.classname('io.murano.Exception')
|
||||
class DslException(murano_object.MuranoObject):
|
||||
def toString(self):
|
||||
return self.get_property('nativeException').format()
|
|
@ -0,0 +1,99 @@
|
|||
# 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 inspect
|
||||
import os.path
|
||||
|
||||
from murano.dsl import helpers
|
||||
from murano.dsl import murano_class
|
||||
from murano.dsl import murano_object
|
||||
from murano.dsl import yaql_expression
|
||||
|
||||
|
||||
@murano_class.classname('io.murano.StackTrace')
|
||||
class StackTrace(murano_object.MuranoObject):
|
||||
def initialize(self, _context, includeNativeFrames=True):
|
||||
frames = []
|
||||
context = _context
|
||||
while True:
|
||||
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.source_file_position,
|
||||
|
||||
'method': helpers.get_current_method(context),
|
||||
'class': helpers.get_type(context)
|
||||
})
|
||||
context = helpers.get_caller_context(context)
|
||||
frames.pop()
|
||||
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,
|
||||
-1, -1, -1, -1, -1)
|
||||
instruction = InstructionStub(
|
||||
info.code_context[0].strip(), position)
|
||||
method = info.function
|
||||
native_frames.append({
|
||||
'instruction': instruction,
|
||||
'method': method,
|
||||
'class': None
|
||||
})
|
||||
frames.extend(native_frames)
|
||||
|
||||
self.set_property('frames', frames)
|
||||
|
||||
def toString(self, prefix=''):
|
||||
def format_frame(frame):
|
||||
instruction = frame['instruction']
|
||||
method = frame['method']
|
||||
murano_class = frame['class']
|
||||
location = frame['location']
|
||||
|
||||
if location:
|
||||
args = (
|
||||
os.path.abspath(location.file_path),
|
||||
location.start_line,
|
||||
':' + str(location.start_column)
|
||||
if location.start_column >= 0 else '',
|
||||
method,
|
||||
instruction,
|
||||
prefix,
|
||||
'' if not murano_class else murano_class.name + '::'
|
||||
)
|
||||
return '{5}File "{0}", line {1}{2} in method {6}{3}\n' \
|
||||
'{5} {4}'.format(*args)
|
||||
else:
|
||||
return '{2}File <unknown> in method {3}{0}\n{2} {1}'.format(
|
||||
method, instruction, prefix,
|
||||
'' if not murano_class else murano_class.name + '::')
|
||||
|
||||
return '\n'.join([format_frame(t)for t in self.get_property('frames')])
|
|
@ -0,0 +1,151 @@
|
|||
# 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 murano.dsl.dsl_exception as dsl_exception
|
||||
import murano.dsl.expressions as expressions
|
||||
import murano.dsl.helpers as helpers
|
||||
import murano.dsl.macros as macros
|
||||
import murano.dsl.yaql_functions as yaql_functions
|
||||
|
||||
|
||||
class ThrowMacro(expressions.DslExpression):
|
||||
def __init__(self, Throw, Message=None, Extra=None, Cause=None):
|
||||
if not Throw:
|
||||
raise ValueError()
|
||||
if not isinstance(Throw, list):
|
||||
Throw = [Throw]
|
||||
|
||||
self._names = Throw
|
||||
self._message = Message
|
||||
self._extra = Extra or {}
|
||||
self._cause = Cause
|
||||
|
||||
def _resolve_names(self, names, context):
|
||||
murano_class = helpers.get_type(context)
|
||||
for name in names:
|
||||
yield murano_class.namespace_resolver.resolve_name(name)
|
||||
|
||||
def execute(self, context, murano_class):
|
||||
stacktrace = yaql_functions.new('io.murano.StackTrace', context,
|
||||
includeNativeFrames=False)
|
||||
cause = None
|
||||
if self._cause:
|
||||
cause = helpers.evaluate(self._cause, context).get_property(
|
||||
'nativeException')
|
||||
raise dsl_exception.MuranoPlException(
|
||||
list(self._resolve_names(helpers.evaluate(self._names, context),
|
||||
context)),
|
||||
helpers.evaluate(self._message, context),
|
||||
stacktrace, self._extra, cause)
|
||||
|
||||
def __str__(self):
|
||||
if self._message:
|
||||
return 'Throw {0}: {1}'.format(self._names, self._message)
|
||||
return 'Throw ' + self._names
|
||||
|
||||
|
||||
class CatchBlock(expressions.DslExpression):
|
||||
def __init__(self, With=None, As=None, Do=None):
|
||||
if With is not None and not isinstance(With, list):
|
||||
With = [With]
|
||||
self._with = With
|
||||
self._as = As
|
||||
self._code_block = None if Do is None else macros.CodeBlock(Do)
|
||||
|
||||
def _resolve_names(self, names, context):
|
||||
murano_class = helpers.get_type(context)
|
||||
for name in names:
|
||||
yield murano_class.namespace_resolver.resolve_name(name)
|
||||
|
||||
def execute(self, context, murano_class):
|
||||
exception = helpers.get_current_exception(context)
|
||||
names = None if self._with is None else \
|
||||
list(self._resolve_names(self._with, context))
|
||||
|
||||
for name in exception.names:
|
||||
if self._with is None or name in names:
|
||||
if self._code_block:
|
||||
if self._as:
|
||||
wrapped = self._wrap_internal_exception(
|
||||
exception, context, name)
|
||||
context.set_data(wrapped, self._as)
|
||||
self._code_block.execute(context, murano_class)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _wrap_internal_exception(self, exception, context, name):
|
||||
obj = yaql_functions.new('io.murano.Exception', context)
|
||||
obj.set_property('name', name)
|
||||
obj.set_property('message', exception.message)
|
||||
obj.set_property('stackTrace', exception.stacktrace)
|
||||
obj.set_property('extra', exception.extra)
|
||||
obj.set_property('nativeException', exception)
|
||||
return obj
|
||||
|
||||
|
||||
class TryBlockMacro(expressions.DslExpression):
|
||||
def __init__(self, Try, Catch=None, Finally=None, Else=None):
|
||||
self._try_block = macros.CodeBlock(Try)
|
||||
self._catch_block = None
|
||||
if Catch is not None:
|
||||
if not isinstance(Catch, list):
|
||||
Catch = [Catch]
|
||||
self._catch_block = [CatchBlock(**c) for c in Catch]
|
||||
self._finally_block = None if Finally is None \
|
||||
else macros.CodeBlock(Finally)
|
||||
self._else_block = None if Else is None \
|
||||
else macros.CodeBlock(Else)
|
||||
|
||||
def execute(self, context, murano_class):
|
||||
try:
|
||||
self._try_block.execute(context, murano_class)
|
||||
except dsl_exception.MuranoPlException as e:
|
||||
caught = False
|
||||
if self._catch_block:
|
||||
try:
|
||||
context.set_data(e, '?currentException')
|
||||
for cb in self._catch_block:
|
||||
if cb.execute(context, murano_class):
|
||||
caught = True
|
||||
break
|
||||
if not caught:
|
||||
raise
|
||||
finally:
|
||||
context.set_data(None, '?currentException')
|
||||
else:
|
||||
if self._else_block:
|
||||
self._else_block.execute(context, murano_class)
|
||||
finally:
|
||||
if self._finally_block:
|
||||
self._finally_block.execute(context, murano_class)
|
||||
|
||||
|
||||
class RethrowMacro(expressions.DslExpression):
|
||||
def __init__(self, Rethrow):
|
||||
pass
|
||||
|
||||
def execute(self, context, murano_class):
|
||||
exception = context.get_data('$?currentException')
|
||||
if not exception:
|
||||
raise TypeError('Rethrow must be inside Catch')
|
||||
raise exception
|
||||
|
||||
def __str__(self):
|
||||
return 'Rethrow'
|
||||
|
||||
|
||||
def register():
|
||||
expressions.register_macro(ThrowMacro)
|
||||
expressions.register_macro(TryBlockMacro)
|
||||
expressions.register_macro(RethrowMacro)
|
|
@ -1,66 +0,0 @@
|
|||
# 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()])
|
|
@ -42,11 +42,11 @@ class YaqlExpression(object):
|
|||
return self._expression
|
||||
|
||||
@property
|
||||
def file_position(self):
|
||||
def source_file_position(self):
|
||||
return self._file_position
|
||||
|
||||
@file_position.setter
|
||||
def file_position(self, value):
|
||||
@source_file_position.setter
|
||||
def source_file_position(self, value):
|
||||
self._file_position = value
|
||||
|
||||
def __repr__(self):
|
||||
|
|
|
@ -53,7 +53,7 @@ def _id(value):
|
|||
@yaql.context.EvalArg('type', str)
|
||||
@yaql.context.ContextAware()
|
||||
def _cast(context, value, type):
|
||||
if not '.' in type:
|
||||
if '.' not in type:
|
||||
murano_class = helpers.get_type(context)
|
||||
type = murano_class.namespace_resolver.resolve_name(type)
|
||||
class_loader = helpers.get_class_loader(context)
|
||||
|
@ -86,6 +86,10 @@ def _new(context, name, *args):
|
|||
None, object_store, new_context, parameters=parameters)
|
||||
|
||||
|
||||
def new(name, context, **kwargs):
|
||||
return _new(context, name, lambda: kwargs)
|
||||
|
||||
|
||||
@yaql.context.EvalArg('value', murano_object.MuranoObject)
|
||||
@yaql.context.ContextAware()
|
||||
def _super(context, value):
|
||||
|
|
|
@ -14,13 +14,33 @@
|
|||
# limitations under the License.
|
||||
|
||||
import yaml
|
||||
import yaml.composer
|
||||
import yaml.constructor
|
||||
|
||||
from murano.dsl import yaql_expression
|
||||
|
||||
|
||||
class YaqlYamlLoader(yaml.Loader):
|
||||
class MuranoPlDict(dict):
|
||||
pass
|
||||
|
||||
|
||||
class MuranoPlYamlConstructor(yaml.constructor.Constructor):
|
||||
def construct_yaml_map(self, node):
|
||||
data = MuranoPlDict()
|
||||
data.source_file_position = build_position(node)
|
||||
yield data
|
||||
value = self.construct_mapping(node)
|
||||
data.update(value)
|
||||
|
||||
|
||||
class YaqlYamlLoader(yaml.Loader, MuranoPlYamlConstructor):
|
||||
pass
|
||||
|
||||
|
||||
YaqlYamlLoader.add_constructor(u'tag:yaml.org,2002:map',
|
||||
MuranoPlYamlConstructor.construct_yaml_map)
|
||||
|
||||
|
||||
# workaround for PyYAML bug: http://pyyaml.org/ticket/221
|
||||
resolvers = {}
|
||||
for k, v in yaml.Loader.yaml_implicit_resolvers.items():
|
||||
|
@ -28,10 +48,8 @@ for k, v in yaml.Loader.yaml_implicit_resolvers.items():
|
|||
YaqlYamlLoader.yaml_implicit_resolvers = resolvers
|
||||
|
||||
|
||||
def yaql_constructor(loader, node):
|
||||
value = loader.construct_scalar(node)
|
||||
result = yaql_expression.YaqlExpression(value)
|
||||
position = yaql_expression.YaqlExpressionFilePosition(
|
||||
def build_position(node):
|
||||
return yaql_expression.YaqlExpressionFilePosition(
|
||||
node.start_mark.name,
|
||||
node.start_mark.line + 1,
|
||||
node.start_mark.column + 1,
|
||||
|
@ -39,7 +57,12 @@ def yaql_constructor(loader, node):
|
|||
node.end_mark.line + 1,
|
||||
node.end_mark.column + 1,
|
||||
node.end_mark.index - node.start_mark.index)
|
||||
result.file_position = position
|
||||
|
||||
|
||||
def yaql_constructor(loader, node):
|
||||
value = loader.construct_scalar(node)
|
||||
result = yaql_expression.YaqlExpression(value)
|
||||
result.source_file_position = build_position(node)
|
||||
return result
|
||||
|
||||
yaml.add_constructor(u'!yaql', yaql_constructor, YaqlYamlLoader)
|
||||
|
|
|
@ -18,12 +18,8 @@ import re
|
|||
import mock
|
||||
import yaql
|
||||
|
||||
import murano.dsl.exceptions as exceptions
|
||||
import murano.dsl.helpers as helpers
|
||||
import murano.dsl.murano_class as murano_class
|
||||
import murano.dsl.murano_object as murano_object
|
||||
import murano.dsl.namespace_resolver as ns_resolver
|
||||
import murano.dsl.typespec as typespec
|
||||
import murano.dsl.yaql_expression as yaql_expression
|
||||
from murano.tests import base
|
||||
|
||||
|
@ -114,325 +110,6 @@ class Bunch(object):
|
|||
setattr(self, key, value)
|
||||
|
||||
|
||||
class TestClassesManipulation(base.MuranoTestCase):
|
||||
resolver = mock.Mock(resolve_name=lambda name: name)
|
||||
|
||||
def setUp(self):
|
||||
super(TestClassesManipulation, self).setUp()
|
||||
|
||||
def test_class_name(self):
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
|
||||
self.assertEqual(ROOT_CLASS, cls.name)
|
||||
|
||||
def test_class_namespace_resolver(self):
|
||||
resolver = ns_resolver.NamespaceResolver({})
|
||||
cls = murano_class.MuranoClass(None, resolver, ROOT_CLASS, None)
|
||||
|
||||
self.assertEqual(resolver, cls.namespace_resolver)
|
||||
|
||||
def test_root_class_has_no_parents(self):
|
||||
root_class = murano_class.MuranoClass(
|
||||
None, self.resolver, ROOT_CLASS, ['You should not see me!'])
|
||||
|
||||
self.assertEqual([], root_class.parents)
|
||||
|
||||
def test_non_root_class_resolves_parents(self):
|
||||
root_cls = murano_class.MuranoClass(None, self.resolver,
|
||||
ROOT_CLASS, None)
|
||||
class_loader = mock.Mock(get_class=lambda name: root_cls)
|
||||
desc_cl1 = murano_class.MuranoClass(class_loader, self.resolver,
|
||||
'Obj', None)
|
||||
desc_cl2 = murano_class.MuranoClass(
|
||||
class_loader, self.resolver, 'Obj', None, [root_cls])
|
||||
|
||||
self.assertEqual([root_cls], desc_cl1.parents)
|
||||
self.assertEqual([root_cls], desc_cl2.parents)
|
||||
|
||||
def test_class_initial_properties(self):
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
self.assertEqual([], cls.properties)
|
||||
|
||||
def test_fails_add_incompatible_property_to_class(self):
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
kwargs = {'name': 'sampleProperty', 'property_typespec': {}}
|
||||
|
||||
self.assertRaises(TypeError, cls.add_property, **kwargs)
|
||||
|
||||
def test_add_property_to_class(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
prop = typespec.PropertySpec({'Default': 1}, self.resolver)
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
cls.add_property('firstPrime', prop)
|
||||
|
||||
class_properties = cls.properties
|
||||
class_property = cls.get_property('firstPrime')
|
||||
|
||||
self.assertEqual(['firstPrime'], class_properties)
|
||||
self.assertEqual(prop, class_property)
|
||||
|
||||
def test_class_property_search(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
void_prop = typespec.PropertySpec({'Default': 'Void'}, self.resolver)
|
||||
mother_prop = typespec.PropertySpec({'Default': 'Mother'},
|
||||
self.resolver)
|
||||
father_prop = typespec.PropertySpec({'Default': 'Father'},
|
||||
self.resolver)
|
||||
child_prop = typespec.PropertySpec({'Default': 'Child'},
|
||||
self.resolver)
|
||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
||||
mother = murano_class.MuranoClass(None, self.resolver,
|
||||
'Mother', [root])
|
||||
father = murano_class.MuranoClass(None, self.resolver,
|
||||
'Father', [root])
|
||||
child = murano_class.MuranoClass(
|
||||
None, self.resolver, 'Child', [mother, father])
|
||||
|
||||
root.add_property('Void', void_prop)
|
||||
mother.add_property('Mother', mother_prop)
|
||||
father.add_property('Father', father_prop)
|
||||
child.add_property('Child', child_prop)
|
||||
|
||||
self.assertEqual(child_prop, child.find_property('Child'))
|
||||
self.assertEqual(father_prop, child.find_property('Father'))
|
||||
self.assertEqual(mother_prop, child.find_property('Mother'))
|
||||
self.assertEqual(void_prop, child.find_property('Void'))
|
||||
|
||||
def test_class_is_compatible(self):
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
descendant_cls = murano_class.MuranoClass(
|
||||
None, self.resolver, 'DescendantCls', None, [cls])
|
||||
obj = mock.Mock(spec=murano_object.MuranoObject)
|
||||
descendant_obj = mock.Mock(spec=murano_object.MuranoObject)
|
||||
obj.type = cls
|
||||
descendant_obj.type = descendant_cls
|
||||
descendant_obj.parents = [obj]
|
||||
|
||||
self.assertTrue(cls.is_compatible(obj))
|
||||
self.assertTrue(cls.is_compatible(descendant_obj))
|
||||
self.assertFalse(descendant_cls.is_compatible(obj))
|
||||
|
||||
def test_new_method_calls_initialize(self):
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
cls.object_class = mock.Mock()
|
||||
|
||||
with mock.patch('inspect.getargspec') as spec_mock:
|
||||
spec_mock.return_value = Bunch(args=())
|
||||
obj = cls.new(None, None, None, {})
|
||||
|
||||
self.assertTrue(obj.initialize.called)
|
||||
|
||||
def test_new_method_not_calls_initialize(self):
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
cls.object_class = mock.Mock()
|
||||
|
||||
obj = cls.new(None, None, None)
|
||||
|
||||
self.assertFalse(obj.initialize.called)
|
||||
|
||||
|
||||
class TestObjectsManipulation(base.MuranoTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestObjectsManipulation, self).setUp()
|
||||
|
||||
self.resolver = mock.Mock(resolve_name=lambda name: name)
|
||||
self.cls = mock.Mock()
|
||||
self.cls.name = ROOT_CLASS
|
||||
self.cls.parents = []
|
||||
|
||||
def test_object_valid_type_instantiation(self):
|
||||
obj = murano_object.MuranoObject(self.cls, None, None, None)
|
||||
|
||||
self.assertEqual(self.cls, obj.type)
|
||||
|
||||
def test_object_own_properties_initialization(self):
|
||||
# TODO: there should be test for initializing first non-dependent
|
||||
# object properties, then the dependent ones (given as
|
||||
# YAQL-expressions)
|
||||
pass
|
||||
|
||||
def test_object_parent_properties_initialization(self):
|
||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
cls = murano_class.MuranoClass(None, self.resolver,
|
||||
'SomeClass', None, [root])
|
||||
root.new = mock.Mock()
|
||||
init_kwargs = {'theArg': 0}
|
||||
obj = murano_object.MuranoObject(cls, None, None, None)
|
||||
expected_calls = [mock.call().initialize(**init_kwargs)]
|
||||
|
||||
obj.initialize(**init_kwargs)
|
||||
|
||||
# each object should also initialize his parent objects
|
||||
self.assertEqual(expected_calls, root.new.mock_calls[1:])
|
||||
|
||||
def test_object_id(self):
|
||||
_id = 'some_id'
|
||||
patch_at = 'murano.dsl.helpers.generate_id'
|
||||
|
||||
obj = murano_object.MuranoObject(self.cls, None, None, None,
|
||||
object_id=_id)
|
||||
with mock.patch(patch_at) as gen_id_mock:
|
||||
gen_id_mock.return_value = _id
|
||||
obj1 = murano_object.MuranoObject(self.cls, None, None, None)
|
||||
|
||||
self.assertEqual(_id, obj.object_id)
|
||||
self.assertEqual(_id, obj1.object_id)
|
||||
|
||||
def test_parent_obj(self):
|
||||
parent = mock.Mock()
|
||||
obj = murano_object.MuranoObject(self.cls, parent, None, None)
|
||||
|
||||
self.assertEqual(parent, obj.parent)
|
||||
|
||||
def test_fails_internal_property_access(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
||||
|
||||
cls.add_property('__hidden',
|
||||
typespec.PropertySpec({'Default': 10}, self.resolver))
|
||||
obj = murano_object.MuranoObject(cls, None, None, None)
|
||||
|
||||
self.assertRaises(AttributeError, lambda: obj.__hidden)
|
||||
|
||||
def test_proper_property_access(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
||||
|
||||
cls.add_property('someProperty',
|
||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
||||
obj = cls.new(None, None, None, {})
|
||||
|
||||
self.assertEqual(0, obj.someProperty)
|
||||
|
||||
def test_parent_class_property_access(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
||||
child_cls = murano_class.MuranoClass(None, self.resolver,
|
||||
'Child', [cls])
|
||||
|
||||
cls.add_property('anotherProperty',
|
||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
||||
obj = child_cls.new(None, None, None, {})
|
||||
|
||||
self.assertEqual(0, obj.anotherProperty)
|
||||
|
||||
def test_fails_on_parents_property_collision(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
||||
mother = murano_class.MuranoClass(None, self.resolver,
|
||||
'Mother', [root])
|
||||
father = murano_class.MuranoClass(None, self.resolver,
|
||||
'Father', [root])
|
||||
child = murano_class.MuranoClass(
|
||||
None, self.resolver, 'Child', [mother, father])
|
||||
|
||||
mother.add_property(
|
||||
'conflictProp',
|
||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
||||
father.add_property(
|
||||
'conflictProp',
|
||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
||||
obj = child.new(None, None, None, {})
|
||||
|
||||
self.assertRaises(LookupError, lambda: obj.conflictProp)
|
||||
|
||||
def test_fails_setting_undeclared_property(self):
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
obj = cls.new(None, None, None, {})
|
||||
|
||||
self.assertRaises(AttributeError, obj.set_property, 'newOne', 10)
|
||||
|
||||
def test_set_undeclared_property_as_internal(self):
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
obj = cls.new(None, None, None, {})
|
||||
obj.cast = mock.Mock(return_value=obj)
|
||||
prop_value = 10
|
||||
|
||||
obj.set_property('internalProp', prop_value, caller_class=cls)
|
||||
resolved_value = obj.get_property('internalProp', caller_class=cls)
|
||||
|
||||
self.assertEqual(prop_value, resolved_value)
|
||||
|
||||
def test_fails_forbidden_set_property(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
||||
cls.add_property('someProperty',
|
||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
||||
cls.is_compatible = mock.Mock(return_value=False)
|
||||
obj = cls.new(None, None, None, {})
|
||||
|
||||
self.assertRaises(exceptions.NoWriteAccess, obj.set_property,
|
||||
'someProperty', 10, caller_class=cls)
|
||||
|
||||
def test_set_property(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
||||
cls.add_property('someProperty',
|
||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
||||
obj = cls.new(None, None, None, {})
|
||||
|
||||
with mock.patch('yaql.context.Context'):
|
||||
with mock.patch('murano.engine.helpers') as helpers_mock:
|
||||
helpers_mock.evaluate = lambda val, ctx, _: val
|
||||
obj.set_property('someProperty', 10)
|
||||
|
||||
self.assertEqual(10, obj.someProperty)
|
||||
|
||||
def test_set_parent_property(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
||||
cls = murano_class.MuranoClass(None, self.resolver,
|
||||
'SomeClass', [root])
|
||||
root.add_property('rootProperty',
|
||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
||||
obj = cls.new(None, None, None, {})
|
||||
|
||||
with mock.patch('murano.engine.helpers') as helpers_mock:
|
||||
with mock.patch('yaql.context.Context'):
|
||||
helpers_mock.evaluate = lambda val, ctx, _: val
|
||||
obj.set_property('rootProperty', 20)
|
||||
|
||||
self.assertEqual(20, obj.rootProperty)
|
||||
|
||||
def test_object_up_cast(self):
|
||||
self.skipTest("FIXME!")
|
||||
|
||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
||||
root_alt = murano_class.MuranoClass(None, self.resolver, 'RootAlt', [])
|
||||
cls = murano_class.MuranoClass(
|
||||
None, self.resolver, 'SomeClass', [root, root_alt])
|
||||
root_obj = root.new(None, None, None)
|
||||
cls_obj = cls.new(None, None, None)
|
||||
|
||||
root_obj_casted2root = root_obj.cast(root)
|
||||
cls_obj_casted2root = cls_obj.cast(root)
|
||||
cls_obj_casted2root_alt = cls_obj.cast(root_alt)
|
||||
|
||||
self.assertEqual(root_obj, root_obj_casted2root)
|
||||
# each object creates an _internal_ parent objects hierarchy,
|
||||
# so direct comparison of objects is not possible
|
||||
self.assertEqual(root, cls_obj_casted2root.type)
|
||||
self.assertEqual(root_alt, cls_obj_casted2root_alt.type)
|
||||
|
||||
def test_fails_object_down_cast(self):
|
||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
||||
cls = murano_class.MuranoClass(
|
||||
None, self.resolver, 'SomeClass', None, [root])
|
||||
root_obj = root.new(None, None, None)
|
||||
|
||||
self.assertRaises(TypeError, root_obj.cast, cls)
|
||||
|
||||
|
||||
class TestHelperFunctions(base.MuranoTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
Loading…
Reference in New Issue