From 1056b8b6d7c451fb9420c702b24db1f1a09a70ad Mon Sep 17 00:00:00 2001 From: lvdongbing Date: Thu, 7 Jan 2016 03:31:45 -0500 Subject: [PATCH] Add tests architecture --- .gitignore | 1 + .testr.conf | 6 ++ bilean/common/messaging.py | 3 +- bilean/tests/__init__.py | 24 ++++++ bilean/tests/common/__init__.py | 0 bilean/tests/common/base.py | 78 +++++++++++++++++++ bilean/tests/common/fakes.py | 85 +++++++++++++++++++++ bilean/tests/common/utils.py | 82 ++++++++++++++++++++ bilean/tests/rules/__init__.py | 0 bilean/tests/rules/test_rule_base.py | 108 +++++++++++++++++++++++++++ etc/bilean/README-bilean.conf.txt | 4 +- requirements.txt | 19 ++--- tox.ini | 2 +- 13 files changed, 398 insertions(+), 14 deletions(-) create mode 100644 .testr.conf create mode 100644 bilean/tests/__init__.py create mode 100644 bilean/tests/common/__init__.py create mode 100644 bilean/tests/common/base.py create mode 100644 bilean/tests/common/fakes.py create mode 100644 bilean/tests/common/utils.py create mode 100644 bilean/tests/rules/__init__.py create mode 100644 bilean/tests/rules/test_rule_base.py diff --git a/.gitignore b/.gitignore index b4d649f..ef6c3fd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .DS_Store .coverage .tox +.testrepository AUTHORS ChangeLog bilean.egg-info/ diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..4b5a0a1 --- /dev/null +++ b/.testr.conf @@ -0,0 +1,6 @@ +[DEFAULT] +test_command= + PYTHON=$(echo ${PYTHON:-python} | sed 's/--source bilean//g') + ${PYTHON} -m subunit.run discover ${OS_TEST_PATH:-./bilean/tests} -t . $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/bilean/common/messaging.py b/bilean/common/messaging.py index be73823..c59a8fd 100644 --- a/bilean/common/messaging.py +++ b/bilean/common/messaging.py @@ -55,7 +55,7 @@ class JsonPayloadSerializer(oslo_messaging.NoOpSerializer): def setup(url=None, optional=False): """Initialise the oslo_messaging layer.""" - global TRANSPORT, TRANSPORTS, NOTIFIER + global TRANSPORT, NOTIFIER if url and url.startswith("fake://"): # NOTE(sileht): oslo_messaging fake driver uses time.sleep @@ -78,7 +78,6 @@ def setup(url=None, optional=False): if not NOTIFIER and TRANSPORT: serializer = RequestContextSerializer(JsonPayloadSerializer()) NOTIFIER = oslo_messaging.Notifier(TRANSPORT, serializer=serializer) - TRANSPORTS[url] = TRANSPORT def cleanup(): diff --git a/bilean/tests/__init__.py b/bilean/tests/__init__.py new file mode 100644 index 0000000..fed5dba --- /dev/null +++ b/bilean/tests/__init__.py @@ -0,0 +1,24 @@ +# 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 oslo_i18n + + +def fake_translate_msgid(msgid, domain, desired_locale=None): + return msgid + +oslo_i18n.enable_lazy() + +# To ensure messages don't really get translated while running tests. +# As there are lots of places where matching is expected when comparing +# exception message(translated) with raw message. +oslo_i18n._translate_msgid = fake_translate_msgid diff --git a/bilean/tests/common/__init__.py b/bilean/tests/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bilean/tests/common/base.py b/bilean/tests/common/base.py new file mode 100644 index 0000000..da8183a --- /dev/null +++ b/bilean/tests/common/base.py @@ -0,0 +1,78 @@ +# 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 logging +import os + +import fixtures +from oslotest import mockpatch +import testscenarios +import testtools + +from bilean.common import messaging +from bilean.tests.common import utils + + +TEST_DEFAULT_LOGLEVELS = {'migrate': logging.WARN, + 'sqlalchemy': logging.WARN} +_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s" +_TRUE_VALUES = ('True', 'true', '1', 'yes') + + +class FakeLogMixin(object): + def setup_logging(self): + # Assign default logs to self.LOG so we can still + # assert on bilean logs. + default_level = logging.INFO + if os.environ.get('OS_DEBUG') in _TRUE_VALUES: + default_level = logging.DEBUG + + self.LOG = self.useFixture( + fixtures.FakeLogger(level=default_level, format=_LOG_FORMAT)) + base_list = set([nlog.split('.')[0] + for nlog in logging.Logger.manager.loggerDict]) + for base in base_list: + if base in TEST_DEFAULT_LOGLEVELS: + self.useFixture(fixtures.FakeLogger( + level=TEST_DEFAULT_LOGLEVELS[base], + name=base, format=_LOG_FORMAT)) + elif base != 'bilean': + self.useFixture(fixtures.FakeLogger( + name=base, format=_LOG_FORMAT)) + + +class BileanTestCase(testscenarios.WithScenarios, + testtools.TestCase, FakeLogMixin): + + def setUp(self): + super(BileanTestCase, self).setUp() + self.setup_logging() + self.useFixture(fixtures.MonkeyPatch( + 'bilean.common.exception._FATAL_EXCEPTION_FORMAT_ERRORS', + True)) + + messaging.setup("fake://", optional=True) + self.addCleanup(messaging.cleanup) + + utils.setup_dummy_db() + self.addCleanup(utils.reset_dummy_db) + + def patchobject(self, obj, attr, **kwargs): + mockfixture = self.useFixture(mockpatch.PatchObject(obj, attr, + **kwargs)) + return mockfixture.mock + + # NOTE(pshchelo): this overrides the testtools.TestCase.patch method + # that does simple monkey-patching in favor of mock's patching + def patch(self, target, **kwargs): + mockfixture = self.useFixture(mockpatch.Patch(target, **kwargs)) + return mockfixture.mock diff --git a/bilean/tests/common/fakes.py b/bilean/tests/common/fakes.py new file mode 100644 index 0000000..1511d52 --- /dev/null +++ b/bilean/tests/common/fakes.py @@ -0,0 +1,85 @@ +# 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. + +""" +A fake server that "responds" to API methods with pre-canned responses. +""" + + +class FakeClient(object): + def assert_called(self, method, url, body=None, pos=-1): + # Assert than an API method was just called. + expected = (method, url) + called = self.client.callstack[pos][0:2] + + assert self.client.callstack, \ + "Expected %s %s but no calls were made." % expected + + assert expected == called, 'Expected %s %s; got %s %s' % \ + (expected + called) + + if body is not None: + assert self.client.callstack[pos][2] == body + + def assert_called_anytime(self, method, url, body=None): + # Assert than an API method was called anytime in the test. + expected = (method, url) + + assert self.client.callstack, \ + "Expected %s %s but no calls were made." % expected + + found = False + for entry in self.client.callstack: + if expected == entry[0:2]: + found = True + break + + assert found, 'Expected %s %s; got %s' % \ + (expected, self.client.callstack) + if body is not None: + try: + assert entry[2] == body + except AssertionError: + print(entry[2]) + print("!=") + print(body) + raise + + self.client.callstack = [] + + def clear_callstack(self): + self.client.callstack = [] + + def authenticate(self): + pass + + +class FakeKeystoneClient(object): + def __init__(self, username='test_user', password='apassword', + user_id='1234', access='4567', secret='8901', + credential_id='abcdxyz', auth_token='abcd1234', + only_services=None): + self.username = username + self.password = password + self.user_id = user_id + self.access = access + self.secret = secret + self.credential_id = credential_id + self.only_services = only_services + + class FakeCred(object): + id = self.credential_id + access = self.access + secret = self.secret + self.creds = FakeCred() + + self.auth_token = auth_token diff --git a/bilean/tests/common/utils.py b/bilean/tests/common/utils.py new file mode 100644 index 0000000..77be1ec --- /dev/null +++ b/bilean/tests/common/utils.py @@ -0,0 +1,82 @@ +# 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 random +import string +import uuid + +from oslo_config import cfg +from oslo_db import options +import sqlalchemy + +from bilean.common import context +from bilean.db import api as db_api + +get_engine = db_api.get_engine + + +class UUIDStub(object): + def __init__(self, value): + self.value = value + + def __enter__(self): + self.uuid4 = uuid.uuid4 + uuid_stub = lambda: self.value + uuid.uuid4 = uuid_stub + + def __exit__(self, *exc_info): + uuid.uuid4 = self.uuid4 + + +def random_name(): + return ''.join(random.choice(string.ascii_uppercase) + for x in range(10)) + + +def setup_dummy_db(): + options.cfg.set_defaults(options.database_opts, sqlite_synchronous=False) + options.set_defaults(cfg.CONF, + connection="sqlite://", + sqlite_db='bilean.db') + engine = get_engine() + db_api.db_sync(engine) + engine.connect() + + +def reset_dummy_db(): + engine = get_engine() + meta = sqlalchemy.MetaData() + meta.reflect(bind=engine) + + for table in reversed(meta.sorted_tables): + if table.name == 'migrate_version': + continue + engine.execute(table.delete()) + + +def dummy_context(user='test_username', project='test_project_id', + password='password', roles=None, user_id='test_user_id', + trust_id=None, region_name=None, domain=None): + roles = roles or [] + return context.RequestContext.from_dict({ + 'project': project, + 'user': user_id, + 'user_name': user, + 'password': password, + 'roles': roles, + 'is_admin': False, + 'auth_url': 'http://server.test:5000/v2.0', + 'auth_token': 'abcd1234', + 'trust_id': trust_id, + 'region_name': region_name, + 'domain': domain + }) diff --git a/bilean/tests/rules/__init__.py b/bilean/tests/rules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bilean/tests/rules/test_rule_base.py b/bilean/tests/rules/test_rule_base.py new file mode 100644 index 0000000..deedc34 --- /dev/null +++ b/bilean/tests/rules/test_rule_base.py @@ -0,0 +1,108 @@ +# 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 mock +from oslo_utils import encodeutils +import six + +from bilean.common import exception +from bilean.common.i18n import _ +from bilean.common import schema +from bilean.db import api as db_api +from bilean.engine import environment +from bilean.rules import base as rule_base +from bilean.tests.common import base +from bilean.tests.common import utils + + +class DummyRule(rule_base.Rule): + VERSION = '1.0' + + properties_schema = { + 'key1': schema.String( + 'First key', + default='value1' + ), + 'key2': schema.Integer( + 'Second key', + required=True, + ), + } + + def __init__(self, name, spec, **kwargs): + super(DummyRule, self).__init__(name, spec, **kwargs) + + +class TestRuleBase(base.BileanTestCase): + + def setUp(self): + super(TestRuleBase, self).setUp() + + self.context = utils.dummy_context() + environment.global_env().register_rule('bilean.rule.dummy', DummyRule) + self.spec = { + 'type': 'bilean.rule.dummy', + 'version': '1.0', + 'properties': { + 'key1': 'value1', + 'key2': 2, + } + } + + def _create_rule(self, rule_name, rule_id=None): + rule = rule_base.Rule(rule_name, self.spec) + if rule_id: + rule.id = rule_id + + return rule + + def _create_db_rule(self, **kwargs): + values = { + 'name': 'test-rule', + 'type': 'bilean.rule.dummy-1.0', + 'spec': self.spec, + 'metadata': {} + } + + values.update(kwargs) + return db_api.rule_create(self.context, values) + + def test_init(self): + name = utils.random_name() + rule = self._create_rule(name) + + self.assertIsNone(rule.id) + self.assertEqual(name, rule.name) + self.assertEqual('bilean.rule.dummy-1.0', rule.type) + self.assertEqual(self.spec, rule.spec) + self.assertEqual({}, rule.metadata) + self.assertIsNone(rule.created_at) + self.assertIsNone(rule.updated_at) + self.assertIsNone(rule.deleted_at) + + spec_data = rule.spec_data + self.assertEqual('bilean.rule.dummy', spec_data['type']) + self.assertEqual('1.0', spec_data['version']) + self.assertEqual({'key1': 'value1', 'key2': 2}, + spec_data['properties']) + self.assertEqual({'key1': 'value1', 'key2': 2}, rule.properties) + + def test_rule_type_not_found(self): + bad_spec = { + 'type': 'bad-type', + 'version': '1.0', + 'properties': '', + } + + self.assertRaises(exception.RuleTypeNotFound, + rule_base.Rule, + 'test-rule', bad_spec) diff --git a/etc/bilean/README-bilean.conf.txt b/etc/bilean/README-bilean.conf.txt index 44bb0d6..af0be9a 100644 --- a/etc/bilean/README-bilean.conf.txt +++ b/etc/bilean/README-bilean.conf.txt @@ -1,4 +1,4 @@ -To generate the sample senlin.conf file, run the following -command from the top level of the senlin directory: +To generate the sample bilean.conf file, run the following +command from the top level of the bilean directory: tox -egenconfig diff --git a/requirements.txt b/requirements.txt index 2a1ef21..cf95028 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,16 +2,11 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.6 -SQLAlchemy<1.1.0,>=0.9.9 -eventlet>=0.17.4 -keystonemiddleware>=4.0.0 -jsonpath-rw-ext>=0.1.9 -WebOb>=1.2.3 apscheduler>=3.0.1 -sqlalchemy-migrate>=0.9.6 -stevedore>=1.5.0 -six>=1.9.0 +cryptography>=1.0 # Apache-2.0 +eventlet>=0.17.4 +jsonpath-rw-ext>=0.1.9 +keystonemiddleware>=4.0.0 oslo.config>=2.7.0 # Apache-2.0 oslo.context>=0.2.0 # Apache-2.0 oslo.db>=4.1.0 # Apache-2.0 @@ -23,3 +18,9 @@ oslo.policy>=0.5.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.service>=1.0.0 # Apache-2.0 oslo.utils!=3.1.0,>=2.8.0 # Apache-2.0 +pbr>=1.6 +six>=1.9.0 +SQLAlchemy<1.1.0,>=0.9.9 +sqlalchemy-migrate>=0.9.6 +stevedore>=1.5.0 +WebOb>=1.2.3 diff --git a/tox.ini b/tox.ini index 14f8fdc..f70b708 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = pep8 +envlist = py34,py27,pep8 skipsdist = True [testenv]