From 06856caecfe41c4bd4d921f0aac6e500115151d5 Mon Sep 17 00:00:00 2001 From: Serg Melikyan Date: Tue, 25 Mar 2014 16:49:28 +0400 Subject: [PATCH] Add MuranoPL Testing Framework Implements simple plugin for nosetests and gives ability to write tests for MuranoPL functions and features on MuranoPL itself. Change-Id: I899d5d89f391167ff34fa35777e85125bad431bf --- muranoapi/dsl/class_loader.py | 3 +- .../ExampleTestCase.yaml | 17 ++ .../io.murano.language.tests/manifest.yaml | 9 + muranoapi/tests/nose_plugins/__init__.py | 0 muranoapi/tests/nose_plugins/munit.py | 158 ++++++++++++++++++ setup.cfg | 2 + tox.ini | 1 + 7 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 muranoapi/tests/language/io.murano.language.tests/ExampleTestCase.yaml create mode 100644 muranoapi/tests/language/io.murano.language.tests/manifest.yaml create mode 100644 muranoapi/tests/nose_plugins/__init__.py create mode 100644 muranoapi/tests/nose_plugins/munit.py diff --git a/muranoapi/dsl/class_loader.py b/muranoapi/dsl/class_loader.py index a2c59572..f35826d2 100644 --- a/muranoapi/dsl/class_loader.py +++ b/muranoapi/dsl/class_loader.py @@ -108,5 +108,6 @@ class MuranoClassLoader(object): for item in dir(cls): method = getattr(cls, item) - if callable(method) and not item.startswith('_'): + if ((inspect.isfunction(method) or inspect.ismethod(method)) and + not item.startswith('_')): m_class.add_method(item, method) diff --git a/muranoapi/tests/language/io.murano.language.tests/ExampleTestCase.yaml b/muranoapi/tests/language/io.murano.language.tests/ExampleTestCase.yaml new file mode 100644 index 00000000..5577c25d --- /dev/null +++ b/muranoapi/tests/language/io.murano.language.tests/ExampleTestCase.yaml @@ -0,0 +1,17 @@ +Namespaces: + =: io.murano.language.tests + +Name: ExampleTestCase + +Extends: TestCase + +Workflow: + testSum: + Body: + - $sum: 1 + 2 + - $.assertEqual(3, $sum) + + testUpper: + Body: + - $uppedValue: toUpper('test') + - $.assertEqual('TEST', $uppedValue) diff --git a/muranoapi/tests/language/io.murano.language.tests/manifest.yaml b/muranoapi/tests/language/io.murano.language.tests/manifest.yaml new file mode 100644 index 00000000..59995d78 --- /dev/null +++ b/muranoapi/tests/language/io.murano.language.tests/manifest.yaml @@ -0,0 +1,9 @@ +Format: 1.0 +Type: Library +FullName: io.murano.language.tests +Name: Example Test Cases +Description: Simple test case intended to be used as example +Author: Example +Tags: [test, example] +Classes: + io.murano.language.tests.ExampleTestCase: ExampleTestCase.yaml diff --git a/muranoapi/tests/nose_plugins/__init__.py b/muranoapi/tests/nose_plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/muranoapi/tests/nose_plugins/munit.py b/muranoapi/tests/nose_plugins/munit.py new file mode 100644 index 00000000..9f7763d3 --- /dev/null +++ b/muranoapi/tests/nose_plugins/munit.py @@ -0,0 +1,158 @@ +# 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 json +import logging +import os +import yaml + +import nose +import unittest2 as unittest + +from muranoapi.common import uuidutils +from muranoapi.dsl import executor +from muranoapi.dsl import murano_class +from muranoapi.dsl import namespace_resolver +from muranoapi.engine import simple_cloader +from muranoapi.engine.system import system_objects + + +LOG = logging.getLogger('nose.plugins.munit') + + +@murano_class.classname('io.murano.language.tests.TestCase') +class BaseTestCase(object): + pass + + +class ManifestClassLoader(simple_cloader.SimpleClassLoader): + def __init__(self, base_loader, base_path): + self._base_loader = base_loader + super(ManifestClassLoader, self).__init__(base_path) + + def load_definition(self, name): + manifest_file = os.path.join(self._base_path, 'manifest.yaml') + if not os.path.exists(manifest_file): + return None + + with open(manifest_file) as stream: + manifest = yaml.load(stream) + pkg_classes = manifest.get('Classes', {}) + + if name in pkg_classes: + class_file = os.path.join(self._base_path, pkg_classes[name]) + with open(class_file) as stream: + return yaml.load(stream) + else: + return self._base_loader.load_definition(name) + + +class MUnitTestCase(unittest.TestCase): + def __init__(self, loader, test_case, case_name): + self._executor = executor.MuranoDslExecutor(loader) + self._test_case = self._executor.load(test_case) + self._case_name = case_name + self.register_asserts() + super(MUnitTestCase, self).__init__(methodName='runTest') + + def register_asserts(self): + for item in dir(self): + method = getattr(self, item) + if ((inspect.ismethod(method) or inspect.isfunction(method)) + and item.startswith('assert')): + self._test_case.type.add_method(item, method) + + def runTest(self): + self._test_case.type.invoke(self._case_name, self._executor, + self._test_case, {}) + + def __repr__(self): + return "{0} ({1})".format(self._case_name, self._test_case.type.name) + + __str__ = __repr__ + + +class MuranoPLUnitTestPlugin(nose.plugins.Plugin): + name = 'munit' + + def options(self, parser, env=os.environ): + parser.add_option('--metadata-dir') + super(MuranoPLUnitTestPlugin, self).options(parser, env=env) + + def configure(self, options, conf): + self._metadata_dir = options.metadata_dir + super(MuranoPLUnitTestPlugin, self).configure(options, conf) + + def loadTestsFromFile(self, filename): + with open(filename) as stream: + manifest = yaml.load(stream) + pkg_directory = os.path.dirname(filename) + + base_cloader = simple_cloader.SimpleClassLoader(self._metadata_dir) + cloader = ManifestClassLoader(base_cloader, pkg_directory) + system_objects.register(cloader, pkg_directory) + cloader.import_class(BaseTestCase) + + package_classes = manifest.get('Classes', {}) + for class_name, class_file in package_classes.iteritems(): + class_file = os.path.join(pkg_directory, class_file) + with open(class_file) as stream: + class_definition = yaml.load(stream) + + if not self._is_test_suite(class_definition): + yield None + + test_object = self._get_test_object(class_name, class_file) + + test_cases = class_definition.get('Workflow', {}) + for case_name, _ in test_cases.iteritems(): + yield MUnitTestCase(cloader, test_object, case_name) + + yield None + + @staticmethod + def _get_test_object(class_name, class_file): + class_file = os.path.abspath(class_file) + + extension_length = os.path.splitext(class_file) + object_file = class_file[:-len(extension_length)] + 'json' + + if os.path.exists(object_file): + with open(object_file) as stream: + return json.load(stream) + else: + return { + "Objects": { + "?": { + "id": uuidutils.generate_uuid(), + "type": class_name + } + } + } + + @staticmethod + def _is_test_suite(class_definition): + namespaces = class_definition.get('Namespaces', {}) + extends = class_definition.get('Extends') + + ns_resolver = namespace_resolver.NamespaceResolver(namespaces) + parent_name = ns_resolver.resolve_name(extends) + + return parent_name == 'io.murano.language.tests.TestCase' + + def wantFile(self, file): + if file.endswith('manifest.yaml'): + return True diff --git a/setup.cfg b/setup.cfg index 9fcc8a81..8549878d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,6 +45,8 @@ console_scripts = murano-api = muranoapi.cmd.api:main murano-engine = muranoapi.cmd.engine:main murano-manage = muranoapi.cmd.manage:main +nose.plugins.0.10 = + munit = muranoapi.tests.nose_plugins.munit:MuranoPLUnitTestPlugin [build_sphinx] all_files = 1 diff --git a/tox.ini b/tox.ini index 2cc518b2..3756b6da 100644 --- a/tox.ini +++ b/tox.ini @@ -15,6 +15,7 @@ setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = nosetests -w muranoapi/tests + nosetests --with-munit -w muranoapi/tests/language --metadata-dir=./meta [testenv:pep8] commands =