ad6b8ece18
* Added package property to MuranoClass * Added new class loader: PackageClassLoader * Support loading packages from API and file-system * Extended ApplicationPackage to support getting resources from packages * Rewritten ResourceLoader to support loading resources from packages Co-Authored-By: Serg Melikyan <smelikyan@mirantis.com> Change-Id: I47e70f960104f78433c285411328f315638186da
486 lines
19 KiB
Python
486 lines
19 KiB
Python
# 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 re
|
|
|
|
import mock
|
|
import unittest2 as unittest
|
|
import yaql
|
|
|
|
import muranoapi.dsl.exceptions as exceptions
|
|
import muranoapi.dsl.helpers as helpers
|
|
import muranoapi.dsl.murano_class as murano_class
|
|
import muranoapi.dsl.murano_object as murano_object
|
|
import muranoapi.dsl.namespace_resolver as ns_resolver
|
|
import muranoapi.dsl.typespec as typespec
|
|
import muranoapi.dsl.yaql_expression as yaql_expression
|
|
|
|
ROOT_CLASS = 'io.murano.Object'
|
|
|
|
|
|
class TestNamespaceResolving(unittest.TestCase):
|
|
def test_fails_w_empty_name(self):
|
|
resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
|
|
|
|
self.assertRaises(ValueError, resolver.resolve_name, None)
|
|
|
|
def test_fails_w_unknown_prefix(self):
|
|
resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
|
|
name = 'unknown_prefix:example.murano'
|
|
|
|
self.assertRaises(KeyError, resolver.resolve_name, name)
|
|
|
|
def test_fails_w_prefix_wo_name(self):
|
|
resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
|
|
name = 'sys:'
|
|
|
|
self.assertRaises(NameError, resolver.resolve_name, name)
|
|
|
|
def test_fails_w_excessive_prefix(self):
|
|
ns = {'sys': 'com.example.murano.system'}
|
|
resolver = ns_resolver.NamespaceResolver(ns)
|
|
invalid_name = 'sys:excessive_ns:muranoResource'
|
|
|
|
self.assertRaises(NameError, resolver.resolve_name, invalid_name)
|
|
|
|
def test_cuts_empty_prefix(self):
|
|
resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
|
|
# name without prefix delimiter
|
|
name = 'some.arbitrary.name'
|
|
|
|
resolved_name = resolver.resolve_name(':' + name)
|
|
|
|
self.assertEqual(name, resolved_name)
|
|
|
|
def test_resolves_specified_ns_prefix(self):
|
|
ns = {'sys': 'com.example.murano.system'}
|
|
resolver = ns_resolver.NamespaceResolver(ns)
|
|
short_name, full_name = 'sys:File', 'com.example.murano.system.File'
|
|
|
|
resolved_name = resolver.resolve_name(short_name)
|
|
|
|
self.assertEqual(full_name, resolved_name)
|
|
|
|
def test_resolves_current_ns(self):
|
|
resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
|
|
short_name, full_name = 'Resource', 'com.example.murano.Resource'
|
|
|
|
resolved_name = resolver.resolve_name(short_name)
|
|
|
|
self.assertEqual(full_name, resolved_name)
|
|
|
|
def test_resolves_explicit_base(self):
|
|
resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
|
|
|
|
resolved_name = resolver.resolve_name('Resource', relative='com.base')
|
|
|
|
self.assertEqual('com.base.Resource', resolved_name)
|
|
|
|
def test_resolves_explicit_base_w_empty_namespaces(self):
|
|
resolver = ns_resolver.NamespaceResolver({})
|
|
|
|
resolved_name = resolver.resolve_name('File', 'com.base')
|
|
|
|
self.assertEqual('com.base.File', resolved_name)
|
|
|
|
def test_resolves_w_empty_namespaces(self):
|
|
resolver = ns_resolver.NamespaceResolver({})
|
|
|
|
resolved_name = resolver.resolve_name('Resource')
|
|
|
|
self.assertEqual('Resource', resolved_name)
|
|
|
|
|
|
class Bunch(object):
|
|
def __init__(self, **kwargs):
|
|
super(Bunch, self).__init__()
|
|
for key, value in kwargs.iteritems():
|
|
setattr(self, key, value)
|
|
|
|
|
|
class TestClassesManipulation(unittest.TestCase):
|
|
resolver = mock.Mock(resolve_name=lambda name: name)
|
|
|
|
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)
|
|
|
|
@unittest.skip
|
|
def test_add_property_to_class(self):
|
|
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)
|
|
|
|
@unittest.skip
|
|
def test_class_property_search(self):
|
|
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(unittest.TestCase):
|
|
def setUp(self):
|
|
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 = 'muranoapi.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)
|
|
|
|
@unittest.skip
|
|
def test_fails_internal_property_access(self):
|
|
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)
|
|
|
|
@unittest.skip
|
|
def test_proper_property_access(self):
|
|
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)
|
|
|
|
@unittest.skip
|
|
def test_parent_class_property_access(self):
|
|
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)
|
|
|
|
@unittest.skip
|
|
def test_fails_on_parents_property_collision(self):
|
|
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)
|
|
|
|
@unittest.skip
|
|
def test_fails_forbidden_set_property(self):
|
|
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)
|
|
|
|
@unittest.skip
|
|
def test_set_property(self):
|
|
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('muranoapi.engine.helpers') as helpers_mock:
|
|
helpers_mock.evaluate = lambda val, ctx, _: val
|
|
obj.set_property('someProperty', 10)
|
|
|
|
self.assertEqual(10, obj.someProperty)
|
|
|
|
@unittest.skip
|
|
def test_set_parent_property(self):
|
|
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('muranoapi.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)
|
|
|
|
@unittest.skip
|
|
def test_object_up_cast(self):
|
|
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(unittest.TestCase):
|
|
def test_generate_id(self):
|
|
generated_id = helpers.generate_id()
|
|
|
|
self.assertTrue(re.match(r'[a-z0-9]{32}', generated_id))
|
|
|
|
def test_evaluate(self):
|
|
yaql_value = mock.Mock(spec=yaql_expression.YaqlExpression,
|
|
evaluate=lambda context: 'atom')
|
|
complex_value = {yaql_value: ['some', (1, yaql_value), lambda: 'hi!'],
|
|
'sample': [yaql_value, xrange(5)]}
|
|
complex_literal = {'atom': ['some', (1, 'atom'), 'hi!'],
|
|
'sample': ['atom', [0, 1, 2, 3, 4]]}
|
|
# tuple(evaluate(list)) transformation adds + 1
|
|
complex_literal_depth = 3 + 1
|
|
|
|
evaluated_value = helpers.evaluate(yaql_value, None, 1)
|
|
non_evaluated_value = helpers.evaluate(yaql_value, None, 0)
|
|
evaluated_complex_value = helpers.evaluate(complex_value, None)
|
|
non_evaluated_complex_value = helpers.evaluate(
|
|
complex_value, None, complex_literal_depth)
|
|
|
|
self.assertEqual('atom', evaluated_value)
|
|
self.assertNotEqual('atom', non_evaluated_value)
|
|
self.assertEqual(complex_literal, evaluated_complex_value)
|
|
self.assertNotEqual(complex_literal, non_evaluated_complex_value)
|
|
|
|
def test_needs_evaluation(self):
|
|
testee = helpers.needs_evaluation
|
|
parsed_expr = yaql.parse("string")
|
|
yaql_expr = yaql_expression.YaqlExpression("string")
|
|
|
|
self.assertTrue(testee(parsed_expr))
|
|
self.assertTrue(testee(yaql_expr))
|
|
self.assertTrue(testee({yaql_expr: 1}))
|
|
self.assertTrue(testee({'label': yaql_expr}))
|
|
self.assertTrue(testee([yaql_expr]))
|
|
|
|
|
|
class TestYaqlExpression(unittest.TestCase):
|
|
def test_expression(self):
|
|
yaql_expr = yaql_expression.YaqlExpression('string')
|
|
|
|
self.assertEqual('string', yaql_expr.expression())
|
|
|
|
def test_evaluate_calls(self):
|
|
string = 'string'
|
|
expected_calls = [mock.call(string),
|
|
mock.call().evaluate(context=None)]
|
|
|
|
with mock.patch('yaql.parse') as mock_parse:
|
|
yaql_expr = yaql_expression.YaqlExpression(string)
|
|
yaql_expr.evaluate()
|
|
|
|
self.assertEqual(expected_calls, mock_parse.mock_calls)
|
|
|
|
def test_match_returns(self):
|
|
expr = yaql_expression.YaqlExpression('string')
|
|
|
|
with mock.patch('yaql.parse'):
|
|
self.assertTrue(expr.match('$some'))
|
|
self.assertTrue(expr.match('$.someMore'))
|
|
|
|
with mock.patch('yaql.parse') as parse_mock:
|
|
parse_mock.side_effect = yaql.exceptions.YaqlGrammarException
|
|
self.assertFalse(expr.match(''))
|
|
|
|
with mock.patch('yaql.parse') as parse_mock:
|
|
parse_mock.side_effect = yaql.exceptions.YaqlLexicalException
|
|
self.assertFalse(expr.match(''))
|