2
# vim: tabstop=4 shiftwidth=4 softtabstop=4 filetype=python
4
# Copyright (c) 2015 Canonical Ltd.
5
# Copyright (c) 2015 Midokura Sarl.
7
# Licensed under the Apache License, Version 2.0 (the "License"); you may
8
# not use this file except in compliance with the License. You may obtain
9
# a copy of the License at
11
# http://www.apache.org/licenses/LICENSE-2.0
13
# Unless required by applicable law or agreed to in writing, software
14
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
# License for the specific language governing permissions and limitations
19
# Taken from the neutron-api charm. It should be included in charm-helpers
20
# in the future. (It was under unit_tests/test_utils.py).
34
Walk backwords from __file__ looking for config.yaml, load and return the
38
directory = os.path.dirname(__file__)
40
if os.path.isfile(os.path.join(directory, 'config.yaml')):
41
config = os.path.join(directory, 'config.yaml')
43
parent_dir = os.path.dirname(directory)
44
if parent_dir == directory:
45
break # We reached the root of the fs
46
directory = parent_dir
49
logging.info('Could not find config.yaml in any parent directory '
53
return yaml.safe_load(open(config).read())['options']
56
class ServiceConfig(dict):
58
self.implicit_save = False
62
def get_default_config():
63
"""Get the default charm config
65
Load default charm config from config.yaml return as a dict.
66
If no default is set in config.yaml, its value is None.
68
default_config = ServiceConfig()
69
config = load_config()
70
if config is not None:
71
for k, v in config.items():
73
default_config[k] = v['default']
75
default_config[k] = None
79
class CharmTestCase(unittest.TestCase):
81
def setUp(self, obj, patches):
82
super(CharmTestCase, self).setUp()
83
self.patches = patches
85
self.test_config = TestConfig()
86
self.test_relation = TestRelation()
89
def patch(self, method):
90
_m = mock.patch.object(self.obj, method)
92
self.addCleanup(_m.stop)
96
for method in self.patches:
97
setattr(self, method, self.patch(method))
100
class TestConfig(object):
103
self.config = get_default_config()
105
def get(self, attr=None):
107
return self.get_all()
109
return self.config[attr]
116
def set(self, attr, value):
117
"""Allows to set even non-original values to match hookenv.Config."""
118
self.config[attr] = value
121
class TestRelation(object):
123
def __init__(self, relation_data={}):
124
self.relation_data = relation_data
126
def set(self, relation_data):
127
self.relation_data = relation_data
129
def get(self, attribute=None, unit=None, rid=None):
130
if attribute is None:
131
return self.relation_data
132
elif attribute in self.relation_data:
133
return self.relation_data[attribute]
137
class FakeRelation(object):
138
"""A fake relation class.
140
Lets tests specify simple relation data
141
for a default relation + unit (foo:0, foo/0, set in setUp()), eg:
144
'private-address': 'foo',
145
'password': 'passwd',
147
relation = FakeRelation(rel)
148
self.relation_get.side_effect = relation.get
149
passwd = self.relation_get('password')
151
or more complex relations meant to be addressed by explicit relation id
157
'private-address': 'foo',
158
'password': 'passwd',
162
relation = FakeRelation(rel)
163
self.relation_get.side_affect = relation.get
164
passwd = self.relation_get('password', rid='mysql:0', unit='mysql/0')
166
def __init__(self, relation_data):
167
self.relation_data = relation_data
169
def get(self, attr=None, unit=None, rid=None):
170
if not rid or rid == 'foo:0':
172
return self.relation_data
173
elif attr in self.relation_data:
174
return self.relation_data[attr]
177
if rid not in self.relation_data:
180
relation = self.relation_data[rid][unit]
185
elif attr in relation:
186
return relation[attr]
189
def relation_ids(self, relation=None):
191
return self.relation_data.keys()
193
for key in self.relation_data.keys():
194
if key.startswith(relation) and key[len(relation)] == ':':
198
def related_units(self, relid=None):
200
return self.relation_data[relid].keys()
204
def relation_units(self, relation_id):
205
if relation_id not in self.relation_data:
207
return self.relation_data[relation_id].keys()
210
@contextlib.contextmanager
214
Mocking allows to mock both open() itself and the file that is being
217
Yields the mock for "open" and "file", respectively.
219
mock_open = mock.MagicMock(spec=open)
220
mock_file = mock.MagicMock(spec=io.FileIO)
222
@contextlib.contextmanager
223
def stub_open(*args, **kwargs):
224
mock_open(*args, **kwargs)
227
with mock.patch('__builtin__.open', stub_open):
228
yield mock_open, mock_file