From ac21005828da3b19c94879b34453b43c61f321d4 Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Thu, 3 Jul 2014 14:24:41 +0400 Subject: [PATCH] Unit tests for contracts in addition to unit tests: * in MuranoObject parent property renamed to owner * fixed ownership of temporary method arguments * more descriptive exceptions in contracts and related places Change-Id: I4ee491c5428a4a64a21ccb5b1947acac386eb78f Closes-Bug: #1337225 Partial-Bug: #1316786 --- murano/dsl/exceptions.py | 22 ++ murano/dsl/executor.py | 4 +- murano/dsl/murano_class.py | 8 +- murano/dsl/murano_object.py | 13 +- murano/dsl/object_store.py | 14 +- murano/dsl/results_serializer.py | 2 +- murano/dsl/type_scheme.py | 96 ++++--- murano/dsl/typespec.py | 11 +- murano/dsl/yaql_functions.py | 4 +- murano/tests/dsl/meta/ContractExamples.yaml | 137 +++++++++ murano/tests/dsl/test_contracts.py | 292 ++++++++++++++++++++ murano/tests/dsl/test_execution.py | 10 +- 12 files changed, 546 insertions(+), 67 deletions(-) create mode 100644 murano/tests/dsl/meta/ContractExamples.yaml create mode 100644 murano/tests/dsl/test_contracts.py diff --git a/murano/dsl/exceptions.py b/murano/dsl/exceptions.py index 43d7b17c..4d5d02ad 100644 --- a/murano/dsl/exceptions.py +++ b/murano/dsl/exceptions.py @@ -47,6 +47,12 @@ class NoPackageForClassFound(Exception): 'is not found' % name) +class NoObjectFoundError(Exception): + def __init__(self, object_id): + super(NoObjectFoundError, self).__init__( + 'Object %s is not found in object store' % object_id) + + class AmbiguousMethodName(Exception): def __init__(self, name): super(AmbiguousMethodName, self).__init__( @@ -57,3 +63,19 @@ class NoWriteAccess(Exception): def __init__(self, name): super(NoWriteAccess, self).__init__( 'Property %s is immutable to the caller' % name) + + +class DslContractSyntaxError(Exception): + pass + + +class ContractViolationException(Exception): + pass + + +class ValueIsMissingError(Exception): + pass + + +class DslSyntaxError(Exception): + pass diff --git a/murano/dsl/executor.py b/murano/dsl/executor.py index f138dd56..fd06755e 100644 --- a/murano/dsl/executor.py +++ b/murano/dsl/executor.py @@ -189,7 +189,7 @@ class MuranoDslExecutor(object): value = value() arg_spec = arguments_scheme[name] parameter_values[name] = arg_spec.validate( - value, this, self._root_context, self._object_store) + value, this, None, self._root_context, self._object_store) for name, arg_spec in arguments_scheme.iteritems(): if name not in parameter_values: @@ -199,7 +199,7 @@ class MuranoDslExecutor(object): this, this.type, context) parameter_values[name] = arg_spec.validate( helpers.evaluate(arg_spec.default, parameter_context), - this, self._root_context, self._object_store) + this, None, self._root_context, self._object_store) return parameter_values diff --git a/murano/dsl/murano_class.py b/murano/dsl/murano_class.py index 97809c89..44191835 100644 --- a/murano/dsl/murano_class.py +++ b/murano/dsl/murano_class.py @@ -175,17 +175,17 @@ class MuranoClass(object): return True return False - def new(self, parent, object_store, context, parameters=None, + def new(self, owner, object_store, context, parameters=None, object_id=None, **kwargs): - obj = self.object_class(self, parent, object_store, context, + obj = self.object_class(self, owner, object_store, context, object_id=object_id, **kwargs) if parameters is not None: argspec = inspect.getargspec(obj.initialize).args if '_context' in argspec: parameters['_context'] = context - if '_parent' in argspec: - parameters['_parent'] = parent + if '_owner' in argspec: + parameters['_owner'] = owner obj.initialize(**parameters) return obj diff --git a/murano/dsl/murano_object.py b/murano/dsl/murano_object.py index 10cceda7..5f188a67 100644 --- a/murano/dsl/murano_object.py +++ b/murano/dsl/murano_object.py @@ -24,12 +24,12 @@ import murano.dsl.typespec as typespec class MuranoObject(object): - def __init__(self, murano_class, parent_obj, object_store, context, + def __init__(self, murano_class, owner, object_store, context, object_id=None, known_classes=None, defaults=None, this=None): if known_classes is None: known_classes = {} - self.__parent_obj = parent_obj + self.__owner = owner self.__object_id = object_id or murano.dsl.helpers.generate_id() self.__type = murano_class self.__properties = {} @@ -42,7 +42,7 @@ class MuranoObject(object): for parent_class in murano_class.parents: name = parent_class.name if name not in known_classes: - obj = parent_class.new(parent_obj, object_store, context, + obj = parent_class.new(owner, object_store, context, None, object_id=self.__object_id, known_classes=known_classes, defaults=defaults, this=self.real_this) @@ -75,8 +75,8 @@ class MuranoObject(object): return self.__type @property - def parent(self): - return self.__parent_obj + def owner(self): + return self.__owner @property def real_this(self): @@ -145,7 +145,8 @@ class MuranoObject(object): default = murano.dsl.helpers.evaluate(default, child_context, 1) self.__properties[key] = spec.validate( - value, self, self.__context, self.__object_store, default) + value, self, self, self.__context, + self.__object_store, default) else: for parent in self.__parents.values(): try: diff --git a/murano/dsl/object_store.py b/murano/dsl/object_store.py index e550600c..70af1979 100644 --- a/murano/dsl/object_store.py +++ b/murano/dsl/object_store.py @@ -46,9 +46,7 @@ class ObjectStore(object): def put(self, murano_object): self._store[murano_object.object_id] = murano_object - def load(self, value, parent, context, defaults=None): - #tmp_store = ObjectStore(self._class_loader, self) - + def load(self, value, owner, context, defaults=None): if value is None: return None if '?' not in value or 'type' not in value['?']: @@ -62,7 +60,7 @@ class ObjectStore(object): if object_id in self._store: obj = self._store[object_id] else: - obj = class_obj.new(parent, self, context=context, + obj = class_obj.new(owner, self, context=context, object_id=object_id, defaults=defaults) self._store[object_id] = obj self._designer_attributes_store[object_id] = \ @@ -72,17 +70,17 @@ class ObjectStore(object): if '_context' in argspec: value['_context'] = context if '_parent' in argspec: - value['_parent'] = parent + value['_owner'] = owner try: - if parent is None: + if owner is None: self._initializing = True obj.initialize(**value) - if parent is None: + if owner is None: self._initializing = False obj.initialize(**value) finally: - if parent is None: + if owner is None: self._initializing = False if not self.initializing: diff --git a/murano/dsl/results_serializer.py b/murano/dsl/results_serializer.py index 21597e71..3ac59d96 100644 --- a/murano/dsl/results_serializer.py +++ b/murano/dsl/results_serializer.py @@ -89,7 +89,7 @@ def _pass1_serialize(value, parent, serialized_objects, types.BooleanType, types.NoneType)): return value elif isinstance(value, murano_object.MuranoObject): - if not _cmp_objects(value.parent, parent) \ + if not _cmp_objects(value.owner, parent) \ or value.object_id in serialized_objects: return ObjRef(value) else: diff --git a/murano/dsl/type_scheme.py b/murano/dsl/type_scheme.py index 80a2790f..70bfe357 100644 --- a/murano/dsl/type_scheme.py +++ b/murano/dsl/type_scheme.py @@ -18,6 +18,7 @@ import uuid import yaql.context +from murano.dsl import exceptions import murano.dsl.helpers import murano.dsl.murano_object import murano.dsl.yaql_expression as yaql_expression @@ -35,7 +36,7 @@ class TypeScheme(object): self._spec = spec @staticmethod - def prepare_context(root_context, this, object_store, + def prepare_context(root_context, this, owner, object_store, namespace_resolver, default): def _int(value): value = value() @@ -46,7 +47,8 @@ class TypeScheme(object): try: return int(value) except Exception: - raise TypeError() + raise exceptions.ContractViolationException( + 'Value {0} violates int() contract'.format(value)) def _string(value): value = value() @@ -57,7 +59,8 @@ class TypeScheme(object): try: return unicode(value) except Exception: - raise TypeError() + raise exceptions.ContractViolationException( + 'Value {0} violates string() contract'.format(value)) def _bool(value): value = value() @@ -74,18 +77,20 @@ class TypeScheme(object): return value if value is None: - raise TypeError() + raise exceptions.ContractViolationException( + 'null value violates notNull() contract') return value def _error(): - raise TypeError() + raise exceptions.ContractViolationException('error() contract') def _check(value, predicate): value = value() if isinstance(value, TypeScheme.ObjRef) or predicate(value): return value else: - raise TypeError(value) + raise exceptions.ContractViolationException( + "Value {0} doesn't match predicate".format(value)) @yaql.context.EvalArg('obj', arg_type=( murano.dsl.murano_object.MuranoObject, @@ -98,10 +103,16 @@ class TypeScheme(object): if obj is None: return None - elif obj.parent is this: - return obj - else: - raise TypeError() + + p = obj.owner + while p is not None: + if p is this: + return obj + p = p.owner + + raise exceptions.ContractViolationException( + 'Object {0} violates owned() contract'.format( + obj.object_id)) @yaql.context.EvalArg('obj', arg_type=( murano.dsl.murano_object.MuranoObject, @@ -114,10 +125,15 @@ class TypeScheme(object): if obj is None: return None - elif obj.parent is this: - raise TypeError() - else: + + try: + _owned(obj) + except exceptions.ContractViolationException: return obj + else: + raise exceptions.ContractViolationException( + 'Object {0} violates notOwned() contract'.format( + obj.object_id)) @yaql.context.EvalArg('name', arg_type=str) def _class(value, name): @@ -132,35 +148,41 @@ class TypeScheme(object): else: default_name = namespace_resolver.resolve_name(default_name) value = value() - if value is NoValue: - value = default - if isinstance(default, types.DictionaryType): - value = {'?': { - 'id': uuid.uuid4().hex, - 'type': default_name - }} class_loader = murano.dsl.helpers.get_class_loader(root_context) murano_class = class_loader.get_class(name) if not murano_class: - raise TypeError() + raise exceptions.NoClassFound( + 'Class {0} cannot be found'.format(name)) if value is None: return None if isinstance(value, murano.dsl.murano_object.MuranoObject): obj = value elif isinstance(value, types.DictionaryType): - obj = object_store.load(value, this, root_context, + if '?' not in value: + new_value = {'?': { + 'id': uuid.uuid4().hex, + 'type': default_name + }} + new_value.update(value) + value = new_value + + obj = object_store.load(value, owner, root_context, defaults=default) elif isinstance(value, types.StringTypes): obj = object_store.get(value) if obj is None: if not object_store.initializing: - raise TypeError('Object %s not found' % value) + raise exceptions.NoObjectFoundError(value) else: return TypeScheme.ObjRef(value) else: - raise TypeError() + raise exceptions.ContractViolationException( + 'Value {0} cannot be represented as class {1}'.format( + value, name)) if not murano_class.is_compatible(obj): - raise TypeError() + raise exceptions.ContractViolationException( + 'Object of type {0} is not compatible with ' + 'requested type {1}'.format(obj.type.name, name)) return obj @yaql.context.EvalArg('prefix', str) @@ -187,7 +209,8 @@ class TypeScheme(object): if data is None or data is NoValue: data = {} if not isinstance(data, types.DictionaryType): - raise TypeError() + raise exceptions.ContractViolationException( + 'Supplied is not of a dictionary type') if not spec: return data result = {} @@ -195,7 +218,9 @@ class TypeScheme(object): for key, value in spec.iteritems(): if isinstance(key, yaql_expression.YaqlExpression): if yaql_key is not None: - raise SyntaxError() + raise exceptions.DslContractSyntaxError( + 'Dictionary contract ' + 'cannot have more than one expression keys') else: yaql_key = key else: @@ -232,7 +257,9 @@ class TypeScheme(object): shift += 1 if not min_length <= len(data) <= max_length: - raise TypeError() + raise exceptions.ContractViolationException( + 'Array length {0} is not within [{1}..{2}] range'.format( + len(data), min_length, max_length)) for index, item in enumerate(data): spec_item = spec[-1 - shift] \ @@ -242,7 +269,8 @@ class TypeScheme(object): def _map_scalar(self, data, spec): if data != spec: - raise TypeError() + raise exceptions.ContractViolationException( + 'Value {0} is not equal to {1}'.format(data, spec)) else: return data @@ -260,7 +288,7 @@ class TypeScheme(object): types.NoneType)): return self._map_scalar(data, spec) - def __call__(self, data, context, this, object_store, + def __call__(self, data, context, this, owner, object_store, namespace_resolver, default): # TODO(ativelkov, slagun): temporary fix, need a better way of handling # composite defaults @@ -270,9 +298,5 @@ class TypeScheme(object): data = default context = self.prepare_context( - context, this, object_store, namespace_resolver, - default) - result = self._map(data, self._spec, context) - if result is NoValue: - raise TypeError('No type specified') - return result + context, this, owner, object_store, namespace_resolver, default) + return self._map(data, self._spec, context) diff --git a/murano/dsl/typespec.py b/murano/dsl/typespec.py index 88a4a525..8241f59a 100644 --- a/murano/dsl/typespec.py +++ b/murano/dsl/typespec.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from murano.dsl import exceptions import murano.dsl.type_scheme as type_scheme @@ -33,13 +34,15 @@ class Spec(object): self._has_default = 'Default' in declaration self._usage = declaration.get('Usage') or 'In' if self._usage not in PropertyUsages.All: - raise SyntaxError('Unknown type {0}. Must be one of ({1})'.format( - self._usage, ', '.join(PropertyUsages.All))) + raise exceptions.DslSyntaxError( + 'Unknown type {0}. Must be one of ({1})'.format( + self._usage, ', '.join(PropertyUsages.All))) - def validate(self, value, this, context, object_store, default=None): + def validate(self, value, this, owner, context, + object_store, default=None): if default is None: default = self.default - return self._contract(value, context, this, object_store, + return self._contract(value, context, this, owner, object_store, self._namespace_resolver, default) @property diff --git a/murano/dsl/yaql_functions.py b/murano/dsl/yaql_functions.py index 10f143e2..29971952 100644 --- a/murano/dsl/yaql_functions.py +++ b/murano/dsl/yaql_functions.py @@ -124,11 +124,11 @@ def _get_container(context, obj, class_name): class_loader = helpers.get_class_loader(context) class_name = namespace_resolver.resolve_name(class_name) murano_class = class_loader.get_class(class_name) - p = obj.parent + p = obj.owner while p is not None: if murano_class.is_compatible(p): return p - p = p.parent + p = p.owner return None diff --git a/murano/tests/dsl/meta/ContractExamples.yaml b/murano/tests/dsl/meta/ContractExamples.yaml new file mode 100644 index 00000000..038c6780 --- /dev/null +++ b/murano/tests/dsl/meta/ContractExamples.yaml @@ -0,0 +1,137 @@ +Name: ContractExamples + +Properties: + sampleClass: + Contract: $.class(SampleClass1) + +Methods: + testStringContract: + Arguments: + arg: + Contract: $.string() + Body: + Return: $arg + + testIntContract: + Arguments: + arg: + Contract: $.int() + Body: + Return: $arg + + testBoolContract: + Arguments: + arg: + Contract: $.bool() + Body: + Return: $arg + + testClassContract: + Arguments: + arg: + Contract: $.class(SampleClass2) + Body: + Return: $arg + + testClassFromIdContract: + Arguments: + arg: + Contract: $.class(SampleClass1) + Body: + Return: $arg + + testCheckContract: + Arguments: + - arg1: + Contract: $.class(SampleClass2).check($.class2Property = qwerty) + - arg2: + Contract: $.int().check($ > 10) + + testOwnedContract: + Arguments: + - arg1: + Contract: $.class(SampleClass1).owned() + - arg2: + Contract: $.class(SampleClass2).owned() + + testNotOwnedContract: + Arguments: + - arg1: + Contract: $.class(SampleClass1).notOwned() + - arg2: + Contract: $.class(SampleClass2).notOwned() + + testScalarContract: + Arguments: + - arg1: + Contract: 'fixed' + - arg2: + Contract: 456 + - arg3: + Contract: true + Body: + Return: $arg1 + + testListContract: + Arguments: + - arg: + Contract: [$.int()] + Body: + Return: $arg + + testListWithMinLengthContract: + Arguments: + - arg: + Contract: [$.int(), 3] + Body: + Return: $arg + + testListWithMinMaxLengthContract: + Arguments: + - arg: + Contract: [$.int(), 2, 4] + Body: + Return: $arg + + testDictContract: + Arguments: + - arg: + Contract: + A: $.string() + B: $.int() + Body: + Return: $arg + + testDictExprContract: + Arguments: + - arg: + Contract: + $.int(): $.string() + B: $.int() + Body: + Return: $arg + + testDictMultiExprContract: + Arguments: + - arg: + Contract: + $.int(): $.string() + $.string(): $.int() + Body: + Return: $arg + + testNotNullContract: + Arguments: + - arg: + Contract: $.notNull() + Body: + Return: $arg + + testDefault: + Arguments: + - arg: + Contract: $.string() + Default: DEFAULT + Body: + Return: $arg + diff --git a/murano/tests/dsl/test_contracts.py b/murano/tests/dsl/test_contracts.py new file mode 100644 index 00000000..d3e66978 --- /dev/null +++ b/murano/tests/dsl/test_contracts.py @@ -0,0 +1,292 @@ +# 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 exceptions +from murano.dsl import murano_object +from murano.tests.dsl.foundation import object_model as om +from murano.tests.dsl.foundation import test_case + + +class TestContracts(test_case.DslTestCase): + def setUp(self): + super(TestContracts, self).setUp() + self._runner = self.new_runner( + om.Object( + 'ContractExamples', + sampleClass=om.Object( + 'SampleClass1', + stringProperty='string1', + classProperty=om.Object( + 'SampleClass2', + class2Property='string2')))) + + def test_string_contract(self): + result = self._runner.testStringContract('qwerty') + self.assertIsInstance(result, types.StringTypes) + self.assertEqual(result, 'qwerty') + + def test_string_from_number_contract(self): + result = self._runner.testStringContract(123) + self.assertIsInstance(result, types.StringTypes) + self.assertEqual(result, '123') + + def test_string_null_contract(self): + self.assertIsNone(self._runner.testStringContract(None)) + + def test_int_contract(self): + result = self._runner.testIntContract(123) + self.assertIsInstance(result, int) + self.assertEqual(result, 123) + + def test_int_from_string_contract(self): + result = self._runner.testIntContract('456') + self.assertIsInstance(result, int) + self.assertEqual(result, 456) + + def test_int_from_string_contract_failure(self): + self.assertRaises(exceptions.ContractViolationException, + self._runner.testIntContract, 'nan') + + def test_int_null_contract(self): + self.assertIsNone(self._runner.testIntContract(None)) + + def test_bool_contract(self): + result = self._runner.testBoolContract(True) + self.assertIsInstance(result, bool) + self.assertIs(result, True) + + result = self._runner.testBoolContract(False) + self.assertIsInstance(result, bool) + self.assertIs(result, False) + + def test_bool_from_int_contract(self): + result = self._runner.testBoolContract(10) + self.assertIsInstance(result, bool) + self.assertIs(result, True) + + result = self._runner.testBoolContract(0) + self.assertIsInstance(result, bool) + self.assertIs(result, False) + + def test_bool_from_string_contract(self): + result = self._runner.testBoolContract('something') + self.assertIsInstance(result, bool) + self.assertIs(result, True) + + result = self._runner.testBoolContract('') + self.assertIsInstance(result, bool) + self.assertIs(result, False) + + def test_bool_null_contract(self): + self.assertIsNone(self._runner.testIntContract(None)) + + def test_class_contract(self): + arg = om.Object('SampleClass2', class2Property='qwerty') + result = self._runner.testClassContract(arg) + self.assertIsInstance(result, murano_object.MuranoObject) + + def test_class_contract_by_ref(self): + arg = om.Object('SampleClass2', class2Property='qwerty') + result = self._runner.testClassContract(arg) + self.assertEqual(result.object_id, arg.id) + + def test_class_contract_failure(self): + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testClassContract, ['invalid type']) + + def test_class_contract_by_ref_failure(self): + self.assertRaises( + exceptions.NoObjectFoundError, + self._runner.testClassContract, 'NoSuchIdExists') + + def test_class_contract_from_dict(self): + self.assertEqual( + 'SampleClass2', + self._runner.testClassContract({ + 'class2Property': 'str'}).type.name) + + def test_class_from_id_contract(self): + object_id = self._runner.root.get_property('sampleClass').object_id + result = self._runner.testClassFromIdContract(object_id) + self.assertIsInstance(result, murano_object.MuranoObject) + self.assertEqual(result.object_id, object_id) + + def test_check_contract(self): + arg = om.Object('SampleClass2', class2Property='qwerty') + self.assertIsNone(self._runner.testCheckContract(arg, 100)) + + def test_check_contract_failure(self): + invalid_arg = om.Object('SampleClass2', class2Property='not qwerty') + self.assertRaises(exceptions.ContractViolationException, + self._runner.testCheckContract, invalid_arg, 100) + + def test_owned_contract(self): + arg1 = self._runner.root.get_property('sampleClass') + arg2 = arg1.get_property('classProperty') + self.assertIsNone(self._runner.testOwnedContract(arg1, arg2)) + + def test_owned_contract_on_null(self): + self.assertIsNone(self._runner.testOwnedContract(None, None)) + + def test_owned_contract_failure(self): + arg1 = self._runner.root.get_property('sampleClass') + arg2 = arg1.get_property('classProperty') + invalid_arg2 = om.Object('SampleClass2', class2Property='string2') + invalid_arg1 = om.Object( + 'SampleClass1', + stringProperty='string1', + classProperty=invalid_arg2) + + self.assertRaises(exceptions.ContractViolationException, + self._runner.testOwnedContract, invalid_arg1, arg2) + self.assertRaises(exceptions.ContractViolationException, + self._runner.testOwnedContract, invalid_arg2, arg1) + + def test_not_owned_contract(self): + arg2 = om.Object('SampleClass2', class2Property='string2') + arg1 = om.Object( + 'SampleClass1', + stringProperty='string1', + classProperty=arg2) + self.assertIsNone(self._runner.testNotOwnedContract(arg1, arg2)) + + def test_not_owned_contract_on_null(self): + self.assertIsNone(self._runner.testNotOwnedContract(None, None)) + + def test_not_owned_contract_failure(self): + invalid_arg1 = self._runner.root.get_property('sampleClass') + invalid_arg2 = invalid_arg1.get_property('classProperty') + arg2 = om.Object('SampleClass2', class2Property='string2') + arg1 = om.Object( + 'SampleClass1', + stringProperty='string1', + classProperty=arg2) + + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testNotOwnedContract, invalid_arg1, arg2) + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testNotOwnedContract, invalid_arg2, arg1) + + def test_scalar_contract(self): + self.assertEqual('fixed', self._runner.testScalarContract( + 'fixed', 456, True)) + + def test_scalar_contract_failure(self): + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testScalarContract, + 'wrong', 456, True) + + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testScalarContract, + 'fixed', 123, True) + + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testScalarContract, + 'fixed', 456, False) + + def test_list_contract(self): + self.assertEqual([3, 2, 1], self._runner.testListContract( + ['3', 2, '1'])) + + def test_list_contract_from_scalar(self): + self.assertEqual([99], self._runner.testListContract('99')) + + def test_list_contract_from_null(self): + self.assertEqual([], self._runner.testListContract(None)) + + def test_list_with_min_length_contract(self): + self.assertEqual( + [1, 2, 3], + self._runner.testListWithMinLengthContract([1, 2, 3])) + self.assertEqual( + [1, 2, 3, 4], + self._runner.testListWithMinLengthContract([1, 2, 3, 4])) + + def test_list_with_min_length_contract_failure(self): + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testListWithMinLengthContract, None) + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testListWithMinLengthContract, [1, 2]) + + def test_list_with_min_max_length_contract(self): + self.assertEqual( + [1, 2], + self._runner.testListWithMinMaxLengthContract([1, 2])) + self.assertEqual( + [1, 2, 3, 4], + self._runner.testListWithMinMaxLengthContract([1, 2, 3, 4])) + + def test_list_with_min_max_length_contract_failure(self): + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testListWithMinMaxLengthContract, [1]) + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testListWithMinMaxLengthContract, [1, 2, 3, 4, 5]) + + def test_dict_contract(self): + self.assertEqual( + {'A': '123', 'B': 456}, + self._runner.testDictContract({'A': '123', 'B': '456'})) + self.assertEqual( + {'A': '123', 'B': 456}, + self._runner.testDictContract({'A': '123', 'B': '456', 'C': 'qq'})) + self.assertEqual( + {'A': '123', 'B': None}, + self._runner.testDictContract({'A': '123'})) + + def test_dict_contract_failure(self): + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testDictContract, 'str') + + def test_dict_expressions_contract(self): + self.assertEqual( + {321: 'qwerty', 99: 'val', 'B': 456}, + self._runner.testDictExprContract({ + '321': 'qwerty', '99': 'val', 'B': 456})) + + def test_dict_expressions_contract_failure(self): + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testDictExprContract, + {'321': 'qwerty', 'str': 'val', 'B': 456}) + + def test_invalid_dict_expr_contract(self): + self.assertRaises( + exceptions.DslContractSyntaxError, + self._runner.testDictMultiExprContract, + {'321': 'qwerty', 'str': 'val', 'B': 456}) + + def test_not_null_contract(self): + self.assertEqual('value', self._runner.testNotNullContract('value')) + + def test_not_null_contract_failure(self): + self.assertRaises( + exceptions.ContractViolationException, + self._runner.testNotNullContract, None) + + def test_default(self): + self.assertEqual('value', self._runner.testDefault('value')) + self.assertEqual('DEFAULT', self._runner.testDefault()) diff --git a/murano/tests/dsl/test_execution.py b/murano/tests/dsl/test_execution.py index ec93d1f5..56ee678d 100644 --- a/murano/tests/dsl/test_execution.py +++ b/murano/tests/dsl/test_execution.py @@ -13,6 +13,7 @@ # under the License. from murano.dsl import dsl_exception +from murano.dsl import exceptions from murano.tests.dsl.foundation import object_model as om from murano.tests.dsl.foundation import test_case @@ -27,10 +28,11 @@ class TestExecution(test_case.DslTestCase): 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_load_failure(self): + self.assertRaises( + exceptions.ContractViolationException, + self.new_runner, + om.Object('SampleClass1')) def test_trace(self): runner = self._load()