Merge lp:~sseman/juju-ci-tools/model-change-watcher-py3-2 into lp:juju-ci-tools

Proposed by Seman
Status: Needs review
Proposed branch: lp:~sseman/juju-ci-tools/model-change-watcher-py3-2
Merge into: lp:juju-ci-tools
Prerequisite: lp:~sseman/juju-ci-tools/model-change-watcher-py3
Diff against target: 288 lines (+244/-11)
3 files modified
Makefile (+6/-3)
jujupy.py (+0/-8)
tests_py3/test_assess_model_change_watcher.py (+238/-0)
To merge this branch: bzr merge lp:~sseman/juju-ci-tools/model-change-watcher-py3-2
Reviewer Review Type Date Requested Status
Nicholas Skaggs (community) Approve
Curtis Hovey (community) code Needs Information
Review via email: mp+312755@code.launchpad.net

Description of the change

This is the second branch that adds unit tests for the assess_model_change_watcher.py script.

It also updates the Makefile so that the right version of Python is used to run unittest and flake8. "unittest" doesn't make it simple to exclude some files, example excluding Python 3 files when running Python 2 tests, therefore I created a different directory for Python 3 test scripts.

First branch:
https://code.launchpad.net/~sseman/juju-ci-tools/model-change-watcher-py3/+merge/312523

To post a comment you must log in.
1796. By Seman

Removed event.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

I'll wait until the pre-req is landed. However, 1 comment below.

Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you. I think the Makefile changes are incomplete.

review: Needs Information (code)
Revision history for this message
Seman (sseman) wrote :

I don't know if it works with all OSes you mentioned but I do see your point.

Instead of breaking "make test", I can add a different rule for Python 3 unit tests "test_python3" and only run tests under "tests_py3" directory.

Instead of using the find, grep, xargs commands for lint test, I will exclude the files explicitly and add a different rule for Python 3.
 py3="assess_model_change_watcher.py test_assess_model_change_watcher.py"
 lint:
    flake8 $$(find -name '*.py') --builtins xrange,basestring --exclude $(py3)
 lint_python3:
    flake8 $(py3)

Does the above seem a reasonable solution?

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Thank you Seman.

review: Approve

Unmerged revisions

1796. By Seman

Removed event.

1795. By Seman

Updated Makefile.

1794. By Seman

Added unit tests.

1793. By Seman

Merged.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2016-12-08 03:12:58 +0000
+++ Makefile 2016-12-08 03:12:58 +0000
@@ -1,10 +1,13 @@
1p=test*.py1p=test*.py
2py3="assess_model_change_watcher.py"2python_3_compatible_tests := $$(find -name '*.py' | xargs grep -xl '\#!/usr/bin/env python3')
3python_2_compatible_tests := $$(find -name '*.py' | xargs grep -xL '\#!/usr/bin/env python3')
4
3test:5test:
6 TMPDIR=/tmp python3 -m unittest discover -vv ./tests_py3 -p "$(p)"
4 TMPDIR=/tmp python -m unittest discover -vv ./tests -p "$(p)"7 TMPDIR=/tmp python -m unittest discover -vv ./tests -p "$(p)"
5lint:8lint:
6 python3 -m flake8 --builtins xrange,basestring $(py3)9 python3 -m flake8 $(python_3_compatible_tests)
7 flake8 $$(find -name '*.py') --builtins xrange,basestring --exclude $(py3)10 flake8 $(python_2_compatible_tests) --builtins xrange,basestring
8cover:11cover:
9 python -m coverage run --source="./" --omit "./tests/*" -m unittest discover -vv ./tests12 python -m coverage run --source="./" --omit "./tests/*" -m unittest discover -vv ./tests
10 python -m coverage report13 python -m coverage report
1114
=== modified file 'jujupy.py'
--- jujupy.py 2016-12-08 03:12:58 +0000
+++ jujupy.py 2016-12-08 03:12:58 +0000
@@ -1272,19 +1272,11 @@
1272 enforced.1272 enforced.
1273 """1273 """
1274 version = EnvJujuClient.get_version(juju_path)1274 version = EnvJujuClient.get_version(juju_path)
1275<<<<<<< TREE
1276 client_class = get_client_class(version)
1277 if config is None:
1278 env = client_class.config_class('', {})
1279 else:
1280 env = client_class.config_class.from_config(config)
1281=======
1282 client_class = get_client_class(str(version))1275 client_class = get_client_class(str(version))
1283 if config is None:1276 if config is None:
1284 env = client_class.config_class('', {})1277 env = client_class.config_class('', {})
1285 else:1278 else:
1286 env = client_class.config_class.from_config(config)1279 env = client_class.config_class.from_config(config)
1287>>>>>>> MERGE-SOURCE
1288 if juju_path is None:1280 if juju_path is None:
1289 full_path = EnvJujuClient.get_full_path()1281 full_path = EnvJujuClient.get_full_path()
1290 else:1282 else:
12911283
=== added directory 'tests_py3'
=== added file 'tests_py3/test_assess_model_change_watcher.py'
--- tests_py3/test_assess_model_change_watcher.py 1970-01-01 00:00:00 +0000
+++ tests_py3/test_assess_model_change_watcher.py 2016-12-08 03:12:58 +0000
@@ -0,0 +1,238 @@
1#!/usr/bin/env python3
2
3import asyncio
4import logging
5from io import BytesIO, StringIO, TextIOWrapper
6import sys
7
8
9from mock import (
10 Mock,
11 patch,
12 )
13
14from assess_model_change_watcher import (
15 assess_model_change_watcher,
16 _get_message,
17 is_config_change_in_event,
18 listen_to_watcher,
19 main,
20 parse_args,
21 run_listener,
22 )
23from fakejuju import fake_juju_client
24from tests import (
25 parse_error,
26 TestCase,
27 )
28
29
30class TestParseArgs(TestCase):
31 def test_common_args(self):
32 with patch('utility.warnings'):
33 args = parse_args(
34 ["an-env", "/bin/juju", "/tmpp/logs", "an-env-mod"])
35 self.assertEqual("an-env", args.env)
36 self.assertEqual("/bin/juju", args.juju_bin)
37 self.assertEqual("/tmpp/logs", args.logs)
38 self.assertEqual("an-env-mod", args.temp_env_name)
39 self.assertEqual(False, args.debug)
40
41 def test_help(self):
42 if isinstance(sys.stdout, TextIOWrapper):
43 fake_stdout = StringIO()
44 else:
45 fake_stdout = BytesIO()
46 with parse_error(self) as fake_stderr:
47 with patch("sys.stdout", fake_stdout):
48 parse_args(["--help"])
49 self.assertEqual("", fake_stderr.getvalue())
50
51
52class TestMain(TestCase):
53
54 def test_main(self):
55 argv = ["an-env", "/bin/juju", "/tmp/logs", "an-env-mod", "--verbose"]
56 client = Mock(spec=["is_jes_enabled"])
57 with patch("assess_model_change_watcher.configure_logging",
58 autospec=True) as mock_cl:
59 with patch("assess_model_change_watcher.BootstrapManager."
60 "booted_context",
61 autospec=True) as mock_bc:
62 with patch('deploy_stack.client_from_config',
63 return_value=client) as mock_cfc:
64 with patch("assess_model_change_watcher."
65 "assess_model_change_watcher",
66 autospec=True) as mock_amcw:
67 with patch('utility.warnings'):
68 main(argv)
69 mock_cl.assert_called_once_with(logging.DEBUG)
70 mock_cfc.assert_called_once_with('an-env', "/bin/juju", debug=False,
71 soft_deadline=None)
72 self.assertEqual(mock_bc.call_count, 1)
73 mock_amcw.assert_called_once_with(client, None, '/bin/juju')
74
75
76class TestAssess(TestCase):
77
78 watcher = [
79 "application",
80 "change",
81 {
82 "owner-tag": "",
83 "status": {
84 "version": "",
85 "message": "Token is bar",
86 "current": "active",
87 "since": "2016-12-01T02:38:03.078457034Z"
88 },
89 "min-units": 0,
90 "life": "alive",
91 "charm-url": "local:trusty/dummy-source-0",
92 "exposed": False,
93 "name": "dummy-source",
94 "model-uuid": "0b9a4bd8-4980-43cd-8764-cc3ead6ac364",
95 "constraints": {},
96 "subordinate": False,
97 "config": {
98 "token": "1234asdf"
99 }
100 }
101 ]
102
103 watcher_no_config = [
104 "application",
105 "change",
106 {
107 "owner-tag": "",
108 "status": {
109 "version": "",
110 "message": "Token is bar",
111 "current": "active",
112 "since": "2016-12-01T02:38:03.078457034Z"
113 },
114 "min-units": 0,
115 "life": "alive",
116 "charm-url": "local:trusty/dummy-source-0",
117 "exposed": False,
118 "name": "dummy-source",
119 "model-uuid": "0b9a4bd8-4980-43cd-8764-cc3ead6ac364",
120 "constraints": {},
121 "subordinate": False,
122 }
123 ]
124
125 def test_assess_model_change_watcher(self):
126 fake_client = Mock(wraps=fake_juju_client())
127 fake_client.bootstrap()
128 fake_loop = Mock(spec=['run_until_complete', 'close'])
129 fake_future = Mock(spec=['result'])
130 fake_future.result.return_value = True
131 with patch('assess_model_change_watcher.run_listener',
132 autospec=True, return_value=(fake_loop, fake_future)
133 ) as mock_rl:
134 assess_model_change_watcher(fake_client, 'angsty', 'bin')
135 fake_client.deploy.assert_called_once_with('dummy-source')
136 fake_client.wait_for_started.assert_called_once_with()
137 self.assertEqual(
138 1, fake_client.get_status().get_service_unit_count('dummy-source'))
139 mock_rl.assert_called_once_with(
140 fake_client, is_config_change_in_event, 'bin')
141 fake_loop.run_until_complete.assert_called_once_with(fake_future)
142 fake_future.result.assert_called_once_with()
143
144 def test_get_message(self):
145 event = _get_message(self.watcher)
146 self.assertEqual(event, self.watcher[2])
147
148 def test_is_config_in_event(self):
149 self.assertIsTrue(is_config_change_in_event(self.watcher))
150
151 def test_is_config_in_event_false(self):
152 watcher = ["application", "change", {"foo": "bar"}]
153 self.assertIsFalse(is_config_change_in_event(watcher))
154
155 def test_run_listener(self):
156 fake_asyncio = Mock(spec=['get_event_loop', 'Future'])
157 fake_client = Mock(env=Mock(juju_home='/tmp'), spec=[])
158 with patch('assess_model_change_watcher.asyncio',
159 autospec=True, return_value=fake_asyncio) as mock_async:
160 with patch("assess_model_change_watcher.Connection", autospec=True,
161 ) as mock_c:
162 with patch("assess_model_change_watcher.listen_to_watcher",
163 autospec=True, ) as mock_ltw:
164 loop, future = run_listener(fake_client, None, 'foo')
165 mock_async.get_event_loop.assert_called_once_with()
166 mock_async.Future.assert_called_once_with()
167 mock_loop = mock_async.get_event_loop.return_value
168 mock_loop.run_until_complete.assert_called_once_with(
169 mock_c.connect_current.return_value)
170 mock_async.ensure_future.assert_called_once_with(mock_ltw(
171 None, mock_loop.run_until_complete.return_value,
172 mock_async.Future.return_value))
173 self.assertEqual(loop, mock_loop)
174 self.assertEqual(future, mock_async.Future.return_value)
175
176 def test_listen_to_watcher(self):
177 fake_future = Mock(spec=['set_result'])
178 loop = asyncio.new_event_loop()
179 asyncio.set_event_loop(None)
180 with patch("assess_model_change_watcher.watcher.AllWatcher",
181 autospec=True, return_value=FakeWatcher()) as w_mock:
182 loop.run_until_complete(listen_to_watcher(
183 is_config_change_in_event, FakeConn(), fake_future))
184 w_mock.assert_called_once_with()
185 fake_future.set_result.assert_called_once_with(True)
186 loop.close()
187
188 def test_listen_to_watcher_no_config(self):
189 fake_future = Mock(spec=['set_result'])
190 loop = asyncio.new_event_loop()
191 asyncio.set_event_loop(None)
192 with patch("assess_model_change_watcher.watcher.AllWatcher",
193 autospec=True, return_value=FakeWatcherNoConfig()
194 ) as w_mock:
195 with patch("assess_model_change_watcher.until_timeout",
196 autospec=True, return_value=[1]) as mock_ut:
197 loop.run_until_complete(listen_to_watcher(
198 is_config_change_in_event, FakeConn(), fake_future))
199 w_mock.assert_called_once_with()
200 fake_future.set_result.assert_called_once_with(False)
201 mock_ut.assert_called_once_with(120)
202 loop.close()
203
204
205class FakeConn:
206
207 async def close(self):
208 return
209
210
211class FakeWatcher:
212
213 def connect(self, conn):
214 pass
215
216 async def Stop(self):
217 return
218
219 async def Next(self):
220 class FakeDelta:
221 deltas = TestAssess.watcher
222
223 class FakeChange:
224 deltas = [FakeDelta()]
225
226 return FakeChange()
227
228
229class FakeWatcherNoConfig(FakeWatcher):
230
231 async def Next(self):
232 class FakeDelta:
233 deltas = TestAssess.watcher_no_config
234
235 class FakeChange:
236 deltas = [FakeDelta()]
237
238 return FakeChange()

Subscribers

People subscribed via source and target branches