Merge lp:~smoser/curtin/trunk.subp-more-tests into lp:~curtin-dev/curtin/trunk

Proposed by Scott Moser
Status: Merged
Merged at revision: 422
Proposed branch: lp:~smoser/curtin/trunk.subp-more-tests
Merge into: lp:~curtin-dev/curtin/trunk
Diff against target: 123 lines (+81/-14)
1 file modified
tests/unittests/test_util.py (+81/-14)
To merge this branch: bzr merge lp:~smoser/curtin/trunk.subp-more-tests
Reviewer Review Type Date Requested Status
Ryan Harper (community) Approve
Server Team CI bot continuous-integration Needs Fixing
Review via email: mp+304297@code.launchpad.net

Commit message

tests: more subp tests

Better test util.subp, exercising its target=/ and target=/some/root, and also verifying that exception is raised if subprocess exits non-zero.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Nice additional tests.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/unittests/test_util.py'
2--- tests/unittests/test_util.py 2016-08-08 18:40:17 +0000
3+++ tests/unittests/test_util.py 2016-08-29 20:00:46 +0000
4@@ -145,6 +145,8 @@
5
6 stdin2err = ['bash', '-c', 'cat >&2']
7 stdin2out = ['cat']
8+ bin_true = ['bash', '-c', ':']
9+ exit_with_value = ['bash', '-c', 'exit ${1:-0}', 'test_subp_exit_val']
10 utf8_invalid = b'ab\xaadef'
11 utf8_valid = b'start \xc3\xa9 end'
12 utf8_valid_2 = b'd\xc3\xa9j\xc8\xa7'
13@@ -161,6 +163,27 @@
14 (out, _err) = util.subp(cmd, capture=True)
15 self.assertEqual(out, self.utf8_valid_2.decode('utf-8'))
16
17+
18+ def test_subp_target_as_different_fors_of_slash_works(self):
19+ # passing target=/ in any form should work.
20+
21+ # it is assumed that if chroot was used, then test case would
22+ # fail unless user was root ('chroot /' is still priviledged)
23+ util.subp(self.bin_true, target="/")
24+ util.subp(self.bin_true, target="//")
25+ util.subp(self.bin_true, target="///")
26+ util.subp(self.bin_true, target="//etc/..//")
27+
28+ def test_subp_exit_nonzero_raises(self):
29+ exc = None
30+ try:
31+ util.subp(self.exit_with_value + ["9"])
32+ except util.ProcessExecutionError as e:
33+ self.assertEqual(9, e.exit_code)
34+ exc = e
35+
36+ self.assertNotEqual(exc, None)
37+
38 def test_subp_respects_decode_false(self):
39 (out, err) = util.subp(self.stdin2out, capture=True, decode=False,
40 data=self.utf8_valid)
41@@ -203,24 +226,68 @@
42 self.assertEqual(err, None)
43 self.assertEqual(out, None)
44
45- @mock.patch("curtin.util.subprocess.Popen")
46- def test_with_target_gets_chroot(self, m_popen):
47- noexist_pname = "mocked-away-does-not-matter"
48- sp = mock.Mock()
49- m_popen.return_value = sp
50- sp.communicate.return_value = (b'out-foo', b'err-foo')
51- sp.returncode = 0
52-
53- ret = util.subp([noexist_pname], target="/mytarget",
54- capture=True, decode=None)
55+ def _subp_wrap_popen(self, cmd, kwargs,
56+ returncode=0, stdout=b'', stderr=b''):
57+ # mocks the subprocess.Popen as expected from subp
58+ # checks that subp returned the output of 'communicate' and
59+ # returns the (args, kwargs) that Popen() was called with.
60+
61+ capture = kwargs.get('capture')
62+
63+ with mock.patch("curtin.util.subprocess.Popen") as m_popen:
64+ sp = mock.Mock()
65+ m_popen.return_value = sp
66+ if capture:
67+ sp.communicate.return_value = (stdout, stderr)
68+ else:
69+ sp.communicate.return_value = (None, None)
70+ sp.returncode = returncode
71+ ret = util.subp(cmd, **kwargs)
72+
73+ # popen should only ever be called once
74 self.assertTrue(m_popen.called)
75 self.assertEqual(1, m_popen.call_count)
76+ # communicate() needs to have been called.
77 self.assertTrue(sp.communicate.called)
78
79- # Popen should have been called with chroot
80- args, kwargs = m_popen.call_args
81- self.assertEqual(['chroot', '/mytarget', noexist_pname], args[0])
82- self.assertEqual((b'out-foo', b'err-foo'), ret)
83+ if capture:
84+ # capture response is decoded if decode is not False
85+ decode = kwargs.get('decode', "replace")
86+ if decode is False:
87+ self.assertEqual(stdout.decode(stdout, stderr), ret)
88+ else:
89+ self.assertEqual((stdout.decode(errors=decode),
90+ stderr.decode(errors=decode)), ret)
91+ else:
92+ # if capture is false, then return is None, None
93+ self.assertEqual((None, None), ret)
94+
95+ popen_args, popen_kwargs = m_popen.call_args
96+
97+ # if target is not provided or is /, chroot should not be used
98+ target = util.target_path(kwargs.get('target', None))
99+ if target == "/":
100+ self.assertEqual(cmd, popen_args[0])
101+ else:
102+ self.assertEqual(['chroot', target] + list(cmd), popen_args[0])
103+ return m_popen.call_args
104+
105+ def test_with_target_gets_chroot(self):
106+ args, kwargs = self._subp_wrap_popen(["my-command"],
107+ {'target': "/mytarget"})
108+ self.assertIn('chroot', args[0])
109+
110+ def test_with_target_as_slash_does_not_chroot(self):
111+ args, kwargs = self._subp_wrap_popen(
112+ ['whatever'], {'capture': True, 'target': "/"})
113+ self.assertNotIn('chroot', args[0])
114+
115+ def test_with_no_target_does_not_chroot(self):
116+ args, kwargs = self._subp_wrap_popen(['whatever'], {'capture': True})
117+ # note this path is reasonably tested with all of the above
118+ # tests that do not mock Popen as if we did try to chroot the
119+ # unit tests would fail unless they were run as root.
120+ self.assertNotIn('chroot', args[0])
121
122
123 class TestHuman2Bytes(TestCase):

Subscribers

People subscribed via source and target branches