From d32175746810a22b551e0d8aa3dd74c9040c642e Mon Sep 17 00:00:00 2001 From: Ekaterina Chernova Date: Thu, 3 Dec 2015 17:55:41 +0300 Subject: [PATCH] [mocking-machinery] Add inject YAQL functions Functions for injecting class methods or objects with mocks are added: * mock can be provided as method name * mock can be provided as YAQL expression * Also, withOriginal function is added. withOriginal allows to use data from the original context in mock function. Targets blueprint mock-context-manager Depends-On: I9a55d07188ff06bdf98248f011a700c297e33417 Change-Id: Iefee767390209bfad0cca4f39389e0205cd3c9e2 --- murano/dsl/constants.py | 1 + murano/engine/mock_context_manager.py | 84 +++++++++++++++++++ murano/tests/unit/engine/meta/TestMock.yaml | 59 +++++++++++++ .../unit/engine/meta/TestMockFixture.yaml | 11 +++ .../unit/engine/test_mock_context_manager.py | 73 ++++++++++++++++ .../notes/enable-mocks-a156e7cc1b1d5066.yaml | 24 ++++++ 6 files changed, 252 insertions(+) create mode 100644 murano/tests/unit/engine/meta/TestMock.yaml create mode 100644 murano/tests/unit/engine/meta/TestMockFixture.yaml create mode 100644 releasenotes/notes/enable-mocks-a156e7cc1b1d5066.yaml diff --git a/murano/dsl/constants.py b/murano/dsl/constants.py index 77a247a8..af39a9ce 100644 --- a/murano/dsl/constants.py +++ b/murano/dsl/constants.py @@ -33,6 +33,7 @@ CTX_THIS = '$?this' CTX_TYPE = '$?type' CTX_VARIABLE_SCOPE = '$?variableScope' CTX_YAQL_ENGINE = '$?yaqlEngine' +CTX_ORIGINAL_CONTEXT = '$?originalContext' DM_OBJECTS = 'Objects' DM_OBJECTS_COPY = 'ObjectsCopy' diff --git a/murano/engine/mock_context_manager.py b/murano/engine/mock_context_manager.py index 13cbaf45..9db18424 100644 --- a/murano/engine/mock_context_manager.py +++ b/murano/engine/mock_context_manager.py @@ -13,7 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from yaql.language import specs +from yaql.language import yaqltypes + from murano.common import engine +from murano.dsl import constants +from murano.dsl import dsl +from murano.dsl import dsl_types +from murano.dsl import helpers from murano.dsl import linked_context from murano.dsl import yaql_integration @@ -68,3 +75,80 @@ class MockContextManager(engine.ContextManager): else: result_context = original_context return result_context + + def create_root_context(self, runtime_version): + root_context = super(MockContextManager, self).create_root_context( + runtime_version) + constext = root_context.create_child_context() + constext.register_function(inject_method_with_str, name='inject') + constext.register_function(inject_method_with_yaql_expr, + name='inject') + constext.register_function(with_original) + return constext + + +@specs.parameter('kwargs', yaqltypes.Lambda(with_context=True)) +def with_original(context, **kwargs): + new_context = context.create_child_context() + + original_context = context[constants.CTX_ORIGINAL_CONTEXT] + for k, v in kwargs.iteritems(): + new_context['$' + k] = v(original_context) + return new_context + + +@specs.parameter('target', + dsl.OneOf(dsl.MuranoTypeName(), dsl_types.MuranoObject)) +@specs.parameter('target_method', yaqltypes.String()) +@specs.parameter('mock_object', dsl_types.MuranoObject) +@specs.parameter('mock_name', yaqltypes.String()) +def inject_method_with_str(context, target, target_method, + mock_object, mock_name): + ctx_manager = helpers.get_executor(context).context_manager + + current_class = helpers.get_type(context) + mock_func = current_class.find_single_method(mock_name) + + if isinstance(target, dsl_types.MuranoClassReference): + original_class = target.murano_class + else: + original_class = target.type + + original_function = original_class.find_single_method(target_method) + result_fd = original_function.yaql_function_definition.clone() + + def payload_adapter(__context, __sender, *args, **kwargs): + executor = helpers.get_executor(__context) + return mock_func.invoke( + executor, mock_object, args, kwargs, __context, True) + + result_fd.payload = payload_adapter + existing_mocks = ctx_manager.class_mock_ctx.setdefault( + original_class.name, []) + existing_mocks.append(result_fd) + + +@specs.parameter('target', + dsl.OneOf(dsl.MuranoTypeName(), dsl_types.MuranoObject)) +@specs.parameter('target_method', yaqltypes.String()) +@specs.parameter('expr', yaqltypes.Lambda(with_context=True)) +def inject_method_with_yaql_expr(context, target, target_method, expr): + ctx_manager = helpers.get_executor(context).context_manager + if isinstance(target, dsl_types.MuranoClassReference): + original_class = target.murano_class + else: + original_class = target.type + + original_function = original_class.find_single_method(target_method) + result_fd = original_function.yaql_function_definition.clone() + + def payload_adapter(__super, __context, __sender, *args, **kwargs): + new_context = context.create_child_context() + new_context[constants.CTX_ORIGINAL_CONTEXT] = __context + return expr(new_context, __sender, *args, **kwargs) + + result_fd.payload = payload_adapter + result_fd.insert_parameter('__super', yaqltypes.Super()) + existing_mocks = ctx_manager.class_mock_ctx.setdefault( + original_class.name, []) + existing_mocks.append(result_fd) diff --git a/murano/tests/unit/engine/meta/TestMock.yaml b/murano/tests/unit/engine/meta/TestMock.yaml new file mode 100644 index 00000000..76d17ed9 --- /dev/null +++ b/murano/tests/unit/engine/meta/TestMock.yaml @@ -0,0 +1,59 @@ +Name: TestMocks + +Methods: + + mock1: + Body: + - Return: 'This is mock1' + + testInjectMethodWithString: + Body: + - inject(TestMocksFixture, simpleMethod1, $this, mock1) + + - $originalClass: new(TestMocksFixture) + - $returnValue: $originalClass.simpleMethod1() + - trace($returnValue) + + testInjectObjectWithString: + Body: + - $originalClass: new(TestMocksFixture) + - inject($originalClass, simpleMethod1, $this, mock1) + + - $returnValue: $originalClass.simpleMethod1() + - trace($returnValue) + + testInjectMethodWithYaqlExpr: + Body: + - $originalClass: new(TestMocksFixture) + # Calling original method without mocking + - trace($originalClass.simpleMethod1()) + + - $mockText: 'I am mock' + - inject(TestMocksFixture, simpleMethod1, trace($mockText)) + + # Calling original method after mocking + - $originalClass.simpleMethod1() + + testInjectObjectWithYaqlExpr: + Body: + - $originalClass: new(TestMocksFixture) + # Calling original method without mocking + - trace($originalClass.simpleMethod1()) + + - $mockText: 'I am mock' + - inject($originalClass, simpleMethod1, trace($mockText)) + + # Calling original method after mocking + - $originalClass.simpleMethod1() + + testWithoriginal: + Body: + - $originalClass: new(TestMocksFixture) + - inject(TestMocksFixture, simpleMethod1, withOriginal(t => $originalClass.someProperty) -> trace($t)) + - $originalClass.simpleMethod1() + + testOriginalMethod: + Body: + - $originalClass: new(TestMocksFixture) + - inject(TestMocksFixture, simpleMethod1, originalMethod()) + - trace($originalClass.simpleMethod1()) \ No newline at end of file diff --git a/murano/tests/unit/engine/meta/TestMockFixture.yaml b/murano/tests/unit/engine/meta/TestMockFixture.yaml new file mode 100644 index 00000000..f713056c --- /dev/null +++ b/murano/tests/unit/engine/meta/TestMockFixture.yaml @@ -0,0 +1,11 @@ +Name: TestMocksFixture + +Properties: + someProperty: + Contract: $.string().notNull() + Default: DEFAULT + +Methods: + simpleMethod1: + Body: + - Return: 'method1' diff --git a/murano/tests/unit/engine/test_mock_context_manager.py b/murano/tests/unit/engine/test_mock_context_manager.py index a6cdd631..a8b530b8 100644 --- a/murano/tests/unit/engine/test_mock_context_manager.py +++ b/murano/tests/unit/engine/test_mock_context_manager.py @@ -14,9 +14,16 @@ import mock from yaql import contexts from yaql import specs +from murano.dsl import constants +from murano.dsl import executor from murano.dsl import murano_class +from murano.engine import environment from murano.engine import mock_context_manager from murano.tests.unit import base +from murano.tests.unit.dsl.foundation import object_model as om +from murano.tests.unit.dsl.foundation import runner +from murano.tests.unit.dsl.foundation import test_case + FIXTURE_CLASS = 'io.murano.system.Agent' FIXTURE_FUNC = 'call' @@ -26,6 +33,34 @@ def _get_fd(set_to_extract): return list(set_to_extract)[0] +class TestMockContextManager(mock_context_manager.MockContextManager): + def __init__(self, functions): + super(TestMockContextManager, self).__init__() + self.__functions = functions + + def create_root_context(self, runtime_version): + root_context = super(TestMockContextManager, self).create_root_context( + runtime_version) + context = root_context.create_child_context() + for name, func in self.__functions.iteritems(): + context.register_function(func, name) + return context + + +class MockRunner(runner.Runner): + def __init__(self, model, package_loader, functions): + if isinstance(model, basestring): + model = om.Object(model) + model = om.build_model(model) + if 'Objects' not in model: + model = {'Objects': model} + + self.executor = executor.MuranoDslExecutor( + package_loader, TestMockContextManager(functions), + environment.Environment()) + self._root = self.executor.load(model).object + + class TestMockManager(base.MuranoTestCase): def test_create_class_context(self): @@ -54,3 +89,41 @@ class TestMockManager(base.MuranoTestCase): # Mock function should go first, but result context should contain both self.assertIs(mock_function, _get_fd(all_functions[0])) self.assertIs(original_function, _get_fd(all_functions[1])) + + def test_create_root_context(self): + mock_manager = mock_context_manager.MockContextManager() + ctx_to_check = mock_manager.create_root_context( + constants.RUNTIME_VERSION_1_1) + inject_count = ctx_to_check.collect_functions('inject') + with_original_count = ctx_to_check.collect_functions('withOriginal') + + self.assertEqual(2, len(inject_count[0])) + self.assertEqual(1, len(with_original_count[0])) + + +class TestMockYaqlFunctions(test_case.DslTestCase): + + def setUp(self): + super(TestMockYaqlFunctions, self).setUp() + self.runner = MockRunner(om.Object('TestMocks'), + self.package_loader, self._functions) + + def test_inject_method_with_str(self): + self.runner.testInjectMethodWithString() + self.assertEqual(['This is mock1'], self.traces) + + def test_inject_object_with_str(self): + self.runner.testInjectObjectWithString() + self.assertEqual(['This is mock1'], self.traces) + + def test_inject_method_with_yaql_expr(self): + self.runner.testInjectMethodWithYaqlExpr() + self.assertEqual(['method1', 'I am mock'], self.traces) + + def test_inject_object_with_yaql_expr(self): + self.runner.testInjectObjectWithYaqlExpr() + self.assertEqual(['method1', 'I am mock'], self.traces) + + def test_with_original(self): + self.runner.testWithoriginal() + self.assertEqual(['DEFAULT'], self.traces) diff --git a/releasenotes/notes/enable-mocks-a156e7cc1b1d5066.yaml b/releasenotes/notes/enable-mocks-a156e7cc1b1d5066.yaml new file mode 100644 index 00000000..9ff9de24 --- /dev/null +++ b/releasenotes/notes/enable-mocks-a156e7cc1b1d5066.yaml @@ -0,0 +1,24 @@ +--- +features: + - Enable mocks in MuranoPL tests cases. + Those test cases are run with murano-test-tunner. + To enable mocks use the one of the *inject* YAQL functions + * `def inject(target, target_method, mock_object, mock_name)` + * `def inject(target, target_method, yaql_expr)` + + Description + ----------- + + * *target* MuranoPL class name (namespaces can be used or full class name + in quotes) or MuranoPL object + + * *target_method* Method name to mock in target + + * *mock_object* Object, where mock definition is contained + + * *mock_name* Name of method, where mock definition is contained + + * *yaql_expr* YAQL expression, parameters are allowed + For more information, please follow the corresponding article in the + Murano developing applications guide. +