Merge lp:~abentley/juju-ci-tools/more-py3 into lp:juju-ci-tools

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 1932
Proposed branch: lp:~abentley/juju-ci-tools/more-py3
Merge into: lp:juju-ci-tools
Diff against target: 381 lines (+60/-47)
7 files modified
.bzrignore (+1/-0)
jujupy/client.py (+4/-4)
jujupy/fake.py (+1/-1)
jujupy/tests/test_client.py (+34/-16)
jujupy/tests/test_version_client.py (+10/-9)
tests/__init__.py (+4/-0)
tests/test_assess_resources.py (+6/-17)
To merge this branch: bzr merge lp:~abentley/juju-ci-tools/more-py3
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+319598@code.launchpad.net

Commit message

Improve Python 3 compatibility.

Description of the change

This branch improves jujupy's compatibility with Python 3.

The bulk of this is due to string handling differences: We had been using string literals as expected values, where the actual value was a binary string. The simplest thing to do seems to be encoding the string to ascii.

The location of StringIO changed, so its imports had to be updated.

Python 3 dicts cannot be sorted, so in a few places I changed it to sort by application name, not sorting application data itself.

Similarly, in Python 3 the return value of dict.keys() is not a list, and is never equal to a list. So dict.keys() had to become list(dict.keys()).

Also, since iterator.next() is iterator.__next__ on Python 3, next(iterator) is used instead.

Python 2's TestCase.assertItemsEqual is replaced in Python 3 with TestCase.assertCountEqual. I forced the old name to work again for tests.TestCase.

jujupy.tests.test_client was importing make_resource_list from tests.test_assess_resources. This is a dependency inversion. It was also causing winrm to be imported, which failed. Rather than chase that down, I fixed the dependency inversion by having test_assess_resources import from test_client.

(I have discovered there are more dependency inversions, especially with juju-ci-tools/tests/__init__.py, but that will have to wait.)

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you very much.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2017-02-24 18:06:58 +0000
3+++ .bzrignore 2017-03-10 16:30:01 +0000
4@@ -4,3 +4,4 @@
5 /logs
6 /repository
7 ./.coverage
8+__pycache__
9
10=== modified file 'jujupy/client.py'
11--- jujupy/client.py 2017-03-10 15:44:39 +0000
12+++ jujupy/client.py 2017-03-10 16:30:01 +0000
13@@ -987,7 +987,7 @@
14
15 def get_unit(self, unit_name):
16 """Return metadata about a unit."""
17- for service in sorted(self.get_applications().values()):
18+ for name, service in sorted(self.get_applications().items()):
19 if unit_name in service.get('units', {}):
20 return service['units'][unit_name]
21 raise KeyError(unit_name)
22@@ -996,8 +996,8 @@
23 """Return subordinate metadata for a service_name."""
24 services = self.get_applications()
25 if service_name in services:
26- for unit in sorted(services[service_name].get(
27- 'units', {}).values()):
28+ for name, unit in sorted(services[service_name].get(
29+ 'units', {}).items()):
30 for sub_name, sub in unit.get('subordinates', {}).items():
31 yield sub_name, sub
32
33@@ -2366,7 +2366,7 @@
34 if status is None:
35 continue
36 states.setdefault(status, []).append(machine)
37- if states.keys() == [desired_state]:
38+ if list(states.keys()) == [desired_state]:
39 if len(states.get(desired_state, [])) >= 3:
40 return None
41 return states
42
43=== modified file 'jujupy/fake.py'
44--- jujupy/fake.py 2017-03-02 17:14:13 +0000
45+++ jujupy/fake.py 2017-03-10 16:30:01 +0000
46@@ -390,7 +390,7 @@
47 if type(pattern) is not list:
48 pattern = [pattern]
49 try:
50- prompt = self.prompts.next()
51+ prompt = next(self.prompts)
52 except StopIteration:
53 if pexpect.EOF not in pattern:
54 raise
55
56=== modified file 'jujupy/tests/test_client.py'
57--- jujupy/tests/test_client.py 2017-03-09 22:57:32 +0000
58+++ jujupy/tests/test_client.py 2017-03-10 16:30:01 +0000
59@@ -9,7 +9,10 @@
60 import logging
61 import os
62 import socket
63-import StringIO
64+try:
65+ from StringIO import StringIO
66+except ImportError:
67+ from io import StringIO
68 import subprocess
69 import sys
70 from textwrap import dedent
71@@ -99,7 +102,6 @@
72 observable_temp_file,
73 TestCase,
74 )
75-from tests.test_assess_resources import make_resource_list
76 from jujupy.utility import (
77 JujuResourceTimeout,
78 scoped_environ,
79@@ -301,7 +303,7 @@
80 soft_deadline=None)
81 with patch('subprocess.Popen') as mock_popen:
82 mock_popen.return_value.communicate.return_value = (
83- 'ctrl1:user1/model1\n', '')
84+ 'ctrl1:user1/model1\n'.encode('ascii'), '')
85 mock_popen.return_value.returncode = 0
86 result = backend.get_active_model('/foo/bar')
87 self.assertEqual(('ctrl1', 'user1', 'model1'), result)
88@@ -367,6 +369,21 @@
89 not_present.do_raise('', None)
90
91
92+def make_resource_list(service_app_id='applicationId'):
93+ return {'resources': [{
94+ 'expected': {
95+ 'origin': 'upload', 'used': True, 'description': 'foo resource.',
96+ 'username': 'admin', 'resourceid': 'dummy-resource/foo',
97+ 'name': 'foo', service_app_id: 'dummy-resource', 'size': 27,
98+ 'fingerprint': '1234', 'type': 'file', 'path': 'foo.txt'},
99+ 'unit': {
100+ 'origin': 'upload', 'username': 'admin', 'used': True,
101+ 'name': 'foo', 'resourceid': 'dummy-resource/foo',
102+ service_app_id: 'dummy-resource', 'fingerprint': '1234',
103+ 'path': 'foo.txt', 'size': 27, 'type': 'file',
104+ 'description': 'foo resource.'}}]}
105+
106+
107 class TestModelClient(ClientTest):
108
109 def test_get_full_path(self):
110@@ -1147,7 +1164,7 @@
111 units:
112 jenkins/0:
113 {0}: {2}
114- """.format(key, machine_value, unit_value))
115+ """.format(key, machine_value, unit_value)).encode('ascii')
116
117 def test_deploy_non_joyent(self):
118 env = ModelClient(
119@@ -2278,7 +2295,8 @@
120 client.wait_for_ha()
121
122 def test_wait_for_ha_no_has_vote(self):
123- value = yaml.safe_dump(self.make_ha_status(voting='no-vote'))
124+ value = yaml.safe_dump(
125+ self.make_ha_status(voting='no-vote')).encode('ascii')
126 client = self.make_controller_client()
127 with patch.object(client, 'get_juju_output', return_value=value):
128 writes = []
129@@ -2469,7 +2487,7 @@
130 '0': {'agent-state': 'started'},
131 '1': {'agent-state': 'started'},
132 },
133- })
134+ }).encode('ascii')
135 client = ModelClient(JujuData('local'), None, None)
136 with patch.object(client, 'get_juju_output', return_value=value), \
137 patch('jujupy.client.until_timeout',
138@@ -5697,7 +5715,7 @@
139 class TestGroupReporter(TestCase):
140
141 def test_single(self):
142- sio = StringIO.StringIO()
143+ sio = StringIO()
144 reporter = GroupReporter(sio, "done")
145 self.assertEqual(sio.getvalue(), "")
146 reporter.update({"working": ["1"]})
147@@ -5706,7 +5724,7 @@
148 self.assertEqual(sio.getvalue(), "working: 1\n")
149
150 def test_single_ticks(self):
151- sio = StringIO.StringIO()
152+ sio = StringIO()
153 reporter = GroupReporter(sio, "done")
154 reporter.update({"working": ["1"]})
155 self.assertEqual(sio.getvalue(), "working: 1")
156@@ -5718,7 +5736,7 @@
157 self.assertEqual(sio.getvalue(), "working: 1 ..\n")
158
159 def test_multiple_values(self):
160- sio = StringIO.StringIO()
161+ sio = StringIO()
162 reporter = GroupReporter(sio, "done")
163 reporter.update({"working": ["1", "2"]})
164 self.assertEqual(sio.getvalue(), "working: 1, 2")
165@@ -5728,7 +5746,7 @@
166 self.assertEqual(sio.getvalue(), "working: 1, 2\nworking: 1\n")
167
168 def test_multiple_groups(self):
169- sio = StringIO.StringIO()
170+ sio = StringIO()
171 reporter = GroupReporter(sio, "done")
172 reporter.update({"working": ["1", "2"], "starting": ["3"]})
173 first = "starting: 3 | working: 1, 2"
174@@ -5740,7 +5758,7 @@
175 self.assertEqual(sio.getvalue(), "\n".join([first, second, ""]))
176
177 def test_finish(self):
178- sio = StringIO.StringIO()
179+ sio = StringIO()
180 reporter = GroupReporter(sio, "done")
181 self.assertEqual(sio.getvalue(), "")
182 reporter.update({"working": ["1"]})
183@@ -5749,14 +5767,14 @@
184 self.assertEqual(sio.getvalue(), "working: 1\n")
185
186 def test_finish_unchanged(self):
187- sio = StringIO.StringIO()
188+ sio = StringIO()
189 reporter = GroupReporter(sio, "done")
190 self.assertEqual(sio.getvalue(), "")
191 reporter.finish()
192 self.assertEqual(sio.getvalue(), "")
193
194 def test_wrap_to_width(self):
195- sio = StringIO.StringIO()
196+ sio = StringIO()
197 reporter = GroupReporter(sio, "done")
198 self.assertEqual(sio.getvalue(), "")
199 for _ in range(150):
200@@ -5769,7 +5787,7 @@
201 """)
202
203 def test_wrap_to_width_exact(self):
204- sio = StringIO.StringIO()
205+ sio = StringIO()
206 reporter = GroupReporter(sio, "done")
207 reporter.wrap_width = 12
208 self.assertEqual(sio.getvalue(), "")
209@@ -5788,7 +5806,7 @@
210 self.assertEqual(sio.getvalue(), changes[-1] + "\n")
211
212 def test_wrap_to_width_overflow(self):
213- sio = StringIO.StringIO()
214+ sio = StringIO()
215 reporter = GroupReporter(sio, "done")
216 reporter.wrap_width = 8
217 self.assertEqual(sio.getvalue(), "")
218@@ -5806,7 +5824,7 @@
219 self.assertEqual(sio.getvalue(), changes[-1] + "\n")
220
221 def test_wrap_to_width_multiple_groups(self):
222- sio = StringIO.StringIO()
223+ sio = StringIO()
224 reporter = GroupReporter(sio, "done")
225 reporter.wrap_width = 16
226 self.assertEqual(sio.getvalue(), "")
227
228=== modified file 'jujupy/tests/test_version_client.py'
229--- jujupy/tests/test_version_client.py 2017-03-01 19:02:24 +0000
230+++ jujupy/tests/test_version_client.py 2017-03-10 16:30:01 +0000
231@@ -804,7 +804,7 @@
232 units:
233 jenkins/0:
234 {0}: {2}
235- """.format(key, machine_value, unit_value))
236+ """.format(key, machine_value, unit_value)).encode('ascii')
237
238 def test_deploy_non_joyent(self):
239 env = EnvJujuClient1X(
240@@ -992,7 +992,8 @@
241 'Timed out waiting for agents to start in local'):
242 client.wait_for_started(0)
243 self.assertEqual(writes, ['pending: 0', '\n'])
244- self.assertEqual(self.log_stream.getvalue(), 'ERROR %s\n' % value)
245+ self.assertEqual(self.log_stream.getvalue(),
246+ 'ERROR %s\n' % value.decode('ascii'))
247
248 def test_wait_for_subordinate_units(self):
249 value = dedent("""\
250@@ -1014,7 +1015,7 @@
251 agent-state: started
252 sub3/0:
253 agent-state: started
254- """)
255+ """).encode('ascii')
256 client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
257 now = datetime.now() + timedelta(days=1)
258 with patch('utility.until_timeout.now', return_value=now):
259@@ -1072,7 +1073,7 @@
260 subordinates:
261 sub1:
262 agent-state: started
263- """)
264+ """).encode('ascii')
265 client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
266 now = datetime.now() + timedelta(days=1)
267 with patch('utility.until_timeout.now', return_value=now):
268@@ -1093,7 +1094,7 @@
269 units:
270 jenkins/0:
271 agent-state: started
272- """)
273+ """).encode('ascii')
274 client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
275 now = datetime.now() + timedelta(days=1)
276 with patch('utility.until_timeout.now', return_value=now):
277@@ -1319,7 +1320,7 @@
278 'machines': {
279 '0': {'agent-state': 'started'},
280 },
281- })
282+ }).encode('utf-8')
283 client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
284 with patch.object(client, 'get_juju_output', return_value=value):
285 client.wait_for(WaitMachineNotPresent('1'), quiet=True)
286@@ -1330,7 +1331,7 @@
287 '0': {'agent-state': 'started'},
288 '1': {'agent-state': 'started'},
289 },
290- })
291+ }).encode('ascii')
292 client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
293 with patch.object(client, 'get_juju_output', return_value=value), \
294 patch('jujupy.client.until_timeout',
295@@ -1865,7 +1866,7 @@
296 def test_action_fetch_timeout(self):
297 client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
298 '1.23-series-arch', None)
299- ret = "status: pending\nfoo: bar"
300+ ret = "status: pending\nfoo: bar".encode('ascii')
301 with patch.object(EnvJujuClient1X,
302 'get_juju_output', return_value=ret):
303 with self.assertRaisesRegexp(Exception,
304@@ -2268,7 +2269,7 @@
305
306 def test_iter_status_data(self):
307 iterator = self.run_iter_status()
308- self.assertEqual(iterator.next().status,
309+ self.assertEqual(next(iterator).status,
310 dict(current='started', message='all good',
311 version='1.25.1'))
312
313
314=== modified file 'tests/__init__.py'
315--- tests/__init__.py 2017-02-22 18:02:19 +0000
316+++ tests/__init__.py 2017-03-10 16:30:01 +0000
317@@ -82,6 +82,10 @@
318 return context.__enter__()
319
320
321+if getattr(TestCase, 'assertItemsEqual', None) is None:
322+ TestCase.assertItemsEqual = TestCase.assertCountEqual
323+
324+
325 class FakeHomeTestCase(TestCase):
326 """FakeHomeTestCase creates an isolated home dir for Juju to use."""
327
328
329=== modified file 'tests/test_assess_resources.py'
330--- tests/test_assess_resources.py 2017-02-25 06:09:40 +0000
331+++ tests/test_assess_resources.py 2017-03-10 16:30:01 +0000
332@@ -1,7 +1,10 @@
333 import logging
334 from argparse import Namespace
335 from mock import Mock, patch, call
336-import StringIO
337+try:
338+ from StringIO import StringIO
339+except ImportError:
340+ from io import StringIO
341
342 from assess_resources import (
343 assess_resources,
344@@ -11,6 +14,7 @@
345 main,
346 verify_status,
347 )
348+from jujupy.tests.test_client import make_resource_list
349 from tests import (
350 parse_error,
351 TestCase,
352@@ -33,7 +37,7 @@
353 self.assertEqual(False, args.debug)
354
355 def test_help(self):
356- fake_stdout = StringIO.StringIO()
357+ fake_stdout = StringIO()
358 with parse_error(self) as fake_stderr:
359 with patch("sys.stdout", fake_stdout):
360 parse_args(["--help"])
361@@ -225,20 +229,5 @@
362 deadline=None,)
363
364
365-def make_resource_list(service_app_id='applicationId'):
366- return {'resources': [{
367- 'expected': {
368- 'origin': 'upload', 'used': True, 'description': 'foo resource.',
369- 'username': 'admin', 'resourceid': 'dummy-resource/foo',
370- 'name': 'foo', service_app_id: 'dummy-resource', 'size': 27,
371- 'fingerprint': '1234', 'type': 'file', 'path': 'foo.txt'},
372- 'unit': {
373- 'origin': 'upload', 'username': 'admin', 'used': True,
374- 'name': 'foo', 'resourceid': 'dummy-resource/foo',
375- service_app_id: 'dummy-resource', 'fingerprint': '1234',
376- 'path': 'foo.txt', 'size': 27, 'type': 'file',
377- 'description': 'foo resource.'}}]}
378-
379-
380 class FakeFile:
381 name = '/tmp/fake'

Subscribers

People subscribed via source and target branches