~james-page/charms/trusty/midonet-agent/trunk

« back to all changes in this revision

Viewing changes to unit_tests/midonet_helpers/test.py

  • Committer: Antoni Segura Puimedon
  • Date: 2015-08-03 13:47:25 UTC
  • Revision ID: toni@midokura.com-20150803134725-uldzl0ghv5oe8la8
Initial midonet-agent commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# vim: tabstop=4 shiftwidth=4 softtabstop=4 filetype=python
 
3
#
 
4
# Copyright (c) 2015 Canonical Ltd.
 
5
# Copyright (c) 2015 Midokura Sarl.
 
6
#
 
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
 
10
#
 
11
# http://www.apache.org/licenses/LICENSE-2.0
 
12
#
 
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
 
17
# under the License.
 
18
 
 
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).
 
21
import contextlib
 
22
import io
 
23
import logging
 
24
import os
 
25
import unittest
 
26
 
 
27
import mock
 
28
import yaml
 
29
 
 
30
 
 
31
def load_config():
 
32
    """Get config options
 
33
 
 
34
    Walk backwords from __file__ looking for config.yaml, load and return the
 
35
    'options' section'
 
36
    """
 
37
    config = None
 
38
    directory = os.path.dirname(__file__)
 
39
    while config is None:
 
40
        if os.path.isfile(os.path.join(directory, 'config.yaml')):
 
41
            config = os.path.join(directory, 'config.yaml')
 
42
            break
 
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
 
47
 
 
48
    if not config:
 
49
        logging.info('Could not find config.yaml in any parent directory '
 
50
                     'of %s. ' % __file__)
 
51
        return
 
52
 
 
53
    return yaml.safe_load(open(config).read())['options']
 
54
 
 
55
 
 
56
class ServiceConfig(dict):
 
57
    def __init__(self):
 
58
        self.implicit_save = False
 
59
        dict.__init__(self)
 
60
 
 
61
 
 
62
def get_default_config():
 
63
    """Get the default charm config
 
64
 
 
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.
 
67
    """
 
68
    default_config = ServiceConfig()
 
69
    config = load_config()
 
70
    if config is not None:
 
71
        for k, v in config.items():
 
72
            if 'default' in v:
 
73
                default_config[k] = v['default']
 
74
            else:
 
75
                default_config[k] = None
 
76
    return default_config
 
77
 
 
78
 
 
79
class CharmTestCase(unittest.TestCase):
 
80
 
 
81
    def setUp(self, obj, patches):
 
82
        super(CharmTestCase, self).setUp()
 
83
        self.patches = patches
 
84
        self.obj = obj
 
85
        self.test_config = TestConfig()
 
86
        self.test_relation = TestRelation()
 
87
        self.patch_all()
 
88
 
 
89
    def patch(self, method):
 
90
        _m = mock.patch.object(self.obj, method)
 
91
        _mock = _m.start()
 
92
        self.addCleanup(_m.stop)
 
93
        return _mock
 
94
 
 
95
    def patch_all(self):
 
96
        for method in self.patches:
 
97
            setattr(self, method, self.patch(method))
 
98
 
 
99
 
 
100
class TestConfig(object):
 
101
 
 
102
    def __init__(self):
 
103
        self.config = get_default_config()
 
104
 
 
105
    def get(self, attr=None):
 
106
        if not attr:
 
107
            return self.get_all()
 
108
        try:
 
109
            return self.config[attr]
 
110
        except KeyError:
 
111
            return None
 
112
 
 
113
    def get_all(self):
 
114
        return self.config
 
115
 
 
116
    def set(self, attr, value):
 
117
        """Allows to set even non-original values to match hookenv.Config."""
 
118
        self.config[attr] = value
 
119
 
 
120
 
 
121
class TestRelation(object):
 
122
 
 
123
    def __init__(self, relation_data={}):
 
124
        self.relation_data = relation_data
 
125
 
 
126
    def set(self, relation_data):
 
127
        self.relation_data = relation_data
 
128
 
 
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]
 
134
        return None
 
135
 
 
136
 
 
137
class FakeRelation(object):
 
138
    """A fake relation class.
 
139
 
 
140
    Lets tests specify simple relation data
 
141
    for a default relation + unit (foo:0, foo/0, set in setUp()), eg:
 
142
 
 
143
        rel = {
 
144
            'private-address': 'foo',
 
145
            'password': 'passwd',
 
146
        }
 
147
        relation = FakeRelation(rel)
 
148
        self.relation_get.side_effect = relation.get
 
149
        passwd = self.relation_get('password')
 
150
 
 
151
    or more complex relations meant to be addressed by explicit relation id
 
152
    + unit id combos:
 
153
 
 
154
        rel = {
 
155
            'mysql:0': {
 
156
                'mysql/0': {
 
157
                    'private-address': 'foo',
 
158
                    'password': 'passwd',
 
159
                }
 
160
            }
 
161
        }
 
162
        relation = FakeRelation(rel)
 
163
        self.relation_get.side_affect = relation.get
 
164
        passwd = self.relation_get('password', rid='mysql:0', unit='mysql/0')
 
165
    """
 
166
    def __init__(self, relation_data):
 
167
        self.relation_data = relation_data
 
168
 
 
169
    def get(self, attr=None, unit=None, rid=None):
 
170
        if not rid or rid == 'foo:0':
 
171
            if attr is None:
 
172
                return self.relation_data
 
173
            elif attr in self.relation_data:
 
174
                return self.relation_data[attr]
 
175
            return None
 
176
        else:
 
177
            if rid not in self.relation_data:
 
178
                return None
 
179
            try:
 
180
                relation = self.relation_data[rid][unit]
 
181
            except KeyError:
 
182
                return None
 
183
            if attr is None:
 
184
                return relation
 
185
            elif attr in relation:
 
186
                return relation[attr]
 
187
            return None
 
188
 
 
189
    def relation_ids(self, relation=None):
 
190
        if relation is None:
 
191
            return self.relation_data.keys()
 
192
        rids = []
 
193
        for key in self.relation_data.keys():
 
194
            if key.startswith(relation) and key[len(relation)] == ':':
 
195
                rids.append(key)
 
196
        return rids
 
197
 
 
198
    def related_units(self, relid=None):
 
199
        try:
 
200
            return self.relation_data[relid].keys()
 
201
        except KeyError:
 
202
            return []
 
203
 
 
204
    def relation_units(self, relation_id):
 
205
        if relation_id not in self.relation_data:
 
206
            return None
 
207
        return self.relation_data[relation_id].keys()
 
208
 
 
209
 
 
210
@contextlib.contextmanager
 
211
def patch_open():
 
212
    """Mock open()
 
213
 
 
214
    Mocking allows to mock both open() itself and the file that is being
 
215
    yielded.
 
216
 
 
217
    Yields the mock for "open" and "file", respectively.
 
218
    """
 
219
    mock_open = mock.MagicMock(spec=open)
 
220
    mock_file = mock.MagicMock(spec=io.FileIO)
 
221
 
 
222
    @contextlib.contextmanager
 
223
    def stub_open(*args, **kwargs):
 
224
        mock_open(*args, **kwargs)
 
225
        yield mock_file
 
226
 
 
227
    with mock.patch('__builtin__.open', stub_open):
 
228
        yield mock_open, mock_file