Merge lp:~adam-collard/charm-helpers/amulet-get-process-id-list-fixes into lp:charm-helpers

Proposed by Adam Collard
Status: Merged
Merged at revision: 429
Proposed branch: lp:~adam-collard/charm-helpers/amulet-get-process-id-list-fixes
Merge into: lp:charm-helpers
Diff against target: 207 lines (+140/-11)
2 files modified
charmhelpers/contrib/amulet/utils.py (+22/-8)
tests/contrib/amulet/test_utils.py (+118/-3)
To merge this branch: bzr merge lp:~adam-collard/charm-helpers/amulet-get-process-id-list-fixes
Reviewer Review Type Date Requested Status
Chris Glass (community) Approve
Review via email: mp+268204@code.launchpad.net

Description of the change

Extend get_process_id and get_unit_process_ids to allow them to be used for testing that processes are /not/ running.

To post a comment you must log in.
Revision history for this message
Chris Glass (tribaal) wrote :

Thanks for your branch. On principle it looks good, but some tests fail and others clutter stdout/stderr on "make test".

I'm going to have to mark this as need fixing until "make test" produces a clean (and passing) output.

review: Needs Fixing
431. By Adam Collard

Use six.iteritems() to fix Py3 test failure (tribaal's review)

432. By Adam Collard

Capture stdout/stderr around tests that expect to run amulet.raise_status and verify the message

Revision history for this message
Adam Collard (adam-collard) wrote :

> Thanks for your branch. On principle it looks good, but some tests fail and
> others clutter stdout/stderr on "make test".

Thanks for the review. Sorry for not running the full test suite before proposing.

The extra tests I added exposed existing failures in the amulet utils when running under Python 3. I've fixed those, and added code to capture stdout.

> I'm going to have to mark this as need fixing until "make test" produces a
> clean (and passing) output.

Please take another look, believed to have been fixed now!

Revision history for this message
Chris Glass (tribaal) wrote :

Yep, looks good now, thanks.

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/amulet/utils.py'
2--- charmhelpers/contrib/amulet/utils.py 2015-08-14 17:09:52 +0000
3+++ charmhelpers/contrib/amulet/utils.py 2015-08-17 11:03:41 +0000
4@@ -460,15 +460,20 @@
5 cmd, code, output))
6 return None
7
8- def get_process_id_list(self, sentry_unit, process_name):
9+ def get_process_id_list(self, sentry_unit, process_name,
10+ expect_success=True):
11 """Get a list of process ID(s) from a single sentry juju unit
12 for a single process name.
13
14- :param sentry_unit: Pointer to amulet sentry instance (juju unit)
15+ :param sentry_unit: Amulet sentry instance (juju unit)
16 :param process_name: Process name
17+ :param expect_success: If False, expect the PID to be missing,
18+ raise if it is present.
19 :returns: List of process IDs
20 """
21- cmd = 'pidof {}'.format(process_name)
22+ cmd = 'pidof -x {}'.format(process_name)
23+ if not expect_success:
24+ cmd += " || exit 0 && exit 1"
25 output, code = sentry_unit.run(cmd)
26 if code != 0:
27 msg = ('{} `{}` returned {} '
28@@ -477,14 +482,23 @@
29 amulet.raise_status(amulet.FAIL, msg=msg)
30 return str(output).split()
31
32- def get_unit_process_ids(self, unit_processes):
33+ def get_unit_process_ids(self, unit_processes, expect_success=True):
34 """Construct a dict containing unit sentries, process names, and
35- process IDs."""
36+ process IDs.
37+
38+ :param unit_processes: A dictionary of Amulet sentry instance
39+ to list of process names.
40+ :param expect_success: if False expect the processes to not be
41+ running, raise if they are.
42+ :returns: Dictionary of Amulet sentry instance to dictionary
43+ of process names to PIDs.
44+ """
45 pid_dict = {}
46- for sentry_unit, process_list in unit_processes.iteritems():
47+ for sentry_unit, process_list in six.iteritems(unit_processes):
48 pid_dict[sentry_unit] = {}
49 for process in process_list:
50- pids = self.get_process_id_list(sentry_unit, process)
51+ pids = self.get_process_id_list(
52+ sentry_unit, process, expect_success=expect_success)
53 pid_dict[sentry_unit].update({process: pids})
54 return pid_dict
55
56@@ -498,7 +512,7 @@
57 return ('Unit count mismatch. expected, actual: {}, '
58 '{} '.format(len(expected), len(actual)))
59
60- for (e_sentry, e_proc_names) in expected.iteritems():
61+ for (e_sentry, e_proc_names) in six.iteritems(expected):
62 e_sentry_name = e_sentry.info['unit_name']
63 if e_sentry in actual.keys():
64 a_proc_names = actual[e_sentry]
65
66=== modified file 'tests/contrib/amulet/test_utils.py'
67--- tests/contrib/amulet/test_utils.py 2015-08-14 17:09:52 +0000
68+++ tests/contrib/amulet/test_utils.py 2015-08-17 11:03:41 +0000
69@@ -3,16 +3,35 @@
70 # Authors:
71 # Adam Collard <adam.collard@canonical.com>
72
73+from contextlib import contextmanager
74+import sys
75 import unittest
76
77+import six
78+
79 from charmhelpers.contrib.amulet.utils import AmuletUtils
80
81
82+@contextmanager
83+def captured_output():
84+ """Simple context manager to capture stdout/stderr.
85+
86+ Source: http://stackoverflow.com/a/17981937/56219.
87+ """
88+ new_out, new_err = six.StringIO(), six.StringIO()
89+ old_out, old_err = sys.stdout, sys.stderr
90+ try:
91+ sys.stdout, sys.stderr = new_out, new_err
92+ yield sys.stdout, sys.stderr
93+ finally:
94+ sys.stdout, sys.stderr = old_out, old_err
95+
96+
97 class FakeSentry(object):
98
99- commands = {}
100-
101- info = {"unit_name": "foo"}
102+ def __init__(self, name="foo"):
103+ self.commands = {}
104+ self.info = {"unit_name": name}
105
106 def run(self, command):
107 return self.commands[command]
108@@ -193,3 +212,99 @@
109
110 self.assertFalse(self.utils.wait_on_action(
111 "action-id", _check_output=fake_check_output))
112+
113+
114+class GetProcessIdListTestCase(unittest.TestCase):
115+
116+ def setUp(self):
117+ self.utils = AmuletUtils()
118+ self.sentry_unit = FakeSentry()
119+
120+ def test_returns_pids(self):
121+ """
122+ Normal execution returns a list of pids
123+ """
124+ self.sentry_unit.commands["pidof -x foo"] = ("123 124 125", 0)
125+ result = self.utils.get_process_id_list(self.sentry_unit, "foo")
126+ self.assertEqual(["123", "124", "125"], result)
127+
128+ def test_fails_if_no_process_found(self):
129+ """
130+ By default, the expectation is that a process is running. Failure
131+ to find a given process results in an amulet.FAIL being
132+ raised.
133+ """
134+ self.sentry_unit.commands["pidof -x foo"] = ("", 1)
135+ with self.assertRaises(SystemExit) as cm, captured_output() as (
136+ out, err):
137+ self.utils.get_process_id_list(self.sentry_unit, "foo")
138+ the_exception = cm.exception
139+ self.assertEqual(1, the_exception.code)
140+ self.assertEqual(
141+ "foo `pidof -x foo` returned 1", out.getvalue().rstrip())
142+
143+ def test_looks_for_scripts(self):
144+ """
145+ pidof command uses -x to return a list of pids of scripts
146+ """
147+ self.sentry_unit.commands["pidof foo"] = ("", 1)
148+ self.sentry_unit.commands["pidof -x foo"] = ("123 124 125", 0)
149+ result = self.utils.get_process_id_list(self.sentry_unit, "foo")
150+ self.assertEqual(["123", "124", "125"], result)
151+
152+ def test_expect_no_pid(self):
153+ """
154+ By setting expectation that there are no pids running the logic
155+ about when to fail is reversed.
156+ """
157+ self.sentry_unit.commands["pidof -x foo || exit 0 && exit 1"] = ("", 0)
158+ self.sentry_unit.commands["pidof -x bar || exit 0 && exit 1"] = ("", 1)
159+ result = self.utils.get_process_id_list(
160+ self.sentry_unit, "foo", expect_success=False)
161+ self.assertEqual([], result)
162+ with self.assertRaises(SystemExit) as cm, captured_output() as (
163+ out, err):
164+ self.utils.get_process_id_list(
165+ self.sentry_unit, "bar", expect_success=False)
166+ the_exception = cm.exception
167+ self.assertEqual(1, the_exception.code)
168+ self.assertEqual(
169+ "foo `pidof -x bar || exit 0 && exit 1` returned 1",
170+ out.getvalue().rstrip())
171+
172+
173+class GetUnitProcessIdsTestCase(unittest.TestCase):
174+
175+ def setUp(self):
176+ self.utils = AmuletUtils()
177+ self.sentry_unit = FakeSentry()
178+
179+ def test_returns_map(self):
180+ """
181+ Normal execution returns a dictionary mapping process names to
182+ PIDs for each unit.
183+ """
184+ second_sentry = FakeSentry(name="bar")
185+ self.sentry_unit.commands["pidof -x foo"] = ("123 124", 0)
186+ second_sentry.commands["pidof -x bar"] = ("456 457", 0)
187+
188+ result = self.utils.get_unit_process_ids({
189+ self.sentry_unit: ["foo"], second_sentry: ["bar"]})
190+ self.assertEqual({
191+ self.sentry_unit: {"foo": ["123", "124"]},
192+ second_sentry: {"bar": ["456", "457"]}}, result)
193+
194+ def test_expect_failure(self):
195+ """
196+ Expected failures return empty lists.
197+ """
198+ second_sentry = FakeSentry(name="bar")
199+ self.sentry_unit.commands["pidof -x foo || exit 0 && exit 1"] = ("", 0)
200+ second_sentry.commands["pidof -x bar || exit 0 && exit 1"] = ("", 0)
201+
202+ result = self.utils.get_unit_process_ids(
203+ {self.sentry_unit: ["foo"], second_sentry: ["bar"]},
204+ expect_success=False)
205+ self.assertEqual({
206+ self.sentry_unit: {"foo": []},
207+ second_sentry: {"bar": []}}, result)

Subscribers

People subscribed via source and target branches