Merge "Adds ability to throw/catch/rethrow exceptions in MuranoPL"

This commit is contained in:
Jenkins 2014-06-12 18:42:12 +00:00 committed by Gerrit Code Review
commit 5369506800
19 changed files with 548 additions and 432 deletions

View File

@ -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

View File

@ -0,0 +1,12 @@
Namespaces:
=: io.murano
Name: StackTrace
Properties:
frames:
Contract:
- instruction: $.string()
location: $
method: $
class: $

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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')

View File

@ -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)

View File

@ -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'

View File

@ -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)

View File

@ -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()

View File

@ -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')])

View File

@ -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)

View File

@ -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()])

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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):