Merge lp:~stub/charm-helpers/py3 into lp:charm-helpers

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 371
Proposed branch: lp:~stub/charm-helpers/py3
Merge into: lp:charm-helpers
Diff against target: 213 lines (+68/-18)
7 files modified
charmhelpers/contrib/peerstorage/__init__.py (+2/-0)
charmhelpers/core/hookenv.py (+6/-0)
charmhelpers/core/host.py (+1/-1)
charmhelpers/fetch/__init__.py (+1/-1)
tests/contrib/peerstorage/test_peerstorage.py (+8/-0)
tests/core/test_hookenv.py (+37/-1)
tests/core/test_templating.py (+13/-15)
To merge this branch: bzr merge lp:~stub/charm-helpers/py3
Reviewer Review Type Date Requested Status
Jorge Niedbalski (community) Needs Fixing
charmers Pending
Review via email: mp+244668@code.launchpad.net

Description of the change

A small grabbag of fixes I have needed putting together a new charm.

The main fix is host.write() for Python3, where Python3 is picky about byte strings vs. text strings. Since we use write for all sorts of files, it needs to accept byte strings. Py2 users are unaffected.

Includes fixes for Bug #1402192 , as well as https://code.launchpad.net/~stub/charm-helpers/improve-peerstorage/+merge/243519 (if that hasn't landed already).

To post a comment you must log in.
lp:~stub/charm-helpers/py3 updated
214. By Stuart Bishop

Merge trunk

Revision history for this message
Jorge Niedbalski (niedbalski) wrote :

LGTM, minor observations.

review: Needs Fixing
lp:~stub/charm-helpers/py3 updated
215. By Stuart Bishop

Tidy a test

Revision history for this message
Stuart Bishop (stub) wrote :

I discussed the .get() method with Jorge, and why the current approach is necessary.

I have tidied the test I made whitespace changes to further per request.

Revision history for this message
Jay R. Wren (evarlast) wrote :

The behavior of the in operator is not consistent.

In an attempt to workaround the get bug, one may use in to prevent a KeyError

    if 'foo' not in config:
        return

but this returns even if 'foo' is in _prev_dict.

a workaround is

    if 'foo' not in config.keys():
        return

but expected behavior is that the .keys() call is not necessary.

lp:~stub/charm-helpers/py3 updated
216. By Stuart Bishop

Merge trunk

217. By Stuart Bishop

Add test demonstrating the behavior of the in operator

Revision history for this message
Stuart Bishop (stub) wrote :

@evarlast - I've added a test for the behaviour of the in operator, and it seems to be fine.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charmhelpers/contrib/peerstorage/__init__.py'
--- charmhelpers/contrib/peerstorage/__init__.py 2015-01-22 06:06:03 +0000
+++ charmhelpers/contrib/peerstorage/__init__.py 2015-04-29 13:06:12 +0000
@@ -73,6 +73,8 @@
73 exc_list = exc_list if exc_list else []73 exc_list = exc_list if exc_list else []
74 peerdb_settings = peer_retrieve('-', relation_name=relation_name)74 peerdb_settings = peer_retrieve('-', relation_name=relation_name)
75 matched = {}75 matched = {}
76 if peerdb_settings is None:
77 return matched
76 for k, v in peerdb_settings.items():78 for k, v in peerdb_settings.items():
77 full_prefix = prefix + delimiter79 full_prefix = prefix + delimiter
78 if k.startswith(full_prefix):80 if k.startswith(full_prefix):
7981
=== modified file 'charmhelpers/core/hookenv.py'
--- charmhelpers/core/hookenv.py 2015-04-07 16:12:51 +0000
+++ charmhelpers/core/hookenv.py 2015-04-29 13:06:12 +0000
@@ -250,6 +250,12 @@
250 except KeyError:250 except KeyError:
251 return (self._prev_dict or {})[key]251 return (self._prev_dict or {})[key]
252252
253 def get(self, key, default=None):
254 try:
255 return self[key]
256 except KeyError:
257 return default
258
253 def keys(self):259 def keys(self):
254 prev_keys = []260 prev_keys = []
255 if self._prev_dict is not None:261 if self._prev_dict is not None:
256262
=== modified file 'charmhelpers/core/host.py'
--- charmhelpers/core/host.py 2015-03-12 10:56:57 +0000
+++ charmhelpers/core/host.py 2015-04-29 13:06:12 +0000
@@ -90,7 +90,7 @@
90 ['service', service_name, 'status'],90 ['service', service_name, 'status'],
91 stderr=subprocess.STDOUT).decode('UTF-8')91 stderr=subprocess.STDOUT).decode('UTF-8')
92 except subprocess.CalledProcessError as e:92 except subprocess.CalledProcessError as e:
93 return 'unrecognized service' not in e.output93 return b'unrecognized service' not in e.output
94 else:94 else:
95 return True95 return True
9696
9797
=== modified file 'charmhelpers/fetch/__init__.py'
--- charmhelpers/fetch/__init__.py 2015-01-22 06:11:15 +0000
+++ charmhelpers/fetch/__init__.py 2015-04-29 13:06:12 +0000
@@ -158,7 +158,7 @@
158158
159def apt_cache(in_memory=True):159def apt_cache(in_memory=True):
160 """Build and return an apt cache"""160 """Build and return an apt cache"""
161 import apt_pkg161 from apt import apt_pkg
162 apt_pkg.init()162 apt_pkg.init()
163 if in_memory:163 if in_memory:
164 apt_pkg.config.set("Dir::Cache::pkgcache", "")164 apt_pkg.config.set("Dir::Cache::pkgcache", "")
165165
=== modified file 'tests/contrib/peerstorage/test_peerstorage.py'
--- tests/contrib/peerstorage/test_peerstorage.py 2014-08-19 09:38:07 +0000
+++ tests/contrib/peerstorage/test_peerstorage.py 2015-04-29 13:06:12 +0000
@@ -146,6 +146,14 @@
146 self.assertEquals(peerstorage.peer_retrieve_by_prefix(rel_id), settings)146 self.assertEquals(peerstorage.peer_retrieve_by_prefix(rel_id), settings)
147147
148 @patch.object(peerstorage, 'peer_retrieve')148 @patch.object(peerstorage, 'peer_retrieve')
149 def test_peer_retrieve_by_prefix_empty_relation(self, peer_retrieve):
150 # If relation-get returns None, peer_retrieve_by_prefix returns
151 # an empty dictionary.
152 peer_retrieve.return_value = None
153 rel_id = 'db:2'
154 self.assertEquals(peerstorage.peer_retrieve_by_prefix(rel_id), {})
155
156 @patch.object(peerstorage, 'peer_retrieve')
149 def test_peer_retrieve_by_prefix_exc_list(self, peer_retrieve):157 def test_peer_retrieve_by_prefix_exc_list(self, peer_retrieve):
150 rel_id = 'db:2'158 rel_id = 'db:2'
151 settings = {159 settings = {
152160
=== modified file 'tests/core/test_hookenv.py'
--- tests/core/test_hookenv.py 2015-03-12 10:31:22 +0000
+++ tests/core/test_hookenv.py 2015-04-29 13:06:12 +0000
@@ -3,7 +3,7 @@
3from subprocess import CalledProcessError3from subprocess import CalledProcessError
4import shutil4import shutil
5import tempfile5import tempfile
6from mock import patch, call, mock_open6from mock import patch, call, mock_open, sentinel
7from mock import MagicMock7from mock import MagicMock
8from testtools import TestCase8from testtools import TestCase
9import yaml9import yaml
@@ -128,11 +128,47 @@
128 self.assertEqual(c['foo'], 'bar')128 self.assertEqual(c['foo'], 'bar')
129 self.assertEqual(c['baz'], 'bam')129 self.assertEqual(c['baz'], 'bam')
130130
131 def test_get(self):
132 c = hookenv.Config(dict(foo='bar'))
133 c.save()
134 c = hookenv.Config(dict(baz='bam'))
135
136 self.assertIsNone(c.get('missing'))
137 self.assertIs(c.get('missing', sentinel.missing), sentinel.missing)
138 self.assertEqual(c.get('foo'), 'bar')
139 self.assertEqual(c.get('baz'), 'bam')
140
131 def test_keys(self):141 def test_keys(self):
132 c = hookenv.Config(dict(foo='bar'))142 c = hookenv.Config(dict(foo='bar'))
133 c["baz"] = "bar"143 c["baz"] = "bar"
134 self.assertEqual(sorted([six.u("foo"), "baz"]), sorted(c.keys()))144 self.assertEqual(sorted([six.u("foo"), "baz"]), sorted(c.keys()))
135145
146 def test_in(self):
147 # Test behavior of the in operator.
148
149 # Items that exist in the dict exist. Items that don't don't.
150 c = hookenv.Config(dict(foo='one'))
151 self.assertTrue('foo' in c)
152 self.assertTrue('bar' not in c)
153 c.save()
154 self.assertTrue('foo' in c)
155 self.assertTrue('bar' not in c)
156
157 # Adding items works as expected.
158 c['foo'] = 'two'
159 c['bar'] = 'two'
160 self.assertTrue('foo' in c)
161 self.assertTrue('bar' in c)
162 c.save()
163 self.assertTrue('foo' in c)
164 self.assertTrue('bar' in c)
165
166 # Removing items works as expected.
167 del c['foo']
168 self.assertTrue('foo' not in c)
169 c.save()
170 self.assertTrue('foo' not in c)
171
136172
137class SerializableTest(TestCase):173class SerializableTest(TestCase):
138 def test_serializes_object_to_json(self):174 def test_serializes_object_to_json(self):
139175
=== modified file 'tests/core/test_templating.py'
--- tests/core/test_templating.py 2015-02-03 21:52:36 +0000
+++ tests/core/test_templating.py 2015-04-29 13:06:12 +0000
@@ -1,9 +1,9 @@
1import os
2import pkg_resources1import pkg_resources
3import shutil2import shutil
4import tempfile3import tempfile
5import unittest4import unittest
6import jinja25import jinja2
6import os.path
7import pwd7import pwd
8import grp8import grp
99
@@ -17,7 +17,8 @@
17class TestTemplating(unittest.TestCase):17class TestTemplating(unittest.TestCase):
18 def setUp(self):18 def setUp(self):
19 self.charm_dir = pkg_resources.resource_filename(__name__, '')19 self.charm_dir = pkg_resources.resource_filename(__name__, '')
20 self._charm_dir_patch = mock.patch.object(templating.hookenv, 'charm_dir')20 self._charm_dir_patch = mock.patch.object(templating.hookenv,
21 'charm_dir')
21 self._charm_dir_mock = self._charm_dir_patch.start()22 self._charm_dir_mock = self._charm_dir_patch.start()
22 self._charm_dir_mock.side_effect = lambda: self.charm_dir23 self._charm_dir_mock.side_effect = lambda: self.charm_dir
2324
@@ -28,9 +29,8 @@
28 @mock.patch.object(templating.host, 'mkdir')29 @mock.patch.object(templating.host, 'mkdir')
29 @mock.patch.object(templating.host, 'log')30 @mock.patch.object(templating.host, 'log')
30 def test_render(self, log, mkdir, fchown):31 def test_render(self, log, mkdir, fchown):
31 _, fn1 = tempfile.mkstemp()32 with tempfile.NamedTemporaryFile() as fn1, \
32 _, fn2 = tempfile.mkstemp()33 tempfile.NamedTemporaryFile() as fn2:
33 try:
34 context = {34 context = {
35 'nats': {35 'nats': {
36 'port': '1234',36 'port': '1234',
@@ -41,23 +41,19 @@
41 },41 },
42 'nginx_port': 80,42 'nginx_port': 80,
43 }43 }
44 templating.render('fake_cc.yml', fn1,44 templating.render('fake_cc.yml', fn1.name,
45 context, templates_dir=TEMPLATES_DIR)45 context, templates_dir=TEMPLATES_DIR)
46 contents = open(fn1).read()46 contents = open(fn1.name).read()
47 self.assertRegexpMatches(contents, 'port: 1234')47 self.assertRegexpMatches(contents, 'port: 1234')
48 self.assertRegexpMatches(contents, 'host: example.com')48 self.assertRegexpMatches(contents, 'host: example.com')
49 self.assertRegexpMatches(contents, 'domain: api.foo.com')49 self.assertRegexpMatches(contents, 'domain: api.foo.com')
5050
51 templating.render('test.conf', fn2, context,51 templating.render('test.conf', fn2.name, context,
52 templates_dir=TEMPLATES_DIR)52 templates_dir=TEMPLATES_DIR)
53 contents = open(fn2).read()53 contents = open(fn2.name).read()
54 self.assertRegexpMatches(contents, 'listen 80')54 self.assertRegexpMatches(contents, 'listen 80')
55 self.assertEqual(fchown.call_count, 2)55 self.assertEqual(fchown.call_count, 2)
56 self.assertEqual(mkdir.call_count, 2)56 self.assertEqual(mkdir.call_count, 2)
57 finally:
58 for fn in (fn1, fn2):
59 if os.path.exists(fn):
60 os.remove(fn)
6157
62 @mock.patch.object(templating.host.os, 'fchown')58 @mock.patch.object(templating.host.os, 'fchown')
63 @mock.patch.object(templating.host, 'log')59 @mock.patch.object(templating.host, 'log')
@@ -80,8 +76,10 @@
80 @mock.patch.object(templating, 'hookenv')76 @mock.patch.object(templating, 'hookenv')
81 @mock.patch('jinja2.Environment')77 @mock.patch('jinja2.Environment')
82 def test_load_error(self, Env, hookenv):78 def test_load_error(self, Env, hookenv):
83 Env().get_template.side_effect = jinja2.exceptions.TemplateNotFound('fake_cc.yml')79 Env().get_template.side_effect = jinja2.exceptions.TemplateNotFound(
80 'fake_cc.yml')
84 self.assertRaises(81 self.assertRaises(
85 jinja2.exceptions.TemplateNotFound, templating.render,82 jinja2.exceptions.TemplateNotFound, templating.render,
86 'fake.src', 'fake.tgt', {}, templates_dir='tmpl')83 'fake.src', 'fake.tgt', {}, templates_dir='tmpl')
87 hookenv.log.assert_called_once_with('Could not load template fake.src from tmpl.', level=hookenv.ERROR)84 hookenv.log.assert_called_once_with(
85 'Could not load template fake.src from tmpl.', level=hookenv.ERROR)

Subscribers

People subscribed via source and target branches