Merge "MuranoPL testing mini-framework"
This commit is contained in:
commit
55abdae728
@ -54,8 +54,10 @@ class MuranoPlException(Exception):
|
||||
names = ['{0}.{1}'.format(exception_type.__module__,
|
||||
exception_type.__name__)]
|
||||
|
||||
return MuranoPlException(
|
||||
result = MuranoPlException(
|
||||
names, exception.message, stacktrace)
|
||||
result.original_exception = exception
|
||||
return result
|
||||
|
||||
def _format_name(self):
|
||||
if not self._names:
|
||||
|
0
murano/tests/dsl/__init__.py
Normal file
0
murano/tests/dsl/__init__.py
Normal file
0
murano/tests/dsl/foundation/__init__.py
Normal file
0
murano/tests/dsl/foundation/__init__.py
Normal file
45
murano/tests/dsl/foundation/object_model.py
Normal file
45
murano/tests/dsl/foundation/object_model.py
Normal file
@ -0,0 +1,45 @@
|
||||
# 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 helpers
|
||||
|
||||
|
||||
class Object(object):
|
||||
def __init__(self, name, **kwargs):
|
||||
self.data = {
|
||||
'?': {
|
||||
'type': name,
|
||||
'id': helpers.generate_id()
|
||||
}
|
||||
}
|
||||
self.data.update(kwargs)
|
||||
|
||||
|
||||
class Ref(object):
|
||||
def __init__(self, obj):
|
||||
self.id = obj.data['?']['id']
|
||||
|
||||
|
||||
def build_model(root):
|
||||
if isinstance(root, dict):
|
||||
for key, value in root.items():
|
||||
root[key] = build_model(value)
|
||||
elif isinstance(root, list):
|
||||
for i in range(len(root)):
|
||||
root[i] = build_model(root[i])
|
||||
elif isinstance(root, Object):
|
||||
return build_model(root.data)
|
||||
elif isinstance(root, Ref):
|
||||
return root.id
|
||||
return root
|
94
murano/tests/dsl/foundation/runner.py
Normal file
94
murano/tests/dsl/foundation/runner.py
Normal file
@ -0,0 +1,94 @@
|
||||
# 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 types
|
||||
|
||||
from murano.dsl import dsl_exception
|
||||
from murano.dsl import executor
|
||||
from murano.dsl import murano_object
|
||||
from murano.dsl import results_serializer
|
||||
from murano.engine import environment
|
||||
from murano.tests.dsl.foundation import object_model
|
||||
|
||||
|
||||
class Runner(object):
|
||||
class DslObjectWrapper(object):
|
||||
def __init__(self, obj, runner):
|
||||
self._runner = runner
|
||||
if isinstance(obj, types.StringTypes):
|
||||
self._object_id = obj
|
||||
elif isinstance(obj, object_model.Object):
|
||||
self._object_id = obj.data['?']['id']
|
||||
elif isinstance(obj, murano_object.MuranoObject):
|
||||
self._object_id = obj.object_id
|
||||
elif isinstance(obj, object_model.Ref):
|
||||
self._object_id = obj.id
|
||||
else:
|
||||
raise ValueError(
|
||||
'obj should be object ID string, MuranoObject or one of '
|
||||
'object_model helper classes (Object, Ref)')
|
||||
self._preserve_exception = False
|
||||
|
||||
def __getattr__(self, item):
|
||||
def call(*args, **kwargs):
|
||||
return self._runner._execute(
|
||||
item, self._object_id, *args, **kwargs)
|
||||
if item.startswith('test'):
|
||||
return call
|
||||
|
||||
def __init__(self, model, class_loader):
|
||||
if isinstance(model, types.StringTypes):
|
||||
model = object_model.Object(model)
|
||||
if not isinstance(model, dict):
|
||||
model = object_model.build_model(model)
|
||||
if 'Objects' not in model:
|
||||
model = {'Objects': model}
|
||||
|
||||
self.executor = executor.MuranoDslExecutor(
|
||||
class_loader, environment.Environment())
|
||||
self.root = self.executor.load(model)
|
||||
|
||||
def _execute(self, name, object_id, *args, **kwargs):
|
||||
obj = self.executor.object_store.get(object_id)
|
||||
try:
|
||||
return obj.type.invoke(
|
||||
name, self.executor, obj,
|
||||
tuple(list(args) + kwargs.items()))
|
||||
except dsl_exception.MuranoPlException as e:
|
||||
if not self.preserve_exception:
|
||||
original_exception = getattr(e, 'original_exception', None)
|
||||
if not isinstance(original_exception,
|
||||
dsl_exception.MuranoPlException):
|
||||
raise original_exception
|
||||
raise
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('test'):
|
||||
return getattr(Runner.DslObjectWrapper(self.root, self), item)
|
||||
|
||||
def on(self, obj):
|
||||
return Runner.DslObjectWrapper(obj, self)
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
return results_serializer.serialize(self.root, self.executor)
|
||||
|
||||
@property
|
||||
def preserve_exception(self):
|
||||
return self._preserve_exception
|
||||
|
||||
@preserve_exception.setter
|
||||
def preserve_exception(self, value):
|
||||
self._preserve_exception = value
|
56
murano/tests/dsl/foundation/test_case.py
Normal file
56
murano/tests/dsl/foundation/test_case.py
Normal file
@ -0,0 +1,56 @@
|
||||
# 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
|
||||
|
||||
import eventlet.debug
|
||||
|
||||
from murano.tests import base
|
||||
from murano.tests.dsl.foundation import runner
|
||||
from murano.tests.dsl.foundation import test_class_loader
|
||||
|
||||
|
||||
class DslTestCase(base.MuranoTestCase):
|
||||
def setUp(self):
|
||||
super(DslTestCase, self).setUp()
|
||||
directory = os.path.join(os.path.dirname(
|
||||
inspect.getfile(self.__class__)), 'meta')
|
||||
sys_class_loader = test_class_loader.TestClassLoader(
|
||||
os.path.join(directory, '../../../../meta/io.murano/Classes'),
|
||||
'murano.io')
|
||||
self._class_loader = test_class_loader.TestClassLoader(
|
||||
directory, 'tests', sys_class_loader)
|
||||
self.register_function(
|
||||
lambda data: self._traces.append(data()), 'trace')
|
||||
self._traces = []
|
||||
|
||||
def new_runner(self, model):
|
||||
return runner.Runner(model, self.class_loader)
|
||||
|
||||
@property
|
||||
def traces(self):
|
||||
return self._traces
|
||||
|
||||
@property
|
||||
def class_loader(self):
|
||||
return self._class_loader
|
||||
|
||||
def register_function(self, func, name):
|
||||
self.class_loader.register_function(func, name)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
eventlet.debug.hub_exceptions(False)
|
85
murano/tests/dsl/foundation/test_class_loader.py
Normal file
85
murano/tests/dsl/foundation/test_class_loader.py
Normal file
@ -0,0 +1,85 @@
|
||||
# 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 glob
|
||||
import os.path
|
||||
import yaml
|
||||
|
||||
from murano.dsl import class_loader
|
||||
from murano.dsl import exceptions
|
||||
from murano.dsl import murano_package
|
||||
from murano.dsl import namespace_resolver
|
||||
from murano.engine.system import yaql_functions
|
||||
from murano.engine import yaql_yaml_loader
|
||||
|
||||
|
||||
class TestClassLoader(class_loader.MuranoClassLoader):
|
||||
def __init__(self, directory, package_name, parent_loader=None):
|
||||
self._classes = {}
|
||||
self._package = murano_package.MuranoPackage()
|
||||
self._package.name = package_name
|
||||
self._parent = parent_loader
|
||||
self._build_index(directory)
|
||||
self._functions = {}
|
||||
super(TestClassLoader, self).__init__()
|
||||
|
||||
def find_package_name(self, class_name):
|
||||
if class_name in self._classes:
|
||||
return self._package.name
|
||||
if self._parent:
|
||||
return self._parent.find_package_name(class_name)
|
||||
return None
|
||||
|
||||
def load_package(self, class_name):
|
||||
return self._package
|
||||
|
||||
def load_definition(self, name):
|
||||
try:
|
||||
return self._classes[name]
|
||||
except KeyError:
|
||||
if self._parent:
|
||||
return self._parent.load_definition(name)
|
||||
raise exceptions.NoClassFound(name)
|
||||
|
||||
def _build_index(self, directory):
|
||||
yamls = glob.glob(os.path.join(directory, '*.yaml'))
|
||||
for class_def_file in yamls:
|
||||
self._load_class(class_def_file)
|
||||
|
||||
def _load_class(self, class_def_file):
|
||||
with open(class_def_file) as stream:
|
||||
data = yaml.load(stream, yaql_yaml_loader.YaqlYamlLoader)
|
||||
|
||||
if 'Name' not in data:
|
||||
return
|
||||
|
||||
for name, method in (data.get('Methods') or data.get(
|
||||
'Workflow') or {}).iteritems():
|
||||
if name.startswith('test'):
|
||||
method['Usage'] = 'Action'
|
||||
|
||||
ns = namespace_resolver.NamespaceResolver(data.get('Namespaces', {}))
|
||||
class_name = ns.resolve_name(data['Name'])
|
||||
self._classes[class_name] = data
|
||||
|
||||
def create_root_context(self):
|
||||
context = super(TestClassLoader, self).create_root_context()
|
||||
yaql_functions.register(context)
|
||||
for name, func in self._functions.iteritems():
|
||||
context.register_function(func, name)
|
||||
return context
|
||||
|
||||
def register_function(self, func, name):
|
||||
self._functions[name] = func
|
29
murano/tests/dsl/meta/SampleClass1.yaml
Normal file
29
murano/tests/dsl/meta/SampleClass1.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
Name: SampleClass1
|
||||
|
||||
Properties:
|
||||
stringProperty:
|
||||
Contract: $.string().notNull()
|
||||
classProperty:
|
||||
Contract: $.class(SampleClass2).notNull()
|
||||
|
||||
Workflow:
|
||||
testTrace:
|
||||
Arguments:
|
||||
- intArg:
|
||||
Contract: $.int().notNull()
|
||||
Body:
|
||||
- trace($intArg)
|
||||
- trace($.stringProperty)
|
||||
- trace($.classProperty.class2Property)
|
||||
|
||||
testException:
|
||||
Body:
|
||||
- raiseException()
|
||||
|
||||
testReturn:
|
||||
Arguments:
|
||||
- intArg:
|
||||
Contract: $.int().notNull()
|
||||
Body:
|
||||
Return: $intArg
|
||||
|
6
murano/tests/dsl/meta/SampleClass2.yaml
Normal file
6
murano/tests/dsl/meta/SampleClass2.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
Name: SampleClass2
|
||||
|
||||
Properties:
|
||||
class2Property:
|
||||
Contract: $.string().notNull()
|
||||
|
60
murano/tests/dsl/test_execution.py
Normal file
60
murano/tests/dsl/test_execution.py
Normal file
@ -0,0 +1,60 @@
|
||||
# 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 dsl_exception
|
||||
from murano.tests.dsl.foundation import object_model as om
|
||||
from murano.tests.dsl.foundation import test_case
|
||||
|
||||
|
||||
class TestExecution(test_case.DslTestCase):
|
||||
def _load(self):
|
||||
return self.new_runner(
|
||||
om.Object('SampleClass1', stringProperty='STRING',
|
||||
classProperty=om.Object(
|
||||
'SampleClass2', class2Property='ANOTHER_STRING')))
|
||||
|
||||
def test_load(self):
|
||||
self._load()
|
||||
|
||||
def test_not_load(self):
|
||||
def try_load():
|
||||
self.new_runner(om.Object('SampleClass1'))
|
||||
self.assertRaises(TypeError, try_load)
|
||||
|
||||
def test_trace(self):
|
||||
runner = self._load()
|
||||
self.assertEqual(self.traces, [])
|
||||
runner.testTrace(123)
|
||||
self.assertEqual(self.traces, [123, 'STRING', 'ANOTHER_STRING'])
|
||||
runner.testTrace(321)
|
||||
self.assertEqual(self.traces, [
|
||||
123, 'STRING', 'ANOTHER_STRING',
|
||||
321, 'STRING', 'ANOTHER_STRING'])
|
||||
|
||||
def test_exception(self):
|
||||
class CustomException(Exception):
|
||||
pass
|
||||
|
||||
def raise_exception():
|
||||
raise CustomException()
|
||||
|
||||
self.register_function(raise_exception, 'raiseException')
|
||||
runner = self._load()
|
||||
self.assertRaises(CustomException, runner.testException)
|
||||
runner.preserve_exception = True
|
||||
self.assertRaises(dsl_exception.MuranoPlException,
|
||||
runner.testException)
|
||||
|
||||
def test_return(self):
|
||||
self.assertEqual(3, self._load().testReturn(3))
|
Loading…
x
Reference in New Issue
Block a user