Merge lp:~jason-hobbs/charms/trusty/odl-controller/add-unit-tests into lp:~sdn-charmers/charms/trusty/odl-controller/trunk

Proposed by Jason Hobbs
Status: Needs review
Proposed branch: lp:~jason-hobbs/charms/trusty/odl-controller/add-unit-tests
Merge into: lp:~sdn-charmers/charms/trusty/odl-controller/trunk
Diff against target: 324 lines (+275/-18)
4 files modified
hooks/odl_controller_utils.py (+26/-18)
unit_tests/__init__.py (+3/-0)
unit_tests/test_odl_controller_utils.py (+125/-0)
unit_tests/test_utils.py (+121/-0)
To merge this branch: bzr merge lp:~jason-hobbs/charms/trusty/odl-controller/add-unit-tests
Reviewer Review Type Date Requested Status
SDN Charmers Pending
Review via email: mp+263431@code.launchpad.net

Commit message

Add unit tests for the maven settings template generator.

Description of the change

Just a start on unit tests for odl controller, along with a minor refactor on one of the methods being tested. I copied test_utils.py from the nova-compute charm.

To post a comment you must log in.

Unmerged revisions

9. By Jason Hobbs

Add some docstrings.

8. By Jason Hobbs

One more unit test for no_proxy.

7. By Jason Hobbs

Finish adding unit tests for odl_controller_utils.

6. By Jason Hobbs

Continue adding unit tests.

5. By Jason Hobbs

Add first unit test for ODL utils.

4. By Jason Hobbs

Use early return from mvn_proxy_ctx().

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'hooks/odl_controller_utils.py'
--- hooks/odl_controller_utils.py 2015-02-19 22:08:13 +0000
+++ hooks/odl_controller_utils.py 2015-07-02 21:50:28 +0000
@@ -9,29 +9,37 @@
9 ctx.update(mvn_proxy_ctx("https"))9 ctx.update(mvn_proxy_ctx("https"))
10 return ctx10 return ctx
1111
12
12def mvn_proxy_ctx(protocol):13def mvn_proxy_ctx(protocol):
13 ctx = {}14 ctx = {}
14 key = protocol + "_proxy"15 key = protocol + "_proxy"
15 if key in environ:16 if key not in environ:
16 url = urlparse.urlparse(environ[key])17 return ctx
17 hostname = url.hostname18
18 if hostname:19 url = urlparse.urlparse(environ[key])
19 ctx[key] = True20 hostname = url.hostname
20 ctx[protocol + "_proxy_host"] = hostname21
21 port = url.port22 if not hostname:
22 ctx[protocol + "_proxy_port"] = port if port else 8023 return ctx
23 username = url.username24
24 if username:25 ctx[key] = True
25 ctx[protocol + "_proxy_username"] = username26 ctx[protocol + "_proxy_host"] = hostname
26 ctx[protocol + "_proxy_password"] = url.password27 port = url.port
27 no_proxy = []28 ctx[protocol + "_proxy_port"] = port if port else 80
28 if "no_proxy" in environ:29 username = url.username
29 np = environ["no_proxy"]30 if username:
30 if np:31 ctx[protocol + "_proxy_username"] = username
31 no_proxy = np.split(",")32 ctx[protocol + "_proxy_password"] = url.password
32 ctx[protocol + "_noproxy"] = no_proxy33 no_proxy = []
34 if "no_proxy" in environ:
35 np = environ["no_proxy"]
36 if np:
37 no_proxy = np.split(",")
38 ctx[protocol + "_noproxy"] = no_proxy
39
33 return ctx40 return ctx
3441
42
35def write_mvn_config():43def write_mvn_config():
36 ctx = mvn_ctx()44 ctx = mvn_ctx()
37 render("settings.xml", "/home/opendaylight/.m2/settings.xml", ctx,45 render("settings.xml", "/home/opendaylight/.m2/settings.xml", ctx,
3846
=== added directory 'unit_tests'
=== added file 'unit_tests/__init__.py'
--- unit_tests/__init__.py 1970-01-01 00:00:00 +0000
+++ unit_tests/__init__.py 2015-07-02 21:50:28 +0000
@@ -0,0 +1,3 @@
1import sys
2
3sys.path.append('hooks/')
04
=== added file 'unit_tests/test_odl_controller_utils.py'
--- unit_tests/test_odl_controller_utils.py 1970-01-01 00:00:00 +0000
+++ unit_tests/test_odl_controller_utils.py 2015-07-02 21:50:28 +0000
@@ -0,0 +1,125 @@
1import odl_controller_utils as utils
2import xml.etree.ElementTree as ET
3
4from mock import patch
5
6from test_utils import (
7 CharmTestCase,
8 patch_open,
9 )
10
11
12class TestWriteMvnConfig(CharmTestCase):
13 """Unit tests for utils.write_mvn_config().
14
15 These tests all follow the pattern of mocking environ
16 to manipulate the http_proxy variables write_mvn_config()
17 uses to determine what variables will be supplied when
18 rendering the settings.xml template.
19 """
20
21 def setUp(self):
22 super(TestMvnProxyCtx, self).setUp(utils, [])
23
24 @patch('charmhelpers.core.host.mkdir')
25 @patch('charmhelpers.core.host.write_file')
26 @patch('charmhelpers.core.hookenv.charm_dir')
27 def get_rendered_settings(self, charm_dir, write_file, mkdir):
28 """Call write_mvn_config() to generate XML settings.
29
30 Uses mocks in the right places to prevent interaction with the
31 filesystem and to intercept the write to settings.xml.
32
33 This returns the root element of the XML as a lxml.etree.Element.
34 """
35 charm_dir.return_value = '.'
36 result = utils.write_mvn_config()
37 first_call = write_file.call_args[0]
38 path = first_call[0]
39 self.assertEqual(
40 '/home/opendaylight/.m2/settings.xml',
41 path)
42 contents = first_call[1]
43 root = ET.fromstring(contents)
44 return root
45
46 @patch.dict(utils.environ, {})
47 def test_proxies_empty_if_proxy_not_provided(self):
48 root = self.get_rendered_settings()
49 self.assertEqual([], root.findall('./proxies//*'))
50
51 @patch.dict(utils.environ, {'http_proxy': '-'})
52 def test_proxies_empty_if_hostname_not_in_url(self):
53 root = self.get_rendered_settings()
54 self.assertEqual([], root.findall('./proxies//*'))
55
56 def assertExpectedProxySettings(self, proxy, protocol, overrides=None):
57 """Assert that a 'proxy' element contains the expected settings.
58
59 protocol -- settings vary based on protocol, which can be either 'http'
60 or 'https'.
61
62 overrides -- a dictionary providing additional settings to verify,
63 or overriding the default settings (see below for default settings).
64 """
65 self.assertIsNotNone(proxy)
66
67 expected_values = {
68 'id': "%s_proxy" % protocol,
69 'active': 'true',
70 'protocol': protocol,
71 'host': 'url.com',
72 'port': '80',
73 'nonProxyHosts': None,
74 }
75
76 if overrides is not None:
77 for key, value in overrides.iteritems():
78 expected_values[key] = value
79
80 for key, value in expected_values.iteritems():
81 element = proxy.find(key)
82 self.assertIsNotNone(element)
83 self.assertEqual(value, element.text)
84
85 @patch.dict(utils.environ, {'http_proxy': 'http://url.com/'})
86 def test_http_proxy_specified(self):
87 root = self.get_rendered_settings()
88 proxy = root.find('.//proxies/proxy')
89 self.assertExpectedProxySettings(proxy, 'http')
90
91 @patch.dict(utils.environ, {'https_proxy': 'http://url.com/'})
92 def test_https_proxy_specified(self):
93 root = self.get_rendered_settings()
94 proxy = root.find('.//proxies/proxy')
95 self.assertExpectedProxySettings(proxy, 'https')
96
97 @patch.dict(utils.environ,
98 {'http_proxy': 'http://url.com/', 'https_proxy': 'http://url.com/'})
99 def test_http_and_https_proxy_specified(self):
100 root = self.get_rendered_settings()
101 http_proxy, https_proxy = root.findall('.//proxies/proxy')
102 self.assertExpectedProxySettings(http_proxy, 'http')
103 self.assertExpectedProxySettings(https_proxy, 'https')
104
105 @patch.dict(utils.environ, {'http_proxy': 'http://url.com:8491/'})
106 def test_uses_provided_port(self):
107 root = self.get_rendered_settings()
108 proxy = root.find('.//proxies/proxy')
109 self.assertExpectedProxySettings(proxy, 'http', {'port': '8491'})
110
111 @patch.dict(utils.environ, {'http_proxy': 'http://u:p@url.com/'})
112 def test_uses_username_and_password(self):
113 root = self.get_rendered_settings()
114 proxy = root.find('.//proxies/proxy')
115 overrides = {'username': 'u', 'password': 'p'}
116 self.assertExpectedProxySettings(proxy, 'http', overrides)
117
118 @patch.dict(
119 utils.environ,
120 {'http_proxy': 'http://url.com/', 'no_proxy': 'host1,host2'})
121 def test_uses_no_proxy(self):
122 root = self.get_rendered_settings()
123 proxy = root.find('.//proxies/proxy')
124 self.assertExpectedProxySettings(
125 proxy, 'http', {'nonProxyHosts': 'host1|host2'})
0126
=== added file 'unit_tests/test_utils.py'
--- unit_tests/test_utils.py 1970-01-01 00:00:00 +0000
+++ unit_tests/test_utils.py 2015-07-02 21:50:28 +0000
@@ -0,0 +1,121 @@
1import logging
2import unittest
3import os
4import yaml
5
6from contextlib import contextmanager
7from mock import patch, MagicMock
8
9
10def load_config():
11 '''
12 Walk backwords from __file__ looking for config.yaml, load and return the
13 'options' section'
14 '''
15 config = None
16 f = __file__
17 while config is None:
18 d = os.path.dirname(f)
19 if os.path.isfile(os.path.join(d, 'config.yaml')):
20 config = os.path.join(d, 'config.yaml')
21 break
22 f = d
23
24 if not config:
25 logging.error('Could not find config.yaml in any parent directory '
26 'of %s. ' % file)
27 raise Exception
28
29 return yaml.safe_load(open(config).read())['options']
30
31
32def get_default_config():
33 '''
34 Load default charm config from config.yaml return as a dict.
35 If no default is set in config.yaml, its value is None.
36 '''
37 default_config = {}
38 config = load_config()
39 for k, v in config.iteritems():
40 if 'default' in v:
41 default_config[k] = v['default']
42 else:
43 default_config[k] = None
44 return default_config
45
46
47class CharmTestCase(unittest.TestCase):
48
49 def setUp(self, obj, patches):
50 super(CharmTestCase, self).setUp()
51 self.patches = patches
52 self.obj = obj
53 self.test_config = TestConfig()
54 self.test_relation = TestRelation()
55 self.patch_all()
56
57 def patch(self, method):
58 _m = patch.object(self.obj, method)
59 mock = _m.start()
60 self.addCleanup(_m.stop)
61 return mock
62
63 def patch_all(self):
64 for method in self.patches:
65 setattr(self, method, self.patch(method))
66
67
68class TestConfig(object):
69
70 def __init__(self):
71 self.config = get_default_config()
72
73 def get(self, attr=None):
74 if not attr:
75 return self.get_all()
76 try:
77 return self.config[attr]
78 except KeyError:
79 return None
80
81 def get_all(self):
82 return self.config
83
84 def set(self, attr, value):
85 if attr not in self.config:
86 raise KeyError
87 self.config[attr] = value
88
89
90class TestRelation(object):
91
92 def __init__(self, relation_data={}):
93 self.relation_data = relation_data
94
95 def set(self, relation_data):
96 self.relation_data = relation_data
97
98 def get(self, attr=None, unit=None, rid=None):
99 if attr is None:
100 return self.relation_data
101 elif attr in self.relation_data:
102 return self.relation_data[attr]
103 return None
104
105
106@contextmanager
107def patch_open():
108 '''Patch open() to allow mocking both open() itself and the file that is
109 yielded.
110
111 Yields the mock for "open" and "file", respectively.'''
112 mock_open = MagicMock(spec=open)
113 mock_file = MagicMock(spec=file)
114
115 @contextmanager
116 def stub_open(*args, **kwargs):
117 mock_open(*args, **kwargs)
118 yield mock_file
119
120 with patch('__builtin__.open', stub_open):
121 yield mock_open, mock_file

Subscribers

People subscribed via source and target branches

to all changes: