Merge lp:~daniel-thewatkins/cloud-init/fix-multi_log into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Dan Watkins on 2015-02-11
Status: Merged
Merged at revision: 1055
Proposed branch: lp:~daniel-thewatkins/cloud-init/fix-multi_log
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 173 lines (+91/-10)
4 files modified
cloudinit/util.py (+3/-3)
tests/unittests/helpers.py (+13/-0)
tests/unittests/test_util.py (+73/-5)
tox.ini (+2/-2)
To merge this branch: bzr merge lp:~daniel-thewatkins/cloud-init/fix-multi_log
Reviewer Review Type Date Requested Status
Scott Moser 2015-02-11 Pending
Review via email: mp+249308@code.launchpad.net

Description of the Change

This fixes the encoding problem that was causing warnings in cloud-init logs.

It also introduces unit tests for util.multi_log (the problematic part of the code), fixes a test that was failing, and makes it easier to run a single test file using tox.

To post a comment you must log in.
1060. By Dan Watkins on 2015-02-11

Open /dev/console in text mode (so we don't have to encode strings to write them).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cloudinit/util.py'
2--- cloudinit/util.py 2015-02-11 01:50:23 +0000
3+++ cloudinit/util.py 2015-02-11 17:25:15 +0000
4@@ -404,7 +404,7 @@
5 if console:
6 conpath = "/dev/console"
7 if os.path.exists(conpath):
8- with open(conpath, 'wb') as wfh:
9+ with open(conpath, 'w') as wfh:
10 wfh.write(text)
11 wfh.flush()
12 else:
13@@ -1481,8 +1481,8 @@
14 device, mtype, exc)
15 pass
16 if not mountpoint:
17- raise MountFailedError("Failed mounting %s to %s due to: %s" %
18- (device, tmpd, exc))
19+ raise MountFailedError(
20+ "Failed mounting %s to %s".format(device, tmpd))
21
22 # Be nice and ensure it ends with a slash
23 if not mountpoint.endswith("/"):
24
25=== modified file 'tests/unittests/helpers.py'
26--- tests/unittests/helpers.py 2015-02-10 20:32:32 +0000
27+++ tests/unittests/helpers.py 2015-02-11 17:25:15 +0000
28@@ -254,6 +254,19 @@
29 self.patched_funcs.enter_context(
30 mock.patch.object(mod, f, trap_func))
31
32+ def patchOpen(self, new_root):
33+ trap_func = retarget_many_wrapper(new_root, 1, open)
34+ name = 'builtins.open' if PY3 else '__builtin__.open'
35+ self.patched_funcs.enter_context(mock.patch(name, trap_func))
36+
37+ def patchStdoutAndStderr(self, stdout=None, stderr=None):
38+ if stdout is not None:
39+ self.patched_funcs.enter_context(
40+ mock.patch.object(sys, 'stdout', stdout))
41+ if stderr is not None:
42+ self.patched_funcs.enter_context(
43+ mock.patch.object(sys, 'stderr', stderr))
44+
45
46 class HttprettyTestCase(TestCase):
47 # necessary as http_proxy gets in the way of httpretty
48
49=== modified file 'tests/unittests/test_util.py'
50--- tests/unittests/test_util.py 2015-01-27 01:02:31 +0000
51+++ tests/unittests/test_util.py 2015-02-11 17:25:15 +0000
52@@ -1,21 +1,22 @@
53 from __future__ import print_function
54
55+import logging
56 import os
57+import shutil
58 import stat
59+import tempfile
60+
61+import six
62 import yaml
63-import shutil
64-import tempfile
65
66+from cloudinit import importer, util
67 from . import helpers
68-import six
69
70 try:
71 from unittest import mock
72 except ImportError:
73 import mock
74
75-from cloudinit import importer
76-from cloudinit import util
77
78
79 class FakeSelinux(object):
80@@ -377,4 +378,71 @@
81 self.assertFalse(None, util.read_dmi_data("key"))
82
83
84+class TestMultiLog(helpers.FilesystemMockingTestCase):
85+
86+ def _createConsole(self, root):
87+ os.mkdir(os.path.join(root, 'dev'))
88+ open(os.path.join(root, 'dev', 'console'), 'a').close()
89+
90+ def setUp(self):
91+ super(TestMultiLog, self).setUp()
92+ self.root = tempfile.mkdtemp()
93+ self.addCleanup(shutil.rmtree, self.root)
94+ self.patchOS(self.root)
95+ self.patchUtils(self.root)
96+ self.patchOpen(self.root)
97+ self.stdout = six.StringIO()
98+ self.stderr = six.StringIO()
99+ self.patchStdoutAndStderr(self.stdout, self.stderr)
100+
101+ def test_stderr_used_by_default(self):
102+ logged_string = 'test stderr output'
103+ util.multi_log(logged_string)
104+ self.assertEqual(logged_string, self.stderr.getvalue())
105+
106+ def test_stderr_not_used_if_false(self):
107+ util.multi_log('should not see this', stderr=False)
108+ self.assertEqual('', self.stderr.getvalue())
109+
110+ def test_logs_go_to_console_by_default(self):
111+ self._createConsole(self.root)
112+ logged_string = 'something very important'
113+ util.multi_log(logged_string)
114+ self.assertEqual(logged_string, open('/dev/console').read())
115+
116+ def test_logs_dont_go_to_stdout_if_console_exists(self):
117+ self._createConsole(self.root)
118+ util.multi_log('something')
119+ self.assertEqual('', self.stdout.getvalue())
120+
121+ def test_logs_go_to_stdout_if_console_does_not_exist(self):
122+ logged_string = 'something very important'
123+ util.multi_log(logged_string)
124+ self.assertEqual(logged_string, self.stdout.getvalue())
125+
126+ def test_logs_go_to_log_if_given(self):
127+ log = mock.MagicMock()
128+ logged_string = 'something very important'
129+ util.multi_log(logged_string, log=log)
130+ self.assertEqual([((mock.ANY, logged_string), {})],
131+ log.log.call_args_list)
132+
133+ def test_newlines_stripped_from_log_call(self):
134+ log = mock.MagicMock()
135+ expected_string = 'something very important'
136+ util.multi_log('{0}\n'.format(expected_string), log=log)
137+ self.assertEqual((mock.ANY, expected_string), log.log.call_args[0])
138+
139+ def test_log_level_defaults_to_debug(self):
140+ log = mock.MagicMock()
141+ util.multi_log('message', log=log)
142+ self.assertEqual((logging.DEBUG, mock.ANY), log.log.call_args[0])
143+
144+ def test_given_log_level_used(self):
145+ log = mock.MagicMock()
146+ log_level = mock.Mock()
147+ util.multi_log('message', log=log, log_level=log_level)
148+ self.assertEqual((log_level, mock.ANY), log.log.call_args[0])
149+
150+
151 # vi: ts=4 expandtab
152
153=== modified file 'tox.ini'
154--- tox.ini 2015-01-27 01:02:31 +0000
155+++ tox.ini 2015-02-11 17:25:15 +0000
156@@ -3,7 +3,7 @@
157 recreate = True
158
159 [testenv]
160-commands = python -m nose tests
161+commands = python -m nose {posargs:tests}
162 deps =
163 contextlib2
164 httpretty>=0.7.1
165@@ -13,7 +13,7 @@
166 pyflakes
167
168 [testenv:py26]
169-commands = nosetests tests
170+commands = nosetests {posargs:tests}
171 deps =
172 contextlib2
173 httpretty>=0.7.1