Add tests architecture

This commit is contained in:
lvdongbing 2016-01-07 03:31:45 -05:00
parent 5ff6ab5518
commit 1056b8b6d7
13 changed files with 398 additions and 14 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@
.DS_Store .DS_Store
.coverage .coverage
.tox .tox
.testrepository
AUTHORS AUTHORS
ChangeLog ChangeLog
bilean.egg-info/ bilean.egg-info/

6
.testr.conf Normal file
View File

@ -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

View File

@ -55,7 +55,7 @@ class JsonPayloadSerializer(oslo_messaging.NoOpSerializer):
def setup(url=None, optional=False): def setup(url=None, optional=False):
"""Initialise the oslo_messaging layer.""" """Initialise the oslo_messaging layer."""
global TRANSPORT, TRANSPORTS, NOTIFIER global TRANSPORT, NOTIFIER
if url and url.startswith("fake://"): if url and url.startswith("fake://"):
# NOTE(sileht): oslo_messaging fake driver uses time.sleep # NOTE(sileht): oslo_messaging fake driver uses time.sleep
@ -78,7 +78,6 @@ def setup(url=None, optional=False):
if not NOTIFIER and TRANSPORT: if not NOTIFIER and TRANSPORT:
serializer = RequestContextSerializer(JsonPayloadSerializer()) serializer = RequestContextSerializer(JsonPayloadSerializer())
NOTIFIER = oslo_messaging.Notifier(TRANSPORT, serializer=serializer) NOTIFIER = oslo_messaging.Notifier(TRANSPORT, serializer=serializer)
TRANSPORTS[url] = TRANSPORT
def cleanup(): def cleanup():

24
bilean/tests/__init__.py Normal file
View File

@ -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

View File

View File

@ -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

View File

@ -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

View File

@ -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
})

View File

View File

@ -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)

View File

@ -1,4 +1,4 @@
To generate the sample senlin.conf file, run the following To generate the sample bilean.conf file, run the following
command from the top level of the senlin directory: command from the top level of the bilean directory:
tox -egenconfig tox -egenconfig

View File

@ -2,16 +2,11 @@
# of appearance. Changing the order has an impact on the overall integration # of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later. # 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 apscheduler>=3.0.1
sqlalchemy-migrate>=0.9.6 cryptography>=1.0 # Apache-2.0
stevedore>=1.5.0 eventlet>=0.17.4
six>=1.9.0 jsonpath-rw-ext>=0.1.9
keystonemiddleware>=4.0.0
oslo.config>=2.7.0 # Apache-2.0 oslo.config>=2.7.0 # Apache-2.0
oslo.context>=0.2.0 # Apache-2.0 oslo.context>=0.2.0 # Apache-2.0
oslo.db>=4.1.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.serialization>=1.10.0 # Apache-2.0
oslo.service>=1.0.0 # Apache-2.0 oslo.service>=1.0.0 # Apache-2.0
oslo.utils!=3.1.0,>=2.8.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

View File

@ -1,6 +1,6 @@
[tox] [tox]
minversion = 1.6 minversion = 1.6
envlist = pep8 envlist = py34,py27,pep8
skipsdist = True skipsdist = True
[testenv] [testenv]