Merge lp:~fwereade/pyjuju/add-upstart-class into lp:pyjuju
- add-upstart-class
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Kapil Thangavelu | ||||
Approved revision: | 446 | ||||
Merged at revision: | 459 | ||||
Proposed branch: | lp:~fwereade/pyjuju/add-upstart-class | ||||
Merge into: | lp:pyjuju | ||||
Prerequisite: | lp:~fwereade/pyjuju/fix-charm-upgrade | ||||
Diff against target: |
427 lines (+375/-1) 7 files modified
juju/errors.py (+3/-0) juju/lib/tests/data/test_basic_install (+10/-0) juju/lib/tests/data/test_less_basic_install (+11/-0) juju/lib/tests/data/test_standard_install (+10/-0) juju/lib/tests/test_upstart.py (+229/-0) juju/lib/upstart.py (+105/-0) juju/tests/test_errors.py (+7/-1) |
||||
To merge this branch: | bzr merge lp:~fwereade/pyjuju/add-upstart-class | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Kapil Thangavelu (community) | Approve | ||
Jim Baker (community) | Approve | ||
Review via email: mp+85335@code.launchpad.net |
Commit message
Description of the change
This is just the class we will be using in lp:~fwereade/juju/upstartify-services, but extracted so as to make the diffs a little less daunting.
- 440. By William Reade
-
merge parent
- 441. By William Reade
-
merge parent
- 442. By William Reade
-
merge parent
- 443. By William Reade
-
merge parent
- 444. By William Reade
-
address review points
- 445. By William Reade
-
merge parent
- 446. By William Reade
-
merge parent
Kapil Thangavelu (hazmat) wrote : | # |
lgtm +1, some minors
[0]
# on class for ease of mocking
s/mocking/testing
[1]
+ self.conf = os.path.
pep8 cheese, double space after =
[2]
+ def set_start_
+ self._start_
this line is never tested, nor entirely clear on usage. There are several upstart gurus in the room if this needs clarification.
[3] more untested lines
+ self.root_path = root_path
its not clear why this mocked out as a class attribute, when the constructor takes the arg.
+ raise ServiceError(
+ raise ServiceError(
William Reade (fwereade) wrote : | # |
[0,1]
Cool, thanks
[2,3]
...gaah, missing @inlineCallbacks~s led to some untestedness; root_path tested explicitly. After consultation with bcsaller (original author), it's fine to drop the "start on filesystem or" complication, so it's gone.
- 447. By William Reade
-
address review points
- 448. By William Reade
-
merge parent
- 449. By William Reade
-
merge parent
- 450. By William Reade
-
merge parent
- 451. By William Reade
-
merge parent
- 452. By William Reade
-
merge parent
- 453. By William Reade
-
merge parent
- 454. By William Reade
-
merge parent
Preview Diff
1 | === modified file 'juju/errors.py' | |||
2 | --- juju/errors.py 2012-02-21 10:36:56 +0000 | |||
3 | +++ juju/errors.py 2012-02-21 10:36:57 +0000 | |||
4 | @@ -174,3 +174,6 @@ | |||
5 | 174 | self.user_policy, | 174 | self.user_policy, |
6 | 175 | self.provider_type, | 175 | self.provider_type, |
7 | 176 | ", ".join(self.provider_policies))) | 176 | ", ".join(self.provider_policies))) |
8 | 177 | |||
9 | 178 | class ServiceError(JujuError): | ||
10 | 179 | """Some problem with an upstart service""" | ||
11 | 177 | 180 | ||
12 | === added directory 'juju/lib/tests/data' | |||
13 | === added file 'juju/lib/tests/data/test_basic_install' | |||
14 | --- juju/lib/tests/data/test_basic_install 1970-01-01 00:00:00 +0000 | |||
15 | +++ juju/lib/tests/data/test_basic_install 2012-02-21 10:36:57 +0000 | |||
16 | @@ -0,0 +1,10 @@ | |||
17 | 1 | description "uninteresting service" | ||
18 | 2 | author "Juju Team <juju@lists.ubuntu.com>" | ||
19 | 3 | |||
20 | 4 | start on runlevel [2345] | ||
21 | 5 | stop on runlevel [!2345] | ||
22 | 6 | respawn | ||
23 | 7 | |||
24 | 8 | |||
25 | 9 | |||
26 | 10 | exec /bin/false | ||
27 | 0 | 11 | ||
28 | === added file 'juju/lib/tests/data/test_less_basic_install' | |||
29 | --- juju/lib/tests/data/test_less_basic_install 1970-01-01 00:00:00 +0000 | |||
30 | +++ juju/lib/tests/data/test_less_basic_install 2012-02-21 10:36:57 +0000 | |||
31 | @@ -0,0 +1,11 @@ | |||
32 | 1 | description "pew pew pew blam" | ||
33 | 2 | author "Juju Team <juju@lists.ubuntu.com>" | ||
34 | 3 | |||
35 | 4 | start on runlevel [2345] | ||
36 | 5 | stop on runlevel [!2345] | ||
37 | 6 | respawn | ||
38 | 7 | |||
39 | 8 | env FOO="bar baz qux" | ||
40 | 9 | env PEW="pew" | ||
41 | 10 | |||
42 | 11 | exec /bin/deathstar --ignore-ewoks endor | ||
43 | 0 | 12 | ||
44 | === added file 'juju/lib/tests/data/test_standard_install' | |||
45 | --- juju/lib/tests/data/test_standard_install 1970-01-01 00:00:00 +0000 | |||
46 | +++ juju/lib/tests/data/test_standard_install 2012-02-21 10:36:57 +0000 | |||
47 | @@ -0,0 +1,10 @@ | |||
48 | 1 | description "a wretched hive of scum and villainy" | ||
49 | 2 | author "Juju Team <juju@lists.ubuntu.com>" | ||
50 | 3 | |||
51 | 4 | start on runlevel [2345] | ||
52 | 5 | stop on runlevel [!2345] | ||
53 | 6 | respawn | ||
54 | 7 | |||
55 | 8 | env LIGHTSABER="civilised weapon" | ||
56 | 9 | |||
57 | 10 | exec /bin/imagination-failure --no-ideas | ||
58 | 0 | 11 | ||
59 | === added file 'juju/lib/tests/test_upstart.py' | |||
60 | --- juju/lib/tests/test_upstart.py 1970-01-01 00:00:00 +0000 | |||
61 | +++ juju/lib/tests/test_upstart.py 2012-02-21 10:36:57 +0000 | |||
62 | @@ -0,0 +1,229 @@ | |||
63 | 1 | import os | ||
64 | 2 | |||
65 | 3 | from twisted.internet.defer import inlineCallbacks, succeed | ||
66 | 4 | |||
67 | 5 | from juju.errors import ServiceError | ||
68 | 6 | from juju.lib.mocker import ANY | ||
69 | 7 | from juju.lib.testing import TestCase | ||
70 | 8 | from juju.lib.upstart import UpstartService | ||
71 | 9 | |||
72 | 10 | |||
73 | 11 | DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data") | ||
74 | 12 | |||
75 | 13 | |||
76 | 14 | class UpstartServiceTest(TestCase): | ||
77 | 15 | |||
78 | 16 | @inlineCallbacks | ||
79 | 17 | def setUp(self): | ||
80 | 18 | yield super(UpstartServiceTest, self).setUp() | ||
81 | 19 | self.root = self.makeDir() | ||
82 | 20 | init_dir = os.path.join(self.root, "etc", "init") | ||
83 | 21 | os.makedirs(init_dir) | ||
84 | 22 | self.conf = os.path.join(init_dir, "some-name.conf") | ||
85 | 23 | self.patch(UpstartService, "root_path", self.root) | ||
86 | 24 | self.service = UpstartService("some-name") | ||
87 | 25 | |||
88 | 26 | def setup_service(self): | ||
89 | 27 | self.service.set_description("a wretched hive of scum and villainy") | ||
90 | 28 | self.service.set_command("/bin/imagination-failure --no-ideas") | ||
91 | 29 | self.service.set_environ({"LIGHTSABER": "civilised weapon"}) | ||
92 | 30 | |||
93 | 31 | def setup_mock(self): | ||
94 | 32 | self.check_call = self.mocker.replace("subprocess.check_call") | ||
95 | 33 | self.getProcessOutput = self.mocker.replace( | ||
96 | 34 | "twisted.internet.utils.getProcessOutput") | ||
97 | 35 | |||
98 | 36 | def mock_status(self, result): | ||
99 | 37 | self.getProcessOutput("/sbin/status", ["some-name"]) | ||
100 | 38 | self.mocker.result(result) | ||
101 | 39 | |||
102 | 40 | def mock_call(self, args): | ||
103 | 41 | self.check_call(args) | ||
104 | 42 | self.mocker.result(0) | ||
105 | 43 | |||
106 | 44 | def mock_start(self): | ||
107 | 45 | self.mock_call(("/sbin/start", "some-name")) | ||
108 | 46 | |||
109 | 47 | def mock_stop(self): | ||
110 | 48 | self.mock_call(("/sbin/stop", "some-name")) | ||
111 | 49 | |||
112 | 50 | def write_dummy_conf(self): | ||
113 | 51 | with open(self.conf, "w") as f: | ||
114 | 52 | f.write("dummy") | ||
115 | 53 | |||
116 | 54 | def assert_dummy_conf(self): | ||
117 | 55 | with open(self.conf) as f: | ||
118 | 56 | self.assertEquals(f.read(), "dummy") | ||
119 | 57 | |||
120 | 58 | def assert_no_conf(self): | ||
121 | 59 | self.assertFalse(os.path.exists(self.conf)) | ||
122 | 60 | |||
123 | 61 | def assert_conf(self, name="test_standard_install"): | ||
124 | 62 | with open(os.path.join(DATA_DIR, name)) as expected: | ||
125 | 63 | with open(self.conf) as actual: | ||
126 | 64 | self.assertEquals(actual.read(), expected.read()) | ||
127 | 65 | |||
128 | 66 | def test_is_installed(self): | ||
129 | 67 | """Check is_installed depends on conf file existence""" | ||
130 | 68 | self.assertFalse(self.service.is_installed()) | ||
131 | 69 | self.write_dummy_conf() | ||
132 | 70 | self.assertTrue(self.service.is_installed()) | ||
133 | 71 | |||
134 | 72 | def test_root_path(self): | ||
135 | 73 | """ | ||
136 | 74 | Check is_installed still works when root_path specified explicitly | ||
137 | 75 | """ | ||
138 | 76 | self.patch(UpstartService, "root_path", "/BAD/PATH") | ||
139 | 77 | self.service = UpstartService("some-name", root_path=self.root) | ||
140 | 78 | self.setup_service() | ||
141 | 79 | |||
142 | 80 | self.assertFalse(self.service.is_installed()) | ||
143 | 81 | self.write_dummy_conf() | ||
144 | 82 | self.assertTrue(self.service.is_installed()) | ||
145 | 83 | |||
146 | 84 | @inlineCallbacks | ||
147 | 85 | def test_is_running(self): | ||
148 | 86 | """ | ||
149 | 87 | Check is_running interprets status output (when service is installed) | ||
150 | 88 | """ | ||
151 | 89 | self.setup_mock() | ||
152 | 90 | self.mock_status(succeed("blah stop/waiting blah")) | ||
153 | 91 | self.mock_status(succeed("blah blob/gibbering blah")) | ||
154 | 92 | self.mock_status(succeed("blah start/running blah")) | ||
155 | 93 | self.mocker.replay() | ||
156 | 94 | |||
157 | 95 | # Won't hit status; conf is not installed | ||
158 | 96 | self.assertFalse((yield self.service.is_running())) | ||
159 | 97 | self.write_dummy_conf() | ||
160 | 98 | |||
161 | 99 | # These 3 calls correspond to the 3 mock_status calls above | ||
162 | 100 | self.assertFalse((yield self.service.is_running())) | ||
163 | 101 | self.assertFalse((yield self.service.is_running())) | ||
164 | 102 | self.assertTrue((yield self.service.is_running())) | ||
165 | 103 | |||
166 | 104 | @inlineCallbacks | ||
167 | 105 | def test_basic_install(self): | ||
168 | 106 | """Check a simple UpstartService writes expected conf file""" | ||
169 | 107 | e = yield self.assertFailure(self.service.install(), ServiceError) | ||
170 | 108 | self.assertEquals(str(e), "Cannot render .conf: no description set") | ||
171 | 109 | self.service.set_description("uninteresting service") | ||
172 | 110 | e = yield self.assertFailure(self.service.install(), ServiceError) | ||
173 | 111 | self.assertEquals(str(e), "Cannot render .conf: no command set") | ||
174 | 112 | self.service.set_command("/bin/false") | ||
175 | 113 | yield self.service.install() | ||
176 | 114 | |||
177 | 115 | self.assert_conf("test_basic_install") | ||
178 | 116 | |||
179 | 117 | @inlineCallbacks | ||
180 | 118 | def test_less_basic_install(self): | ||
181 | 119 | """Check conf for a different UpstartService (which sets an env var)""" | ||
182 | 120 | self.service.set_description("pew pew pew blam") | ||
183 | 121 | self.service.set_command("/bin/deathstar --ignore-ewoks endor") | ||
184 | 122 | self.service.set_environ({"FOO": "bar baz qux", "PEW": "pew"}) | ||
185 | 123 | yield self.service.install() | ||
186 | 124 | |||
187 | 125 | self.assert_conf("test_less_basic_install") | ||
188 | 126 | |||
189 | 127 | def test_install_via_script(self): | ||
190 | 128 | """Check that the output-as-script form does the right thing""" | ||
191 | 129 | self.setup_service() | ||
192 | 130 | install, start = self.service.get_cloud_init_commands() | ||
193 | 131 | |||
194 | 132 | os.system(install) | ||
195 | 133 | self.assert_conf() | ||
196 | 134 | self.assertEquals(start, "/sbin/start some-name") | ||
197 | 135 | |||
198 | 136 | @inlineCallbacks | ||
199 | 137 | def test_start_not_installed(self): | ||
200 | 138 | """Check that .start() also installs if necessary""" | ||
201 | 139 | self.setup_mock() | ||
202 | 140 | self.mock_status(succeed("blah stop/waiting blah")) | ||
203 | 141 | self.mock_start() | ||
204 | 142 | self.mocker.replay() | ||
205 | 143 | |||
206 | 144 | self.setup_service() | ||
207 | 145 | yield self.service.start() | ||
208 | 146 | self.assert_conf() | ||
209 | 147 | |||
210 | 148 | @inlineCallbacks | ||
211 | 149 | def test_start_not_started(self): | ||
212 | 150 | """Check that .start() starts if stopped""" | ||
213 | 151 | self.write_dummy_conf() | ||
214 | 152 | self.setup_mock() | ||
215 | 153 | self.mock_status(succeed("blah stop/waiting blah")) | ||
216 | 154 | self.mock_start() | ||
217 | 155 | self.mocker.replay() | ||
218 | 156 | |||
219 | 157 | self.setup_service() | ||
220 | 158 | yield self.service.start() | ||
221 | 159 | self.assert_dummy_conf() | ||
222 | 160 | |||
223 | 161 | @inlineCallbacks | ||
224 | 162 | def test_start_started(self): | ||
225 | 163 | """Check that .start() does nothing if already running""" | ||
226 | 164 | self.write_dummy_conf() | ||
227 | 165 | self.setup_mock() | ||
228 | 166 | self.mock_status(succeed("blah start/running blah")) | ||
229 | 167 | self.mocker.replay() | ||
230 | 168 | |||
231 | 169 | self.setup_service() | ||
232 | 170 | yield self.service.start() | ||
233 | 171 | self.assert_dummy_conf() | ||
234 | 172 | |||
235 | 173 | @inlineCallbacks | ||
236 | 174 | def test_destroy_not_installed(self): | ||
237 | 175 | """Check .destroy() does nothing if not installed""" | ||
238 | 176 | yield self.service.destroy() | ||
239 | 177 | self.assert_no_conf() | ||
240 | 178 | |||
241 | 179 | @inlineCallbacks | ||
242 | 180 | def test_destroy_not_started(self): | ||
243 | 181 | """Check .destroy just deletes conf if not running""" | ||
244 | 182 | self.write_dummy_conf() | ||
245 | 183 | self.setup_mock() | ||
246 | 184 | self.mock_status(succeed("blah stop/waiting blah")) | ||
247 | 185 | self.mocker.replay() | ||
248 | 186 | |||
249 | 187 | yield self.service.destroy() | ||
250 | 188 | self.assert_no_conf() | ||
251 | 189 | |||
252 | 190 | @inlineCallbacks | ||
253 | 191 | def test_destroy_started(self): | ||
254 | 192 | """Check .destroy() stops running service and deletes conf file""" | ||
255 | 193 | self.write_dummy_conf() | ||
256 | 194 | self.setup_mock() | ||
257 | 195 | self.mock_status(succeed("blah start/running blah")) | ||
258 | 196 | self.mock_stop() | ||
259 | 197 | self.mocker.replay() | ||
260 | 198 | |||
261 | 199 | yield self.service.destroy() | ||
262 | 200 | self.assert_no_conf() | ||
263 | 201 | |||
264 | 202 | @inlineCallbacks | ||
265 | 203 | def test_use_sudo(self): | ||
266 | 204 | """Check that expected commands are generated when use_sudo is set""" | ||
267 | 205 | self.setup_mock() | ||
268 | 206 | self.service = UpstartService("some-name", use_sudo=True) | ||
269 | 207 | self.setup_service() | ||
270 | 208 | |||
271 | 209 | def verify_cp(args): | ||
272 | 210 | sudo, cp, src, dst = args | ||
273 | 211 | self.assertEquals(sudo, "sudo") | ||
274 | 212 | self.assertEquals(cp, "cp") | ||
275 | 213 | with open(os.path.join(DATA_DIR, "test_standard_install")) as exp: | ||
276 | 214 | with open(src) as actual: | ||
277 | 215 | self.assertEquals(actual.read(), exp.read()) | ||
278 | 216 | self.assertEquals(dst, self.conf) | ||
279 | 217 | self.write_dummy_conf() | ||
280 | 218 | self.check_call(ANY) | ||
281 | 219 | self.mocker.call(verify_cp) | ||
282 | 220 | self.mock_call(("sudo", "chmod", "644", self.conf)) | ||
283 | 221 | self.mock_status(succeed("blah stop/waiting blah")) | ||
284 | 222 | self.mock_call(("sudo", "/sbin/start", "some-name")) | ||
285 | 223 | self.mock_status(succeed("blah start/running blah")) | ||
286 | 224 | self.mock_call(("sudo", "/sbin/stop", "some-name")) | ||
287 | 225 | self.mock_call(("sudo", "rm", self.conf)) | ||
288 | 226 | |||
289 | 227 | self.mocker.replay() | ||
290 | 228 | yield self.service.start() | ||
291 | 229 | yield self.service.destroy() | ||
292 | 0 | 230 | ||
293 | === added file 'juju/lib/upstart.py' | |||
294 | --- juju/lib/upstart.py 1970-01-01 00:00:00 +0000 | |||
295 | +++ juju/lib/upstart.py 2012-02-21 10:36:57 +0000 | |||
296 | @@ -0,0 +1,105 @@ | |||
297 | 1 | import os | ||
298 | 2 | import subprocess | ||
299 | 3 | from tempfile import NamedTemporaryFile | ||
300 | 4 | |||
301 | 5 | from twisted.internet.defer import inlineCallbacks, returnValue | ||
302 | 6 | from twisted.internet.threads import deferToThread | ||
303 | 7 | from twisted.internet.utils import getProcessOutput | ||
304 | 8 | |||
305 | 9 | from juju.errors import ServiceError | ||
306 | 10 | |||
307 | 11 | |||
308 | 12 | _UPSTART_TEMPLATE = """\ | ||
309 | 13 | description "%s" | ||
310 | 14 | author "Juju Team <juju@lists.ubuntu.com>" | ||
311 | 15 | |||
312 | 16 | start on runlevel [2345] | ||
313 | 17 | stop on runlevel [!2345] | ||
314 | 18 | respawn | ||
315 | 19 | |||
316 | 20 | %s | ||
317 | 21 | |||
318 | 22 | exec %s | ||
319 | 23 | """ | ||
320 | 24 | |||
321 | 25 | |||
322 | 26 | class UpstartService(object): | ||
323 | 27 | |||
324 | 28 | # on class for ease of testing | ||
325 | 29 | root_path = "/" | ||
326 | 30 | |||
327 | 31 | def __init__(self, name, root_path=None, use_sudo=False): | ||
328 | 32 | self._name = name | ||
329 | 33 | self._use_sudo = use_sudo | ||
330 | 34 | if root_path is not None: | ||
331 | 35 | self.root_path = root_path | ||
332 | 36 | self._description = None | ||
333 | 37 | self._environ = {} | ||
334 | 38 | self._command = None | ||
335 | 39 | |||
336 | 40 | def set_description(self, description): | ||
337 | 41 | self._description = description | ||
338 | 42 | |||
339 | 43 | def set_environ(self, environ): | ||
340 | 44 | self._environ = environ | ||
341 | 45 | |||
342 | 46 | def set_command(self, command): | ||
343 | 47 | self._command = command | ||
344 | 48 | |||
345 | 49 | def _render(self): | ||
346 | 50 | if self._description is None: | ||
347 | 51 | raise ServiceError("Cannot render .conf: no description set") | ||
348 | 52 | if self._command is None: | ||
349 | 53 | raise ServiceError("Cannot render .conf: no command set") | ||
350 | 54 | return _UPSTART_TEMPLATE % ( | ||
351 | 55 | self._description, | ||
352 | 56 | "\n".join('env %s="%s"' % kv | ||
353 | 57 | for kv in sorted(self._environ.items())), | ||
354 | 58 | self._command) | ||
355 | 59 | |||
356 | 60 | @property | ||
357 | 61 | def _conf_path(self): | ||
358 | 62 | return os.path.join( | ||
359 | 63 | self.root_path, "etc", "init", "%s.conf" % self._name) | ||
360 | 64 | |||
361 | 65 | def _call(self, *args): | ||
362 | 66 | if self._use_sudo: | ||
363 | 67 | args = ("sudo",) + args | ||
364 | 68 | return deferToThread(subprocess.check_call, args) | ||
365 | 69 | |||
366 | 70 | def get_cloud_init_commands(self): | ||
367 | 71 | return ["cat >> %s <<EOF\n%sEOF\n" % (self._conf_path, self._render()), | ||
368 | 72 | "/sbin/start %s" % self._name] | ||
369 | 73 | |||
370 | 74 | @inlineCallbacks | ||
371 | 75 | def install(self): | ||
372 | 76 | with NamedTemporaryFile() as f: | ||
373 | 77 | f.write(self._render()) | ||
374 | 78 | f.flush() | ||
375 | 79 | yield self._call("cp", f.name, self._conf_path) | ||
376 | 80 | yield self._call("chmod", "644", self._conf_path) | ||
377 | 81 | |||
378 | 82 | @inlineCallbacks | ||
379 | 83 | def start(self): | ||
380 | 84 | if not self.is_installed(): | ||
381 | 85 | yield self.install() | ||
382 | 86 | if (yield self.is_running()): | ||
383 | 87 | return | ||
384 | 88 | yield self._call("/sbin/start", self._name) | ||
385 | 89 | |||
386 | 90 | @inlineCallbacks | ||
387 | 91 | def destroy(self): | ||
388 | 92 | if (yield self.is_running()): | ||
389 | 93 | yield self._call("/sbin/stop", self._name) | ||
390 | 94 | if self.is_installed(): | ||
391 | 95 | yield self._call("rm", self._conf_path) | ||
392 | 96 | |||
393 | 97 | @inlineCallbacks | ||
394 | 98 | def is_running(self): | ||
395 | 99 | if not self.is_installed(): | ||
396 | 100 | returnValue(False) | ||
397 | 101 | status = yield getProcessOutput("/sbin/status", [self._name]) | ||
398 | 102 | returnValue("start/running" in status) | ||
399 | 103 | |||
400 | 104 | def is_installed(self): | ||
401 | 105 | return os.path.exists(self._conf_path) | ||
402 | 0 | 106 | ||
403 | === modified file 'juju/tests/test_errors.py' | |||
404 | --- juju/tests/test_errors.py 2012-02-21 10:36:56 +0000 | |||
405 | +++ juju/tests/test_errors.py 2012-02-21 10:36:57 +0000 | |||
406 | @@ -3,7 +3,8 @@ | |||
407 | 3 | CharmInvocationError, CharmUpgradeError, NoConnection, InvalidHost, | 3 | CharmInvocationError, CharmUpgradeError, NoConnection, InvalidHost, |
408 | 4 | InvalidUser, ProviderError, CloudInitError, ProviderInteractionError, | 4 | InvalidUser, ProviderError, CloudInitError, ProviderInteractionError, |
409 | 5 | CannotTerminateMachine, MachinesNotFound, EnvironmentPending, | 5 | CannotTerminateMachine, MachinesNotFound, EnvironmentPending, |
411 | 6 | EnvironmentNotFound, IncompatibleVersion, InvalidPlacementPolicy) | 6 | EnvironmentNotFound, IncompatibleVersion, InvalidPlacementPolicy, |
412 | 7 | ServiceError) | ||
413 | 7 | 8 | ||
414 | 8 | from juju.lib.testing import TestCase | 9 | from juju.lib.testing import TestCase |
415 | 9 | 10 | ||
416 | @@ -111,6 +112,11 @@ | |||
417 | 111 | ("Unsupported placement policy: 'x' for provider: 'foobar', " | 112 | ("Unsupported placement policy: 'x' for provider: 'foobar', " |
418 | 112 | "supported policies a, b, c")) | 113 | "supported policies a, b, c")) |
419 | 113 | 114 | ||
420 | 115 | def test_ServiceError(self): | ||
421 | 116 | error = ServiceError("blah") | ||
422 | 117 | self.assertEquals(str(error), "blah") | ||
423 | 118 | self.assertIsJujuError(error) | ||
424 | 119 | |||
425 | 114 | def test_CharmError(self): | 120 | def test_CharmError(self): |
426 | 115 | error = CharmError("/foo/bar", "blah blah") | 121 | error = CharmError("/foo/bar", "blah blah") |
427 | 116 | self.assertIsJujuError(error) | 122 | self.assertIsJujuError(error) |
+1. Just minors here:
[1]
+description "blah" n-failure --no-ideas
+author "Juju Team <email address hidden>"
+
+start on runlevel [2345]
+stop on runlevel [!2345]
+respawn
+
+env BLAH="blah blah"
+
+exec /bin/imaginatio
Having a generating family of dummy data (eg your Star Wars theme or really whatever)
can be helpful for seeing the expected patterns. "blah blah" is unfortunately
just too much noise when looking at these tests.
The other thing is that putting these files inline might work much better, to avoid one
more place to look to see what the expected output should be.
[2]
+class UpstartServiceT est(TestCase) : join(self. root, "etc", "init") init_dir) join(init_ dir, "some-name.conf") UpstartService, "root_path", self.root) "some-name" )
+
+ def setUp(self):
+ self.root = self.makeDir()
+ init_dir = os.path.
+ os.makedirs(
+ self.conf = os.path.
+ self.patch(
+ self.service = UpstartService(
It's unlikely this will be mixed in with other tests, but it's likely best to preserve
the existing cooperative super inheritance support, including returning a Deferred
(usually by inlineCallbacks).
[3]
+ def test_is_ installed( self):
Doc strings for tests!
+ self.assertFals e(self. service. is_installed( )) dummy_conf( ) (self.service. is_installed( ))
+ self.write_
+ self.assertTrue
+
[4]
+ @inlineCallbacks running( self):
+ def test_is_
Docs/comments especially here. Best to be explicit about how the mocks are
setting things up, especially when there's a sequence of multiple calls as seen here.