Merge lp:~hduran-8/juju-ci-tools/add_status_ci_tests into lp:juju-ci-tools
- add_status_ci_tests
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1024 |
Proposed branch: | lp:~hduran-8/juju-ci-tools/add_status_ci_tests |
Merge into: | lp:juju-ci-tools |
Prerequisite: | lp:~hduran-8/juju-ci-tools/repository |
Diff against target: |
1142 lines (+1084/-5) 6 files modified
assess_status_output.py (+154/-0) base_asses.py (+58/-0) jujupy.py (+7/-5) status.py (+385/-0) template_assess.py.tmpl (+54/-0) test_assess_status_output.py (+426/-0) |
To merge this branch: | bzr merge lp:~hduran-8/juju-ci-tools/add_status_ci_tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey (community) | code | Approve | |
Review via email: mp+262150@code.launchpad.net |
Commit message
Description of the change
Added Status CI tests.
CI tests where added to ensure that the format of various
formats of status output remain the same through changes.
The formats covered are Tabular, yaml and json and the
testing is done by getting the same status in all formats
and ensuring all expected data is there and that there is
no unexpected data not covered by testing.
- 981. By Horacio Durán
-
Merge with master and resolve conflict in jujupy.py
- 982. By Horacio Durán
-
Proper python formatting.
- 983. By Horacio Durán
-
Fixed a couple of bugs on parse args.
Curtis Hovey (sinzui) wrote : | # |
Hi Horatio. Sorry for taking so long to review this branch. We normally keep branches to 400 lines.
I think there is too much duplication with other feature in juju-ci-tools and Python itself. My comments are inline, but in general, there is already a helper to bring up and tear down an env, you should use it because CI needs more control over what is under test then your main() provides. We do not want to write assertions when Python provides them for free. I think all the calls to assert can be written in fewer lines using unittest.
https:/
- 984. By Horacio Durán
-
Addressed curtis observations.
I refactored most of my tests to suit CI team
style and requirements as per Curtis comments.
Horacio Durán (hduran-8) wrote : | # |
Hey the only thing I dont agree with is the issue about the literals for status output, I added a comment there, I dont really think we should use equivalent since the literal status output is what we are testing, I added more detail in the comment.
- 985. By Horacio Durán
-
Addressed extra comments from Curtis.
Curtis Hovey (sinzui) wrote : | # |
Thank you Horatio.
I will merge your branch. in a few minutes. I will then start a new branch to reconcile the duplication of of the argument parser with two other parsers. I want to update the template you provide to provide the same maintained infrastructure as other jobs so that test authors only need to add args that the test will need. The env, and the collection of logs, and clean up or resources will be done by the infrastructure.
- 986. By Horacio Durán
-
Merge master
- 987. By Horacio Durán
-
Added missing return
Preview Diff
1 | === added file 'assess_status_output.py' |
2 | --- assess_status_output.py 1970-01-01 00:00:00 +0000 |
3 | +++ assess_status_output.py 2015-07-13 18:48:12 +0000 |
4 | @@ -0,0 +1,154 @@ |
5 | +#!/usr/bin/python |
6 | +from __future__ import print_function |
7 | + |
8 | +__metaclass__ = type |
9 | + |
10 | +from base_asses import get_base_parser |
11 | +from status import StatusTester |
12 | +from jujupy import ( |
13 | + make_client, |
14 | +) |
15 | +from deploy_stack import ( |
16 | + boot_context, |
17 | + prepare_environment, |
18 | +) |
19 | + |
20 | + |
21 | +def run_complete_status(client, status): |
22 | + """run the complete set of tests possible for any StatusParser. |
23 | + |
24 | + :param client: python juju client. |
25 | + :type client: jujupy.EnvJujuClient |
26 | + :param status: a BaseStatusParser |
27 | + :type status: BaseStatusParser |
28 | + """ |
29 | + status.s.assert_machines_len(2) |
30 | + status.s.assert_machines_ids(("0", "1")) |
31 | + juju_status = client.get_status() |
32 | + for name, machine in juju_status.iter_machines(False): |
33 | + status.s.assert_machine_agent_state(name, "started") |
34 | + status.s.assert_machine_agent_version(name, |
35 | + machine.get("agent-version")) |
36 | + status.s.assert_machine_dns_name(name, machine.get("dns-name")) |
37 | + status.s.assert_machine_instance_id(name, |
38 | + machine.get("instance-id")) |
39 | + status.s.assert_machine_series(name, machine.get("series")) |
40 | + status.s.assert_machine_hardware(name, machine.get("hardware")) |
41 | + state_server = machine.get("state-server-member-status", None) |
42 | + if state_server: |
43 | + status.s.assert_machine_member_status(name, "has-vote") |
44 | + |
45 | + status.s.assert_service_charm("statusstresser", |
46 | + "local:trusty/statusstresser-1") |
47 | + status.s.assert_service_exposed("statusstresser", False) |
48 | + status.s.assert_service_service_status("statusstresser", |
49 | + {"current": "active", |
50 | + "message": "called in " |
51 | + "config-changed hook"}) |
52 | + status.s.assert_unit_workload_status("statusstresser/0", |
53 | + {"current": "active", |
54 | + "message": "called in " |
55 | + "config-changed hook"}) |
56 | + status.s.assert_unit_agent_status("statusstresser/0", |
57 | + {"current": "idle", "message": ""}) |
58 | + status.s.assert_unit_agent_state("statusstresser/0", "started") |
59 | + agent_versions = juju_status.get_agent_versions() |
60 | + for version in agent_versions: |
61 | + for item in agent_versions[version]: |
62 | + if not item.isdigit(): |
63 | + status.s.assert_unit_agent_version(item, version) |
64 | + status.s.assert_unit_machine("statusstresser/0", "1") |
65 | + |
66 | + |
67 | +def run_reduced_status(client, status): |
68 | + """run a subset of the status asserts. |
69 | + |
70 | + run a reduced set of tests for a StatusParser, this is useful for |
71 | + status outputs such as Tabular that hold less information. |
72 | + |
73 | + :param client: python juju client. |
74 | + :type client: jujupy.EnvJujuClient |
75 | + :param status: a BaseStatusParser |
76 | + :type status: BaseStatusParser |
77 | + """ |
78 | + status.s.assert_machines_len(2) |
79 | + status.s.assert_machines_ids(("0", "1")) |
80 | + juju_status = client.get_status() |
81 | + for name, machine in juju_status.iter_machines(False): |
82 | + status.s.assert_machine_agent_state(name, "started") |
83 | + status.s.assert_machine_agent_version(name, |
84 | + machine.get("agent-version")) |
85 | + status.s.assert_machine_dns_name(name, machine.get("dns-name")) |
86 | + status.s.assert_machine_instance_id(name, machine.get("instance-id")) |
87 | + status.s.assert_machine_series(name, machine.get("series")) |
88 | + status.s.assert_machine_hardware(name, machine.get("hardware")) |
89 | + |
90 | + status.s.assert_service_charm("statusstresser", |
91 | + "local:trusty/statusstresser-1") |
92 | + status.s.assert_service_exposed("statusstresser", False) |
93 | + status.s.assert_service_service_status("statusstresser", |
94 | + {"current": "active", |
95 | + "message": ""}) |
96 | + status.s.assert_unit_workload_status("statusstresser/0", |
97 | + {"current": "active", |
98 | + "message": "called in " |
99 | + "config-changed hook"}) |
100 | + status.s.assert_unit_agent_status("statusstresser/0", |
101 | + {"current": "idle", "message": ""}) |
102 | + status.s.assert_unit_machine("statusstresser/0", "1") |
103 | + |
104 | + |
105 | +def test_status_set_on_install(client): |
106 | + """Test the status after install. |
107 | + |
108 | + Test that status set is proplerly called during install and |
109 | + that all formats are returning proper information. |
110 | + |
111 | + :param client: python juju client. |
112 | + :type client: jujupy.EnvJujuClient |
113 | + """ |
114 | + status = StatusTester.from_text(client.get_status(60, True, |
115 | + "--format=yaml"), |
116 | + "yaml") |
117 | + run_complete_status(client, status) |
118 | + status = StatusTester.from_text(client.get_status(60, True, |
119 | + "--format=json"), |
120 | + "json") |
121 | + run_complete_status(client, status) |
122 | + status = StatusTester.from_text(client.get_status(60, True, |
123 | + "--format=tabular"), |
124 | + "tabular") |
125 | + run_reduced_status(client, status) |
126 | + |
127 | + |
128 | +def parse_args(): |
129 | + """Parse all arguments.""" |
130 | + parser = get_base_parser('Test status outputs') |
131 | + return parser.parse_args() |
132 | + |
133 | + |
134 | +def main(): |
135 | + args = parse_args() |
136 | + log_dir = args.logs |
137 | + |
138 | + client = make_client( |
139 | + args.juju_path, args.debug, args.env, args.temp_env_name) |
140 | + # client.destroy_environment() |
141 | + series = args.series |
142 | + if series is None: |
143 | + series = 'precise' |
144 | + with boot_context(args.job_name, client, args.bootstrap_host, |
145 | + args.machine, series, args.agent_url, args.agent_stream, |
146 | + log_dir, args.keep_env, args.upload_tools): |
147 | + prepare_environment( |
148 | + client, already_bootstrapped=True, machines=args.machine) |
149 | + |
150 | + client.get_status(60) |
151 | + client.juju("deploy", ('local:trusty/statusstresser',)) |
152 | + client.wait_for_started() |
153 | + |
154 | + test_status_set_on_install(client) |
155 | + |
156 | + |
157 | +if __name__ == '__main__': |
158 | + main() |
159 | |
160 | === added file 'base_asses.py' |
161 | --- base_asses.py 1970-01-01 00:00:00 +0000 |
162 | +++ base_asses.py 2015-07-13 18:48:12 +0000 |
163 | @@ -0,0 +1,58 @@ |
164 | +from argparse import ArgumentParser |
165 | +from deploy_stack import add_juju_args, add_output_args |
166 | +from unittest import FunctionTestCase |
167 | + |
168 | + |
169 | +def get_base_parser(description=""): |
170 | + """Return a parser with the base arguments required |
171 | + for any asses_ test. |
172 | + |
173 | + :param description: description for argument parser. |
174 | + :type description: str |
175 | + :returns: argument parser with base fields |
176 | + :rtype: ArgumentParser |
177 | + """ |
178 | + parser = ArgumentParser(description) |
179 | + positional_args = [ |
180 | + ('env', 'The juju environment to base the temp test environment on.'), |
181 | + ('juju_bin', 'Full path to the Juju binary.'), |
182 | + ('logs', 'A directory in which to store logs.'), |
183 | + ('temp_env_name', 'A temporary test environment name.'), |
184 | + ] |
185 | + for p_arg in positional_args: |
186 | + name, help_txt = p_arg |
187 | + parser.add_argument(name, help=help_txt) |
188 | + |
189 | + parser.add_argument( |
190 | + 'juju_path', help='Directory your juju binary lives in.') |
191 | + parser.add_argument( |
192 | + 'job_name', help='An arbitrary name for this job.') |
193 | + |
194 | + add_juju_args(parser) |
195 | + add_output_args(parser) |
196 | + |
197 | + parser.add_argument('--upgrade', action="store_true", default=False, |
198 | + help='Perform an upgrade test.') |
199 | + parser.add_argument('--bootstrap-host', |
200 | + help='The host to use for bootstrap.', default=None) |
201 | + parser.add_argument('--machine', help='A machine to add or when used with ' |
202 | + 'KVM based MaaS, a KVM image to start.', |
203 | + action='append', default=[]) |
204 | + parser.add_argument('--keep-env', action='store_true', default=False, |
205 | + help='Keep the Juju environment after the test' |
206 | + ' completes.') |
207 | + parser.add_argument( |
208 | + '--upload-tools', action='store_true', default=False, |
209 | + help='upload local version of tools before bootstrapping') |
210 | + return parser |
211 | + |
212 | + |
213 | +def toUnitTest(testCase): |
214 | + def unitTestify(*args, **kwargs): |
215 | + tc = FunctionTestCase(testCase) |
216 | + largs = list(args) |
217 | + largs.insert(1, tc) |
218 | + args = tuple(largs) |
219 | + return testCase(*args, **kwargs) |
220 | + |
221 | + return unitTestify |
222 | |
223 | === modified file 'jujupy.py' |
224 | --- jujupy.py 2015-06-16 20:18:37 +0000 |
225 | +++ jujupy.py 2015-07-13 18:48:12 +0000 |
226 | @@ -124,9 +124,9 @@ |
227 | return self.get_env_client(environment).get_juju_output( |
228 | command, *args, **kwargs) |
229 | |
230 | - def get_status(self, environment, timeout=60): |
231 | + def get_status(self, environment, timeout=60, raw=False, *args): |
232 | """Get the current status as a dict.""" |
233 | - return self.get_env_client(environment).get_status(timeout) |
234 | + return self.get_env_client(environment).get_status(timeout, raw, *args) |
235 | |
236 | def get_env_option(self, environment, option): |
237 | """Return the value of the environment's configured option.""" |
238 | @@ -297,10 +297,12 @@ |
239 | print('!!! ' + e.stderr) |
240 | raise |
241 | |
242 | - def get_status(self, timeout=60): |
243 | + def get_status(self, timeout=60, raw=False, *args): |
244 | """Get the current status as a dict.""" |
245 | for ignored in until_timeout(timeout): |
246 | try: |
247 | + if raw: |
248 | + return self.get_juju_output('status', *args) |
249 | return Status.from_text(self.get_juju_output('status')) |
250 | except subprocess.CalledProcessError as e: |
251 | pass |
252 | @@ -929,8 +931,8 @@ |
253 | def juju(self, command, *args): |
254 | return self.client.juju(self, command, args) |
255 | |
256 | - def get_status(self, timeout=60): |
257 | - return self.client.get_status(self, timeout) |
258 | + def get_status(self, timeout=60, raw=False, *args): |
259 | + return self.client.get_status(self, timeout, raw, *args) |
260 | |
261 | def wait_for_deploy_started(self, service_count=1, timeout=1200): |
262 | """Wait until service_count services are 'started'. |
263 | |
264 | === added file 'status.py' |
265 | --- status.py 1970-01-01 00:00:00 +0000 |
266 | +++ status.py 2015-07-13 18:48:12 +0000 |
267 | @@ -0,0 +1,385 @@ |
268 | +from __future__ import print_function |
269 | + |
270 | +__metaclass__ = type |
271 | + |
272 | +import json |
273 | +import re |
274 | +from base_asses import toUnitTest |
275 | +from jujupy import yaml_loads |
276 | + |
277 | +# Machine and unit, deprecated in unit |
278 | +AGENT_STATE_KEY = "agent-state" |
279 | +AGENT_VERSION_KEY = "agent-version" |
280 | +DNS_NAME_KEY = "dns-name" |
281 | +INSTANCE_ID_KEY = "instance-id" |
282 | +HARDWARE_KEY = "hardware" |
283 | +SERIES_KEY = "series" |
284 | +STATE_SERVER_MEMBER_STATUS_KEY = "state-server-member-status" |
285 | +# service |
286 | +CHARM_KEY = "charm" |
287 | +EXPOSED_KEY = "exposed" |
288 | +SERVICE_STATUS_KEY = "service-status" |
289 | +# unit |
290 | +WORKLOAD_STATUS_KEY = "workload-status" |
291 | +AGENT_STATUS_KEY = "agent-status" |
292 | +MACHINE_KEY = "machine" |
293 | +PUBLIC_ADDRESS_KEY = "public-address" |
294 | + |
295 | +MACHINE_TAB_HEADERS = ['ID', 'STATE', 'VERSION', 'DNS', 'INS-ID', 'SERIES', |
296 | + 'HARDWARE'] |
297 | +UNIT_TAB_HEADERS = ['ID', 'WORKLOAD-STATE', 'AGENT-STATE', 'VERSION', |
298 | + 'MACHINE', 'PORTS', 'PUBLIC-ADDRESS', 'MESSAGE'] |
299 | +SERVICE_TAB_HEADERS = ['NAME', 'STATUS', 'EXPOSED', 'CHARM'] |
300 | + |
301 | + |
302 | +class StatusTester: |
303 | + def __init__(self, text="", status_format="yaml"): |
304 | + self._text = text |
305 | + self._format = status_format |
306 | + self.s = globals()["Status%sParser" % status_format.capitalize()](text) |
307 | + |
308 | + @classmethod |
309 | + def from_text(cls, text, status_format): |
310 | + return cls(text, status_format) |
311 | + |
312 | + def __unicode__(self): |
313 | + return self._text |
314 | + |
315 | + def __str__(self): |
316 | + return self._text |
317 | + |
318 | + |
319 | +class ErrNoStatus(Exception): |
320 | + """An exception for missing juju status.""" |
321 | + |
322 | + |
323 | +class ErrMalformedStatus(Exception): |
324 | + """An exception for unexpected formats of status.""" |
325 | + |
326 | + |
327 | +class ErrUntestedStatusOutput(Exception): |
328 | + """An exception for results returned by status. |
329 | + |
330 | + Status that are known not to be covered by the tests should raise |
331 | + this exception. """ |
332 | + |
333 | + |
334 | +class BaseStatusParser: |
335 | + |
336 | + _expected = set(["environment", "services", "machines"]) |
337 | + |
338 | + def __init__(self): |
339 | + # expected entity storage |
340 | + self._machines = dict() |
341 | + self._services = dict() |
342 | + self._units = dict() |
343 | + |
344 | + self.parse() |
345 | + |
346 | + def parse(self): |
347 | + return self._parse() |
348 | + |
349 | + @toUnitTest |
350 | + def store(self, tc, parsed): |
351 | + # Either there are less items than expected and therefore status |
352 | + # is returning a subset of what it should or there are more and |
353 | + # this means there are untested keys in status. |
354 | + tc.assertItemsEqual(parsed.keys(), self._expected, |
355 | + "untested items or incomplete status output") |
356 | + |
357 | + # status of machines. |
358 | + for machine_id, machine in parsed.get("machines", {}).iteritems(): |
359 | + tc.assertNotIn(machine_id, self._machines, |
360 | + "Machine %s is repeated in yaml" |
361 | + " status " % machine_id) |
362 | + self._machines[machine_id] = machine |
363 | + |
364 | + # status of services |
365 | + for service_name, service in parsed.get("services", {}).iteritems(): |
366 | + tc.assertNotIn(service_name, self._services, |
367 | + "Service %s is repeated in yaml " |
368 | + "status " % service_name) |
369 | + self._services[service_name] = service |
370 | + |
371 | + # status of units |
372 | + for service_name, service in self._services.iteritems(): |
373 | + for unit_name, unit in service.get("units", {}).iteritems(): |
374 | + tc.assertNotIn(unit_name, self._units, |
375 | + "Unit %s is repeated in yaml " |
376 | + "status " % unit_name) |
377 | + self._units[unit_name] = unit |
378 | + |
379 | + @toUnitTest |
380 | + def assert_machines_len(self, tc, expected_len): |
381 | + """Assert that we got as many machines as we where expecting. |
382 | + |
383 | + :param expected_len: expected quantity of machines. |
384 | + :type expected_len: int |
385 | + """ |
386 | + tc.assertEqual(len(self._machines), expected_len) |
387 | + |
388 | + @toUnitTest |
389 | + def assert_machines_ids(self, tc, expected_ids): |
390 | + """Assert that we got the machines we where expecting. |
391 | + |
392 | + :param expected_ids: expected ids of machines. |
393 | + :type expected_ids: tuple |
394 | + """ |
395 | + tc.assertItemsEqual(self._machines, expected_ids) |
396 | + |
397 | + def _machine_key_get(self, tc, machine_id, key): |
398 | + tc.assertIn(machine_id, self._machines, |
399 | + "Machine \"%s\" not present in machines" % machine_id) |
400 | + tc.assertIn(key, self._machines[machine_id], |
401 | + "Key \"%s\" not present in Machine \"%s\"" % |
402 | + (key, machine_id)) |
403 | + return self._machines[machine_id][key] |
404 | + |
405 | + @toUnitTest |
406 | + def assert_machine_agent_state(self, tc, machine_id, state): |
407 | + value = self._machine_key_get(tc, machine_id, AGENT_STATE_KEY) |
408 | + tc.assertEqual(value, state) |
409 | + |
410 | + @toUnitTest |
411 | + def assert_machine_agent_version(self, tc, machine_id, version): |
412 | + value = self._machine_key_get(tc, machine_id, AGENT_VERSION_KEY) |
413 | + tc.assertEqual(value, version) |
414 | + |
415 | + @toUnitTest |
416 | + def assert_machine_dns_name(self, tc, machine_id, dns_name): |
417 | + value = self._machine_key_get(tc, machine_id, DNS_NAME_KEY) |
418 | + tc.assertEqual(value, dns_name) |
419 | + |
420 | + @toUnitTest |
421 | + def assert_machine_instance_id(self, tc, machine_id, instance_id): |
422 | + value = self._machine_key_get(tc, machine_id, INSTANCE_ID_KEY) |
423 | + tc.assertEqual(value, instance_id) |
424 | + |
425 | + @toUnitTest |
426 | + def assert_machine_series(self, tc, machine_id, series): |
427 | + value = self._machine_key_get(tc, machine_id, SERIES_KEY) |
428 | + tc.assertEqual(value, series) |
429 | + |
430 | + @toUnitTest |
431 | + def assert_machine_hardware(self, tc, machine_id, hardware): |
432 | + value = self._machine_key_get(tc, machine_id, HARDWARE_KEY) |
433 | + tc.assertEqual(value, hardware) |
434 | + |
435 | + @toUnitTest |
436 | + def assert_machine_member_status(self, tc, machine_id, member_status): |
437 | + value = self._machine_key_get(tc, machine_id, |
438 | + STATE_SERVER_MEMBER_STATUS_KEY) |
439 | + tc.assertEqual(value, member_status) |
440 | + |
441 | + def _service_key_get(self, tc, service_name, key): |
442 | + tc.assertIn(service_name, self._services, |
443 | + "Service \"%s\" not present in services." % service_name) |
444 | + tc.assertIn(key, self._services[service_name], |
445 | + "Key \"%s\" not present in Service \"%s\"" % |
446 | + (key, service_name)) |
447 | + return self._services[service_name][key] |
448 | + |
449 | + # Service status |
450 | + @toUnitTest |
451 | + def assert_service_charm(self, tc, service_name, charm): |
452 | + value = self._service_key_get(tc, service_name, CHARM_KEY) |
453 | + tc.assertEqual(value, charm) |
454 | + |
455 | + @toUnitTest |
456 | + def assert_service_exposed(self, tc, service_name, exposed): |
457 | + value = self._service_key_get(tc, service_name, EXPOSED_KEY) |
458 | + tc.assertEqual(value, exposed) |
459 | + |
460 | + @toUnitTest |
461 | + def assert_service_service_status(self, tc, service_name, |
462 | + status={"current": "", "message": ""}): |
463 | + value = self._service_key_get(tc, service_name, SERVICE_STATUS_KEY) |
464 | + tc.assertEqual(value["current"], status["current"]) |
465 | + tc.assertEqual(value["message"], status["message"]) |
466 | + |
467 | + def _unit_key_get(self, tc, unit_name, key): |
468 | + tc.assertIn(unit_name, self._units, |
469 | + "Unit \"%s\" not present in units" % unit_name) |
470 | + tc.assertIn(key, self._units[unit_name], |
471 | + "Key \"%s\" not present in Unit \"%s\"" % |
472 | + (key, unit_name)) |
473 | + return self._units[unit_name][key] |
474 | + |
475 | + # Units status |
476 | + @toUnitTest |
477 | + def assert_unit_workload_status(self, tc, unit_name, |
478 | + status={"current": "", "message": ""}): |
479 | + value = self._unit_key_get(tc, unit_name, WORKLOAD_STATUS_KEY) |
480 | + tc.assertEqual(value["current"], status["current"]) |
481 | + tc.assertEqual(value["message"], status["message"]) |
482 | + |
483 | + @toUnitTest |
484 | + def assert_unit_agent_status(self, tc, unit_name, |
485 | + status={"current": "", "message": ""}): |
486 | + value = self._unit_key_get(tc, unit_name, AGENT_STATUS_KEY) |
487 | + tc.assertEqual(value["current"], status["current"]) |
488 | + # Message is optional for unit agents. |
489 | + tc.assertEqual(value.get("message", ""), status["message"]) |
490 | + |
491 | + @toUnitTest |
492 | + def assert_unit_agent_state(self, tc, unit_name, state): |
493 | + value = self._unit_key_get(tc, unit_name, AGENT_STATE_KEY) |
494 | + tc.assertEqual(value, state) |
495 | + |
496 | + @toUnitTest |
497 | + def assert_unit_agent_version(self, tc, unit_name, version): |
498 | + value = self._unit_key_get(tc, unit_name, AGENT_VERSION_KEY) |
499 | + tc.assertEqual(value, version) |
500 | + |
501 | + @toUnitTest |
502 | + def assert_unit_machine(self, tc, unit_name, machine): |
503 | + value = self._unit_key_get(tc, unit_name, MACHINE_KEY) |
504 | + tc.assertEqual(value, machine) |
505 | + |
506 | + @toUnitTest |
507 | + def assert_unit_public_address(self, tc, unit_name, address): |
508 | + value = self._unit_key_get(tc, unit_name, PUBLIC_ADDRESS_KEY) |
509 | + tc.assertEqual(value, address) |
510 | + |
511 | + |
512 | +class StatusYamlParser(BaseStatusParser): |
513 | + """StatusYamlParser handles parsing of status output in yaml format. |
514 | + |
515 | + To be used by status tester. |
516 | + """ |
517 | + |
518 | + def __init__(self, yaml=""): |
519 | + self._yaml = yaml |
520 | + if yaml == "": |
521 | + raise ErrNoStatus("Yaml status was empty") |
522 | + super(StatusYamlParser, self).__init__() |
523 | + |
524 | + def _parse(self): |
525 | + parsed = yaml_loads(self._yaml) |
526 | + self.store(parsed) |
527 | + |
528 | + |
529 | +class StatusJsonParser(BaseStatusParser): |
530 | + """StatusJSONParser handles parsing of status output in JSON format. |
531 | + |
532 | + To be used by status tester. |
533 | + """ |
534 | + |
535 | + def __init__(self, json_text=""): |
536 | + self._json = json_text |
537 | + if json_text == "": |
538 | + raise ErrNoStatus("JSON status was empty") |
539 | + super(StatusJsonParser, self).__init__() |
540 | + |
541 | + def _parse(self): |
542 | + parsed = json.loads(self._json) |
543 | + self.store(parsed) |
544 | + |
545 | + |
546 | +class StatusTabularParser(BaseStatusParser): |
547 | + """StatusTabularParser handles parsing of status output in Tabular format. |
548 | + |
549 | + To be used by status tester. |
550 | + """ |
551 | + |
552 | + def __init__(self, tabular_text=""): |
553 | + self._tabular = tabular_text |
554 | + if tabular_text == "": |
555 | + raise ErrNoStatus("tabular status was empty") |
556 | + super(StatusTabularParser, self).__init__() |
557 | + |
558 | + @toUnitTest |
559 | + def _normalize_machines(self, tc, header, items): |
560 | + nitems = items[:6] |
561 | + nitems.append(" ".join(items[6:])) |
562 | + tc.assertEqual(header, MACHINE_TAB_HEADERS, |
563 | + "Unexpected headers for machine:\n" |
564 | + "wanted: %s" |
565 | + "got: %s" % (MACHINE_TAB_HEADERS, header)) |
566 | + normalized = dict(zip((AGENT_STATE_KEY, AGENT_VERSION_KEY, |
567 | + DNS_NAME_KEY, INSTANCE_ID_KEY, |
568 | + SERIES_KEY, HARDWARE_KEY), |
569 | + nitems[1:])) |
570 | + return nitems[0], normalized |
571 | + |
572 | + @toUnitTest |
573 | + def _normalize_units(self, tc, header, items): |
574 | + eid, wlstate, astate, version, machine, paddress = items[:6] |
575 | + message = " ".join(items[6:]) |
576 | + wlstatus = {"current": wlstate, "message": message, |
577 | + "since": "bogus date"} |
578 | + astatus = {"current": astate, "message": "", "since": "bogus date"} |
579 | + tc.assertEqual(header, UNIT_TAB_HEADERS, |
580 | + "Unexpected headers for unit.\n" |
581 | + "wanted: %s" |
582 | + "got: %s" % (UNIT_TAB_HEADERS, header)) |
583 | + normalized = dict(zip((WORKLOAD_STATUS_KEY, AGENT_STATUS_KEY, |
584 | + AGENT_VERSION_KEY, MACHINE_KEY, |
585 | + PUBLIC_ADDRESS_KEY), |
586 | + (wlstatus, astatus, version, machine, paddress))) |
587 | + |
588 | + return eid, normalized |
589 | + |
590 | + @toUnitTest |
591 | + def _normalize_services(self, tc, header, items): |
592 | + name, status, exposed, charm = items |
593 | + tc.assertEqual(header, SERVICE_TAB_HEADERS, |
594 | + "Unexpected headers for service.\n" |
595 | + "wanted: %s" |
596 | + "got: %s" % (SERVICE_TAB_HEADERS, header)) |
597 | + normalized = dict(zip((CHARM_KEY, EXPOSED_KEY, SERVICE_STATUS_KEY), |
598 | + (charm, exposed == "true", {"current": status, |
599 | + "message": ""}))) |
600 | + return name, normalized |
601 | + |
602 | + def _parse(self): |
603 | + section = re.compile("^\[(\w*)\]") |
604 | + base = {"environment": "not provided"} |
605 | + current_parent = "" |
606 | + current_headers = [] |
607 | + prev_was_section = False |
608 | + for line in self._tabular.splitlines(): |
609 | + # parse section |
610 | + is_section = section.findall(line) |
611 | + if len(is_section) == 1: |
612 | + current_parent = is_section[0].lower() |
613 | + if current_parent != "units": |
614 | + base[current_parent] = {} |
615 | + prev_was_section = True |
616 | + continue |
617 | + # parse headers |
618 | + if prev_was_section: |
619 | + prev_was_section = False |
620 | + current_headers = line.split() |
621 | + continue |
622 | + |
623 | + # parse content |
624 | + if current_parent == "" or current_headers == []: |
625 | + raise ErrMalformedStatus("Tabular status is malformed") |
626 | + items = line.split() |
627 | + |
628 | + # separation line |
629 | + if len(items) == 0: |
630 | + continue |
631 | + |
632 | + normalize = None |
633 | + if current_parent == "services": |
634 | + normalize = self._normalize_services |
635 | + elif current_parent == "units": |
636 | + normalize = self._normalize_units |
637 | + elif current_parent == "machines": |
638 | + normalize = self._normalize_machines |
639 | + |
640 | + if not normalize: |
641 | + raise ErrUntestedStatusOutput("%s is not an expected tabular" |
642 | + " status section" % |
643 | + current_parent) |
644 | + k, v = normalize(current_headers, items) |
645 | + if current_parent == "units": |
646 | + base.setdefault("services", dict()) |
647 | + service = k.split("/")[0] |
648 | + base["services"][service].setdefault("units", dict()) |
649 | + base["services"][service]["units"][k] = v |
650 | + else: |
651 | + base[current_parent][k] = v |
652 | + self.store(base) |
653 | |
654 | === added file 'template_assess.py.tmpl' |
655 | --- template_assess.py.tmpl 1970-01-01 00:00:00 +0000 |
656 | +++ template_assess.py.tmpl 2015-07-13 18:48:12 +0000 |
657 | @@ -0,0 +1,54 @@ |
658 | +#!/usr/bin/python |
659 | +from __future__ import print_function |
660 | + |
661 | +__metaclass__ = type |
662 | + |
663 | +from base_asses import get_base_parser |
664 | +from status import StatusTester |
665 | +from jujupy import ( |
666 | + make_client, |
667 | +) |
668 | +from deploy_stack import ( |
669 | + boot_context, |
670 | + prepare_environment, |
671 | +) |
672 | + |
673 | +TEST_HUMAN_NAME="<YOUR TEST HUMAN READABLE HERE>" |
674 | + |
675 | + |
676 | +def parse_args(): |
677 | + """Parse all arguments.""" |
678 | + parser = get_base_parser(TEST_HUMAN_NAME) |
679 | + return parser.parse_args() |
680 | + |
681 | + |
682 | +def main(): |
683 | + args = parse_args() |
684 | + log_dir = args.logs |
685 | + |
686 | + client = make_client( |
687 | + args.juju_path, args.debug, args.env_name, args.temp_env_name) |
688 | + client.destroy_environment() |
689 | + series = args.series |
690 | + if series is None: |
691 | + series = 'precise' |
692 | + with boot_context(args.job_name, client, args.bootstrap_host, |
693 | + args.machine, series, args.agent_url, args.agent_stream, |
694 | + log_dir, args.keep_env, args.upload_tools): |
695 | + prepare_environment( |
696 | + client, already_bootstrapped=True, machines=args.machine) |
697 | + |
698 | + client.get_status(60) |
699 | + # Deploy charms, there are several under ./repository |
700 | + client.juju("deploy", ('local:trusty/wordpress',)) |
701 | + # Wait for the deployment to finish. |
702 | + client.wait_for_started() |
703 | + #----------------- CALL YOUR TESTS HERE |
704 | + # At this point you have a juju bootstraped with a wordpress charm |
705 | + # deployed and active with the agent idle. |
706 | + |
707 | + #----------------- TESTS END HERE |
708 | + |
709 | + |
710 | +if __name__ == '__main__': |
711 | + main() |
712 | |
713 | === added file 'test_assess_status_output.py' |
714 | --- test_assess_status_output.py 1970-01-01 00:00:00 +0000 |
715 | +++ test_assess_status_output.py 2015-07-13 18:48:12 +0000 |
716 | @@ -0,0 +1,426 @@ |
717 | +__metaclass__ = type |
718 | + |
719 | +from status import ( |
720 | + ErrNoStatus, |
721 | + StatusYamlParser, |
722 | + StatusJsonParser, |
723 | + StatusTabularParser |
724 | +) |
725 | + |
726 | +from unittest import TestCase |
727 | + |
728 | +SAMPLE_YAML_OUTPUT = """environment: bogusec2 |
729 | +machines: |
730 | + "0": |
731 | + agent-state: started |
732 | + agent-version: 1.25-alpha1 |
733 | + dns-name: 54.82.51.4 |
734 | + instance-id: i-c0dadd10 |
735 | + instance-state: running |
736 | + series: trusty |
737 | + hardware: arch=amd64 cpu-cores=1 cpu-power=100 mem=1740M """ \ |
738 | + """root-disk=8192M availability-zone=us-east-1c |
739 | + state-server-member-status: has-vote |
740 | + "1": |
741 | + agent-state: started |
742 | + agent-version: 1.25-alpha1 |
743 | + dns-name: 54.162.95.230 |
744 | + instance-id: i-39280ac6 |
745 | + instance-state: running |
746 | + series: trusty |
747 | + hardware: arch=amd64 cpu-cores=1 cpu-power=100 mem=1740M """\ |
748 | + """root-disk=8192M availability-zone=us-east-1d |
749 | +services: |
750 | + statusstresser: |
751 | + charm: local:trusty/statusstresser-1 |
752 | + exposed: false |
753 | + service-status: |
754 | + current: active |
755 | + message: called in config-changed hook |
756 | + since: 12 Jun 2015 13:15:25-03:00 |
757 | + units: |
758 | + statusstresser/0: |
759 | + workload-status: |
760 | + current: active |
761 | + message: called in config-changed hook |
762 | + since: 12 Jun 2015 13:15:25-03:00 |
763 | + agent-status: |
764 | + current: idle |
765 | + since: 12 Jun 2015 14:33:08-03:00 |
766 | + version: 1.25-alpha1 |
767 | + agent-state: started |
768 | + agent-version: 1.25-alpha1 |
769 | + machine: "1" |
770 | + public-address: 54.162.95.230 |
771 | +""" |
772 | +# This could be replaced by a json.dumps({}) but the text is kept to |
773 | +# make this test about status output as accurate as possible. |
774 | +SAMPLE_JSON_OUTPUT = \ |
775 | + """{"environment":"perritoec2","machines":{"0":{"agent-state":"started","""\ |
776 | + """"agent-version":"1.25-alpha1","dns-name":"54.82.51.4","""\ |
777 | + """"instance-id":"i-c0dadd10","instance-state":"running","""\ |
778 | + """"series":"trusty","hardware":"arch=amd64 cpu-cores=1 cpu-power=100 """\ |
779 | + """mem=1740M root-disk=8192M availability-zone=us-east-1c","""\ |
780 | + """"state-server-member-status":"has-vote"},"1":{"agent-state":"""\ |
781 | + """"started","agent-version":"1.25-alpha1","dns-name":"54.162.95.230","""\ |
782 | + """"instance-id":"i-a7a2b377","instance-state":"running","""\ |
783 | + """"series":"trusty","hardware":"arch=amd64 cpu-cores=1 cpu-power=300 """\ |
784 | + """mem=3840M root-disk=8192M availability-zone=us-east-1c"}},"""\ |
785 | + """"services":{"statusstresser":{"charm": """\ |
786 | + """"local:trusty/statusstresser-1","exposed":false,"service-status":"""\ |
787 | + """{"current":"active","message":"called in config-changed hook","""\ |
788 | + """"since":"15 Jun 2015 20:56:29-03:00"},"""\ |
789 | + """"units":{"statusstresser/0":{"workload-status":"""\ |
790 | + """{"current":"active","message":"called in config-changed hook","""\ |
791 | + """"since":"15 Jun 2015 20:56:29-03:00"},"agent-status":"""\ |
792 | + """{"current":"idle","since":"15 Jun 2015 20:56:41-03:00","""\ |
793 | + """"version":"1.25-alpha1"},"agent-state":"started","agent-version":"""\ |
794 | + """"1.25-alpha1","machine":"1","public-address":"54.162.95.230"}}}}}""" |
795 | + |
796 | +SAMPLE_TABULAR_OUTPUT = """[Services] |
797 | +NAME STATUS EXPOSED CHARM |
798 | +statusstresser active false local:trusty/statusstresser-1 |
799 | + |
800 | +[Units] |
801 | +ID WORKLOAD-STATE AGENT-STATE VERSION MACHINE PORTS """\ |
802 | +"""PUBLIC-ADDRESS MESSAGE |
803 | +statusstresser/0 active idle 1.25-alpha1 1 """\ |
804 | +"""54.162.95.230 called in config-changed hook |
805 | + |
806 | +[Machines] |
807 | +ID STATE VERSION DNS INS-ID SERIES HARDWARE |
808 | +0 started 1.25-alpha1 54.82.51.4 i-c0dadd10 trusty arch=amd64 """\ |
809 | + """cpu-cores=1 cpu-power=100 mem=1740M root-disk=8192M """\ |
810 | + """availability-zone=us-east-1c |
811 | +1 started 1.25-alpha1 54.162.95.230 i-39280ac6 trusty arch=amd64 """\ |
812 | + """cpu-cores=1 cpu-power=100 mem=1740M root-disk=8192M """\ |
813 | + """availability-zone=us-east-1d""" |
814 | + |
815 | + |
816 | +class ReducedTestStatus: |
817 | + |
818 | + def test_assert_machine_ids(self): |
819 | + self.parser.assert_machines_ids(["0", "1"]) |
820 | + |
821 | + def test_assert_machine_ids_failed(self): |
822 | + with self.assertRaises(AssertionError): |
823 | + self.parser.assert_machines_ids(["0", "1", "2"]) |
824 | + |
825 | + def test_assert_machine_len(self): |
826 | + self.parser.assert_machines_len(2) |
827 | + |
828 | + def test_assert_machine_len_failed(self): |
829 | + with self.assertRaises(AssertionError): |
830 | + self.parser.assert_machines_len(3) |
831 | + |
832 | + def test_machine_agent_state_valid(self): |
833 | + self.parser.assert_machine_agent_state("0", "started") |
834 | + |
835 | + def test_machine_agent_state_failed(self): |
836 | + with self.assertRaises(AssertionError): |
837 | + self.parser.assert_machine_agent_state("0", "stopped") |
838 | + |
839 | + def test_machine_agent_state_error(self): |
840 | + with self.assertRaises(AssertionError): |
841 | + self.parser.assert_machine_agent_state("3", "stopped") |
842 | + |
843 | + def test_assert_machine_agent_version(self): |
844 | + self.parser.assert_machine_agent_version("0", "1.25-alpha1") |
845 | + |
846 | + def test_assert_machine_agent_version_failed(self): |
847 | + with self.assertRaises(AssertionError): |
848 | + self.parser.assert_machine_agent_version("0", "1.25-alpha2") |
849 | + |
850 | + def test_assert_machine_agent_version_error(self): |
851 | + with self.assertRaises(AssertionError): |
852 | + self.parser.assert_machine_agent_version("5", "1.25-alpha1") |
853 | + |
854 | + def test_assert_machine_dns_name(self): |
855 | + self.parser.assert_machine_dns_name("0", "54.82.51.4") |
856 | + |
857 | + def test_assert_machine_dns_name_failed(self): |
858 | + with self.assertRaises(AssertionError): |
859 | + self.parser.assert_machine_dns_name("0", "54.82.51.5") |
860 | + |
861 | + def test_assert_machine_dns_name_error(self): |
862 | + with self.assertRaises(AssertionError): |
863 | + self.parser.assert_machine_dns_name("3", "54.82.51.4") |
864 | + |
865 | + def test_assert_machine_instance_id(self): |
866 | + self.parser.assert_machine_instance_id("0", "i-c0dadd10") |
867 | + |
868 | + def test_assert_machine_instance_id_failed(self): |
869 | + with self.assertRaises(AssertionError): |
870 | + self.parser.assert_machine_instance_id("0", "i-c0dadd11") |
871 | + |
872 | + def test_assert_machine_instance_id_error(self): |
873 | + with self.assertRaises(AssertionError): |
874 | + self.parser.assert_machine_instance_id("3", "i-c0dadd10") |
875 | + |
876 | + def test_assert_machine_series(self): |
877 | + self.parser.assert_machine_series("0", "trusty") |
878 | + |
879 | + def test_assert_machine_series_failed(self): |
880 | + with self.assertRaises(AssertionError): |
881 | + self.parser.assert_machine_series("0", "utopic") |
882 | + |
883 | + def test_assert_machine_series_error(self): |
884 | + with self.assertRaises(AssertionError): |
885 | + self.parser.assert_machine_series("3", "trusty") |
886 | + |
887 | + def test_assert_machine_hardware(self): |
888 | + self.parser.assert_machine_hardware("0", "arch=amd64 cpu-cores=1 " |
889 | + "cpu-power=100 mem=1740M " |
890 | + "root-disk=8192M " |
891 | + "availability-zone=" |
892 | + "us-east-1c") |
893 | + |
894 | + def test_assert_machine_hardware_failed(self): |
895 | + with self.assertRaises(AssertionError): |
896 | + self.parser.assert_machine_hardware("0", "arch=arm cpu-cores=1 " |
897 | + "cpu-power=100 mem=1740M " |
898 | + "root-disk=8192M " |
899 | + "availability-zone=" |
900 | + "us-east-1c") |
901 | + |
902 | + def test_assert_machine_hardware_error(self): |
903 | + with self.assertRaises(AssertionError): |
904 | + self.parser.assert_machine_hardware("3", "arch=amd64 cpu-cores=1 " |
905 | + "cpu-power=100 mem=1740M " |
906 | + "root-disk=8192M " |
907 | + "availability-zone=" |
908 | + "us-east-1c") |
909 | + |
910 | + def test_assert_service_charm(self): |
911 | + self.parser.assert_service_charm("statusstresser", |
912 | + "local:trusty/statusstresser-1") |
913 | + |
914 | + def test_assert_service_charm_failed(self): |
915 | + with self.assertRaises(AssertionError): |
916 | + self.parser.assert_service_charm("statusstresser", |
917 | + "local:trusty/statusstresser-2") |
918 | + |
919 | + def test_assert_service_charm_error(self): |
920 | + with self.assertRaises(AssertionError): |
921 | + self.parser.assert_service_charm("statusrelaxer", |
922 | + "local:trusty/statusstresser-1") |
923 | + |
924 | + def test_assert_service_exposed(self): |
925 | + self.parser.assert_service_exposed("statusstresser", False) |
926 | + |
927 | + def test_assert_service_exposed_failed(self): |
928 | + with self.assertRaises(AssertionError): |
929 | + self.parser.assert_service_exposed("statusstresser", True) |
930 | + |
931 | + def test_assert_service_exposed_error(self): |
932 | + with self.assertRaises(AssertionError): |
933 | + self.parser.assert_service_exposed("statusrelaxer", False) |
934 | + |
935 | + def test_assert_unit_public_address(self): |
936 | + self.parser.assert_unit_public_address("statusstresser/0", |
937 | + "54.162.95.230") |
938 | + |
939 | + def test_assert_unit_public_address_failed(self): |
940 | + with self.assertRaises(AssertionError): |
941 | + self.parser.assert_unit_public_address("statusstresser/0", |
942 | + "54.162.95.231") |
943 | + |
944 | + def test_assert_unit_public_address_error(self): |
945 | + with self.assertRaises(AssertionError): |
946 | + self.parser.assert_unit_public_address("statusrelaxer/0", |
947 | + "54.162.95.230") |
948 | + |
949 | + |
950 | +class BaseTestStatus(ReducedTestStatus): |
951 | + |
952 | + def test_assert_machine_member_status(self): |
953 | + self.parser.assert_machine_member_status("0", "has-vote") |
954 | + |
955 | + def test_assert_machine_member_status_failed(self): |
956 | + with self.assertRaises(AssertionError): |
957 | + self.parser.assert_machine_member_status("0", "not-voting") |
958 | + |
959 | + def test_assert_machine_member_status_error(self): |
960 | + with self.assertRaises(AssertionError): |
961 | + self.parser.assert_machine_member_status("3", "has-vote") |
962 | + |
963 | + def test_assert_service_service_status(self): |
964 | + self.parser.assert_service_service_status("statusstresser", |
965 | + {"current": "active", |
966 | + "message": "called in " |
967 | + "config-changed hook"}) |
968 | + |
969 | + def test_assert_service_service_status_failed(self): |
970 | + with self.assertRaises(AssertionError): |
971 | + self.parser.assert_service_service_status("statusstresser", |
972 | + {"current": "active", |
973 | + "message": "another " |
974 | + "message"}) |
975 | + |
976 | + def test_assert_service_service_status_error(self): |
977 | + with self.assertRaises(AssertionError): |
978 | + self.parser.assert_service_service_status("statusrelaxer", |
979 | + {"current": "active", |
980 | + "message": "called in " |
981 | + "config-changed hook"}) |
982 | + |
983 | + def test_assert_unit_workload_status(self): |
984 | + self.parser.assert_unit_workload_status("statusstresser/0", |
985 | + {"current": "active", |
986 | + "message": "called in " |
987 | + "config-changed hook"}) |
988 | + |
989 | + def test_assert_unit_workload_status_failed(self): |
990 | + with self.assertRaises(AssertionError): |
991 | + self.parser.assert_unit_workload_status("statusstresser/0", |
992 | + {"current": "active", |
993 | + "message": "another " |
994 | + "message"}) |
995 | + |
996 | + def test_assert_unit_workload_status_error(self): |
997 | + with self.assertRaises(AssertionError): |
998 | + self.parser.assert_unit_workload_status("statusrelaxer/0", |
999 | + {"current": "active", |
1000 | + "message": "called in " |
1001 | + "config-changed hook"}) |
1002 | + |
1003 | + def test_assert_unit_agent_status(self): |
1004 | + self.parser.assert_unit_agent_status("statusstresser/0", |
1005 | + {"current": "idle", |
1006 | + "message": ""}) |
1007 | + |
1008 | + def test_assert_unit_agent_status_failed(self): |
1009 | + with self.assertRaises(AssertionError): |
1010 | + self.parser.assert_unit_agent_status("statusstresser/0", |
1011 | + {"current": "idle", |
1012 | + "message": "an unexpected " |
1013 | + "message"}) |
1014 | + |
1015 | + def test_assert_unit_agent_status_error(self): |
1016 | + with self.assertRaises(AssertionError): |
1017 | + self.parser.assert_unit_agent_status("statusrelaxer/0", |
1018 | + {"current": "idle", |
1019 | + "message": ""}) |
1020 | + |
1021 | + def test_assert_unit_agent_state(self): |
1022 | + self.parser.assert_unit_agent_state("statusstresser/0", "started") |
1023 | + |
1024 | + def test_assert_unit_agent_state_failed(self): |
1025 | + with self.assertRaises(AssertionError): |
1026 | + self.parser.assert_unit_agent_state("statusstresser/0", "stopped") |
1027 | + |
1028 | + def test_assert_unit_agent_state_error(self): |
1029 | + with self.assertRaises(AssertionError): |
1030 | + self.parser.assert_unit_agent_state("statusrelaxer/0", "started") |
1031 | + |
1032 | + def test_assert_unit_agent_version(self): |
1033 | + self.parser.assert_unit_agent_version("statusstresser/0", |
1034 | + "1.25-alpha1") |
1035 | + |
1036 | + def test_assert_unit_agent_version_failed(self): |
1037 | + with self.assertRaises(AssertionError): |
1038 | + self.parser.assert_unit_agent_version("statusstresser/0", |
1039 | + "1.25-alpha2") |
1040 | + |
1041 | + def test_assert_unit_agent_version_error(self): |
1042 | + with self.assertRaises(AssertionError): |
1043 | + self.parser.assert_unit_agent_version("statusrelaxer/0", |
1044 | + "1.25-alpha1") |
1045 | + |
1046 | + def test_assert_unit_machine(self): |
1047 | + self.parser.assert_unit_machine("statusstresser/0", "1") |
1048 | + |
1049 | + def test_assert_unit_machine_failed(self): |
1050 | + with self.assertRaises(AssertionError): |
1051 | + self.parser.assert_unit_machine("statusstresser/0", "2") |
1052 | + |
1053 | + def test_assert_unit_machine_error(self): |
1054 | + with self.assertRaises(AssertionError): |
1055 | + self.parser.assert_unit_machine("statusrelaxer/0", "1") |
1056 | + |
1057 | + |
1058 | +class TestStatusForYaml(TestCase, BaseTestStatus): |
1059 | + |
1060 | + def test_empty_yaml_fails(self): |
1061 | + with self.assertRaises(ErrNoStatus): |
1062 | + StatusYamlParser(yaml="") |
1063 | + |
1064 | + def setUp(self): |
1065 | + self.parser = StatusYamlParser(yaml=SAMPLE_YAML_OUTPUT) |
1066 | + |
1067 | + |
1068 | +class TestStatusForJson(TestCase, BaseTestStatus): |
1069 | + |
1070 | + def test_empty_json_fails(self): |
1071 | + with self.assertRaises(ErrNoStatus): |
1072 | + StatusJsonParser(json_text="") |
1073 | + |
1074 | + def setUp(self): |
1075 | + self.parser = StatusJsonParser(json_text=SAMPLE_JSON_OUTPUT) |
1076 | + |
1077 | + |
1078 | +class TestStatusTabular(TestCase, ReducedTestStatus): |
1079 | + |
1080 | + def test_empty_tabular_fails(self): |
1081 | + with self.assertRaises(ErrNoStatus): |
1082 | + StatusTabularParser("") |
1083 | + |
1084 | + def setUp(self): |
1085 | + self.parser = StatusTabularParser(tabular_text=SAMPLE_TABULAR_OUTPUT) |
1086 | + |
1087 | + def test_assert_service_service_status(self): |
1088 | + self.parser.assert_service_service_status("statusstresser", |
1089 | + {"current": "active", |
1090 | + "message": ""}) |
1091 | + |
1092 | + def test_assert_service_service_status_failed(self): |
1093 | + with self.assertRaises(AssertionError): |
1094 | + self.parser.assert_service_service_status("statusstresser", |
1095 | + {"current": "active", |
1096 | + "message": "another " |
1097 | + "message"}) |
1098 | + |
1099 | + def test_assert_service_service_status_error(self): |
1100 | + with self.assertRaises(AssertionError): |
1101 | + self.parser.assert_service_service_status("statusrelaxer", |
1102 | + {"current": "active", |
1103 | + "message": "called in " |
1104 | + "config-changed hook"}) |
1105 | + |
1106 | + def test_assert_unit_workload_status(self): |
1107 | + self.parser.assert_unit_workload_status("statusstresser/0", |
1108 | + {"current": "active", |
1109 | + "message": "called in " |
1110 | + "config-changed hook"}) |
1111 | + |
1112 | + def test_assert_unit_workload_status_failed(self): |
1113 | + with self.assertRaises(AssertionError): |
1114 | + self.parser.assert_unit_workload_status("statusstresser/0", |
1115 | + {"current": "active", |
1116 | + "message": "another " |
1117 | + "message"}) |
1118 | + |
1119 | + def test_assert_unit_workload_status_error(self): |
1120 | + with self.assertRaises(AssertionError): |
1121 | + self.parser.assert_unit_workload_status("statusrelaxer/0", |
1122 | + {"current": "active", |
1123 | + "message": "called in " |
1124 | + "config-changed hook"}) |
1125 | + |
1126 | + def test_assert_unit_agent_status(self): |
1127 | + self.parser.assert_unit_agent_status("statusstresser/0", |
1128 | + {"current": "idle", |
1129 | + "message": ""}) |
1130 | + |
1131 | + def test_assert_unit_agent_status_failed(self): |
1132 | + with self.assertRaises(AssertionError): |
1133 | + self.parser.assert_unit_agent_status("statusstresser/0", |
1134 | + {"current": "idle", |
1135 | + "message": "an unexpected " |
1136 | + "message"}) |
1137 | + |
1138 | + def test_assert_unit_agent_status_error(self): |
1139 | + with self.assertRaises(AssertionError): |
1140 | + self.parser.assert_unit_agent_status("statusrelaxer/0", |
1141 | + {"current": "idle", |
1142 | + "message": ""}) |
Thank you Horatio. This was a long branch to review. I like the actual testing done in this branch, but I believe there are easier ways to do it. My comments are inline. I also like the idea of a template to write new tests, and I think there is a context manager that makes writing test easier than what you choose.
Please run make lint to address style issues. you may want to try autopep8 which claims to reformat python to pep8 and pep257. In my experience, there will be some reformatting that is not specified by pep8