Merge lp:~salgado/linaro-image-tools/improve-cmd_runner into lp:linaro-image-tools/11.11

Proposed by Guilherme Salgado
Status: Merged
Merged at revision: 187
Proposed branch: lp:~salgado/linaro-image-tools/improve-cmd_runner
Merge into: lp:linaro-image-tools/11.11
Diff against target: 155 lines (+47/-23)
3 files modified
media_create/cmd_runner.py (+21/-9)
media_create/tests/fixtures.py (+14/-3)
media_create/tests/test_media_create.py (+12/-11)
To merge this branch: bzr merge lp:~salgado/linaro-image-tools/improve-cmd_runner
Reviewer Review Type Date Requested Status
James Westby (community) Approve
Review via email: mp+43272@code.launchpad.net

Description of the change

Create a subprocess.Popen subclass which raises an error when the process
exits with a non-zero return code. This is useful in cases when cmd_runner
can't be used but we still want to raise an error on non-zero exit.

To post a comment you must log in.
Revision history for this message
James Westby (james-w) wrote :

Looks good thanks.

This is assuming that other things than wait() aren't used (e.g. communicate()),
but I think that's ok.

Plus communicate() would be trickier to do like this anyway.

Thanks,

James

review: Approve
Revision history for this message
Guilherme Salgado (salgado) wrote :

The nice thing is that it works with communicate() just fine as communicate() wait()s:

>>> p = Popen('false', stdout=PIPE, stdin=PIPE, stderr=PIPE)
>>> p.communicate('')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/subprocess.py", line 691, in communicate
    return self._communicate(input)
  File "/usr/lib/python2.6/subprocess.py", line 1258, in _communicate
    self.wait()
  File "media_create/cmd_runner.py", line 38, in wait
    raise SubcommandNonZeroReturnValue(self._my_args, returncode)
media_create.cmd_runner.SubcommandNonZeroReturnValue: Sub process "false" returned a non-zero value: 1

Revision history for this message
James Westby (james-w) wrote :

On Thu, 09 Dec 2010 19:39:52 -0000, Guilherme Salgado <email address hidden> wrote:
> The nice thing is that it works with communicate() just fine as communicate() wait()s:
>
> >>> p = Popen('false', stdout=PIPE, stdin=PIPE, stderr=PIPE)
> >>> p.communicate('')
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> File "/usr/lib/python2.6/subprocess.py", line 691, in communicate
> return self._communicate(input)
> File "/usr/lib/python2.6/subprocess.py", line 1258, in _communicate
> self.wait()
> File "media_create/cmd_runner.py", line 38, in wait
> raise SubcommandNonZeroReturnValue(self._my_args, returncode)
> media_create.cmd_runner.SubcommandNonZeroReturnValue: Sub process "false" returned a non-zero value: 1

Ah, ok.

Is it still possible to get the output when using communicate() with
pipes?

I'm not sure we do that anyway right now, but there are cases when you
have to use communicate, with stdout/stderr pipes, and you want to
report the output if the command fails.

Thanks,

James

Revision history for this message
Guilherme Salgado (salgado) wrote :

Yes, communicate is meant to be used only with pipes and it always
returns (stdout, stderr), but when the subprocess returns non-zero
communicate() will raise (via wait()) before it returns, so I'll have to
think of a way to make stdout/stderr available in that case. Maybe just
including them in the exception is good enough?

Revision history for this message
James Westby (james-w) wrote :

On Thu, 09 Dec 2010 20:56:45 -0000, Guilherme Salgado <email address hidden> wrote:
> Yes, communicate is meant to be used only with pipes and it always
> returns (stdout, stderr), but when the subprocess returns non-zero
> communicate() will raise (via wait()) before it returns, so I'll have to
> think of a way to make stdout/stderr available in that case. Maybe just
> including them in the exception is good enough?

That would work.

It can probably wait until we need it though.

Thanks,

James

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'media_create/cmd_runner.py'
2--- media_create/cmd_runner.py 2010-12-03 18:55:10 +0000
3+++ media_create/cmd_runner.py 2010-12-09 19:21:50 +0000
4@@ -16,15 +16,27 @@
5 if as_root:
6 args = args[:]
7 args.insert(0, 'sudo')
8- return_value = do_run(args, stdout=stdout)
9- if return_value != 0:
10- raise SubcommandNonZeroReturnValue(args, return_value)
11- return return_value
12-
13-
14-def do_run(args, stdout=None):
15- """A wrapper around subprocess.call() to make testing easier."""
16- return subprocess.call(args, stdout=stdout)
17+ proc = Popen(args, stdout=stdout)
18+ proc.wait()
19+ return proc.returncode
20+
21+
22+class Popen(subprocess.Popen):
23+ """A version of Popen which raises an error on non-zero returncode.
24+
25+ Once the subprocess completes we check its returncode and raise
26+ SubcommandNonZeroReturnValue if it's non-zero.
27+ """
28+
29+ def __init__(self, args, **kwargs):
30+ self._my_args = args
31+ super(Popen, self).__init__(args, **kwargs)
32+
33+ def wait(self):
34+ returncode = super(Popen, self).wait()
35+ if returncode != 0:
36+ raise SubcommandNonZeroReturnValue(self._my_args, returncode)
37+ return returncode
38
39
40 class SubcommandNonZeroReturnValue(Exception):
41
42=== modified file 'media_create/tests/fixtures.py'
43--- media_create/tests/fixtures.py 2010-12-07 09:15:09 +0000
44+++ media_create/tests/fixtures.py 2010-12-09 19:21:50 +0000
45@@ -70,7 +70,17 @@
46 return 0
47
48
49-class MockDoRunFixture(MockSomethingFixture):
50+class MockCmdRunnerPopen(object):
51+ def __call__(self, args, **kwargs):
52+ self.args = args
53+ self.returncode = 0
54+ return self
55+
56+ def wait(self):
57+ return self.returncode
58+
59+
60+class MockCmdRunnerPopenFixture(MockSomethingFixture):
61 """A test fixture which mocks cmd_runner.do_run with the given mock.
62
63 If no mock is given, MockDoRun is used.
64@@ -78,8 +88,9 @@
65
66 def __init__(self, mock=None):
67 if mock is None:
68- mock = MockDoRun()
69- super(MockDoRunFixture, self).__init__(cmd_runner, 'do_run', mock)
70+ mock = MockCmdRunnerPopen()
71+ super(MockCmdRunnerPopenFixture, self).__init__(
72+ cmd_runner, 'Popen', mock)
73
74
75 class ChangeCurrentWorkingDirFixture(object):
76
77=== modified file 'media_create/tests/test_media_create.py'
78--- media_create/tests/test_media_create.py 2010-12-08 13:09:27 +0000
79+++ media_create/tests/test_media_create.py 2010-12-09 19:21:50 +0000
80@@ -29,7 +29,7 @@
81 ChangeCurrentWorkingDirFixture,
82 CreateTempDirFixture,
83 CreateTarballFixture,
84- MockDoRunFixture,
85+ MockCmdRunnerPopenFixture,
86 MockSomethingFixture,
87 )
88
89@@ -127,14 +127,14 @@
90 class TestCmdRunner(TestCaseWithFixtures):
91
92 def test_run(self):
93- fixture = MockDoRunFixture()
94+ fixture = MockCmdRunnerPopenFixture()
95 self.useFixture(fixture)
96 return_code = cmd_runner.run(['foo', 'bar', 'baz'])
97 self.assertEqual(0, return_code)
98 self.assertEqual(['foo', 'bar', 'baz'], fixture.mock.args)
99
100 def test_run_as_root(self):
101- fixture = MockDoRunFixture()
102+ fixture = MockCmdRunnerPopenFixture()
103 self.useFixture(fixture)
104 cmd_runner.run(['foo', 'bar'], as_root=True)
105 self.assertEqual(['sudo', 'foo', 'bar'], fixture.mock.args)
106@@ -151,9 +151,10 @@
107 def test_run_must_be_given_list_as_args(self):
108 self.assertRaises(AssertionError, cmd_runner.run, 'true')
109
110- def test_do_run(self):
111- return_code = cmd_runner.do_run('true')
112- self.assertEqual(0, return_code)
113+ def test_Popen(self):
114+ proc = cmd_runner.Popen('true')
115+ returncode = proc.wait()
116+ self.assertEqual(0, returncode)
117
118
119 class TestPopulateBoot(TestCaseWithFixtures):
120@@ -163,14 +164,14 @@
121 populate_boot, '_get_file_matching',
122 lambda regex: regex))
123
124- def _mock_do_run(self):
125- fixture = MockDoRunFixture()
126+ def _mock_Popen(self):
127+ fixture = MockCmdRunnerPopenFixture()
128 self.useFixture(fixture)
129 return fixture
130
131 def test_make_uImage(self):
132 self._mock_get_file_matching()
133- fixture = self._mock_do_run()
134+ fixture = self._mock_Popen()
135 make_uImage('load_addr', 'parts_dir', 'sub_arch', 'boot_disk')
136 expected = [
137 'sudo', 'mkimage', '-A', 'arm', '-O', 'linux', '-T', 'kernel',
138@@ -180,7 +181,7 @@
139
140 def test_make_uInitrd(self):
141 self._mock_get_file_matching()
142- fixture = self._mock_do_run()
143+ fixture = self._mock_Popen()
144 make_uInitrd('parts_dir', 'sub_arch', 'boot_disk')
145 expected = [
146 'sudo', 'mkimage', '-A', 'arm', '-O', 'linux', '-T', 'ramdisk',
147@@ -190,7 +191,7 @@
148
149 def test_make_boot_script(self):
150 self._mock_get_file_matching()
151- fixture = self._mock_do_run()
152+ fixture = self._mock_Popen()
153 make_boot_script('boot_script', 'tmp_dir')
154 expected = [
155 'sudo', 'mkimage', '-A', 'arm', '-O', 'linux', '-T', 'script',

Subscribers

People subscribed via source and target branches