OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 | 2 |
3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. | 3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 # | 6 # |
7 # Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se> | 7 # Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se> |
8 # Licensed to PSF under a Contributor Agreement. | 8 # Licensed to PSF under a Contributor Agreement. |
9 # See http://www.python.org/2.4/license for licensing details. | 9 # See http://www.python.org/2.4/license for licensing details. |
10 | 10 |
11 """Subprocress execution | 11 """Subprocress execution |
12 | 12 |
13 This module holds a subclass of subprocess.Popen with our own required | 13 This module holds a subclass of subprocess.Popen with our own required |
14 features. | 14 features. |
15 """ | 15 """ |
16 | 16 |
17 import errno | 17 import errno |
18 import os | 18 import os |
19 import pty | 19 import pty |
20 import select | 20 import select |
21 import subprocess | 21 import subprocess |
22 import sys | 22 import sys |
23 import unittest | 23 import unittest |
24 | 24 |
25 | 25 |
26 # Import these here so the caller does not need to import subprocess also. | |
27 PIPE = subprocess.PIPE | |
28 STDOUT = subprocess.STDOUT | |
29 PIPE_PTY = -3 # Pipe output through a pty | |
30 | |
31 | 26 |
32 class Popen(subprocess.Popen): | 27 class Popen(subprocess.Popen): |
33 """Like subprocess.Popen with ptys and incremental output | 28 """Like subprocess.Popen with ptys and incremental output |
34 | 29 |
35 This class deals with running a child process and filtering its output on | 30 This class deals with running a child process and filtering its output on |
36 both stdout and stderr while it is running. We do this so we can monitor | 31 both stdout and stderr while it is running. We do this so we can monitor |
37 progress, and possibly relay the output to the user if requested. | 32 progress, and possibly relay the output to the user if requested. |
38 | 33 |
39 The class is similar to subprocess.Popen, the equivalent is something like: | 34 The class is similar to subprocess.Popen, the equivalent is something like: |
40 | 35 |
41 Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 36 Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
42 | 37 |
43 But this class has many fewer features, and two enhancement: | 38 But this class has many fewer features, and two enhancement: |
44 | 39 |
45 1. Rather than getting the output data only at the end, this class sends it | 40 1. Rather than getting the output data only at the end, this class sends it |
46 to a provided operation as it arrives. | 41 to a provided operation as it arrives. |
47 2. We use pseudo terminals so that the child will hopefully flush its output | 42 2. We use pseudo terminals so that the child will hopefully flush its output |
48 to us as soon as it is produced, rather than waiting for the end of a | 43 to us as soon as it is produced, rather than waiting for the end of a |
49 line. | 44 line. |
50 | 45 |
51 Use CommunicateFilter() to handle output from the subprocess. | 46 Use CommunicateFilter() to handle output from the subprocess. |
52 | 47 |
53 """ | 48 """ |
| 49 PIPE_PTY = -3 |
54 | 50 |
55 def __init__(self, args, stdin=None, stdout=PIPE_PTY, stderr=PIPE_PTY, | 51 def __init__(self, args, stdin=None, stdout=PIPE_PTY, stderr=PIPE_PTY, |
56 shell=False, cwd=None, env=None, **kwargs): | 52 shell=False, cwd=None, env=None, **kwargs): |
57 """Cut-down constructor | 53 """Cut-down constructor |
58 | 54 |
59 Args: | 55 Args: |
60 args: Program and arguments for subprocess to execute. | 56 args: Program and arguments for subprocess to execute. |
61 stdin: See subprocess.Popen() | 57 stdin: See subprocess.Popen() |
62 stdout: See subprocess.Popen(), except that we support the sentinel | 58 stdout: See subprocess.Popen(), except that we support the sentinel |
63 value of cros_subprocess.PIPE_PTY. | 59 value of cros_subprocess.Popen.PIPE_PTY. |
64 stderr: See subprocess.Popen(), except that we support the sentinel | 60 stderr: See subprocess.Popen(), except that we support the sentinel |
65 value of cros_subprocess.PIPE_PTY. | 61 value of cros_subprocess.Popen.PIPE_PTY. |
66 shell: See subprocess.Popen() | 62 shell: See subprocess.Popen() |
67 cwd: Working directory to change to for subprocess, or None if none. | 63 cwd: Working directory to change to for subprocess, or None if none. |
68 env: Environment to use for this subprocess, or None to inherit parent. | 64 env: Environment to use for this subprocess, or None to inherit parent. |
69 kwargs: No other arguments are supported at the moment. Passing other | 65 kwargs: No other arguments are supported at the moment. Passing other |
70 arguments will cause a ValueError to be raised. | 66 arguments will cause a ValueError to be raised. |
71 """ | 67 """ |
72 stdout_pty = None | 68 stdout_pty = None |
73 stderr_pty = None | 69 stderr_pty = None |
74 | 70 |
75 if stdout == PIPE_PTY: | 71 if stdout == Popen.PIPE_PTY: |
76 stdout_pty = pty.openpty() | 72 stdout_pty = pty.openpty() |
77 stdout = os.fdopen(stdout_pty[1]) | 73 stdout = os.fdopen(stdout_pty[1]) |
78 if stderr == PIPE_PTY: | 74 if stderr == Popen.PIPE_PTY: |
79 stderr_pty = pty.openpty() | 75 stderr_pty = pty.openpty() |
80 stderr = os.fdopen(stderr_pty[1]) | 76 stderr = os.fdopen(stderr_pty[1]) |
81 | 77 |
82 super(Popen, self).__init__(args, stdin=stdin, | 78 super(Popen, self).__init__(args, stdin=stdin, |
83 stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, env=env, | 79 stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, env=env, |
84 **kwargs) | 80 **kwargs) |
85 | 81 |
86 # If we're on a PTY, we passed the slave half of the PTY to the subprocess. | 82 # If we're on a PTY, we passed the slave half of the PTY to the subprocess. |
87 # We want to use the master half on our end from now on. Setting this here | 83 # We want to use the master half on our end from now on. Setting this here |
88 # does make some assumptions about the implementation of subprocess, but | 84 # does make some assumptions about the implementation of subprocess, but |
89 # those assumptions are pretty minor. | 85 # those assumptions are pretty minor. |
90 | |
91 # Note that if stderr is STDOUT, then self.stderr will be set to None by | |
92 # this constructor. | |
93 if stdout_pty is not None: | 86 if stdout_pty is not None: |
94 self.stdout = os.fdopen(stdout_pty[0]) | 87 self.stdout = os.fdopen(stdout_pty[0]) |
95 if stderr_pty is not None: | 88 if stderr_pty is not None: |
96 self.stderr = os.fdopen(stderr_pty[0]) | 89 self.stderr = os.fdopen(stderr_pty[0]) |
97 | 90 |
98 # Insist that unit tests exist for other arguments we don't support. | 91 # Insist that unit tests exist for other arguments we don't support. |
99 if kwargs: | 92 if kwargs: |
100 raise ValueError("Unit tests do not test extra args - please add tests") | 93 raise ValueError("Unit tests do not test extra args - please add tests") |
101 | 94 |
102 def CommunicateFilter(self, output): | 95 def CommunicateFilter(self, operation): |
103 """Interact with process: Read data from stdout and stderr. | 96 """Interact with process: Read data from stdout and stderr. |
104 | 97 |
105 This method runs until end-of-file is reached, then waits for the | 98 This method runs until end-of-file is reached, then waits for the |
106 subprocess to terminate. | 99 subprocess to terminate. |
107 | 100 |
108 The output function is sent all output from the subprocess and must be | 101 The operation object is send all output from the subprocess through its |
109 defined like this: | 102 Output() method which must be defined like this: |
110 | 103 |
111 def Output([self,] stream, data) | 104 def Output(self, stream, data) |
112 Args: | 105 Args: |
113 stream: the stream the output was received on, which will be | 106 stream: the stream the output was received on, which will be |
114 sys.stdout or sys.stderr. | 107 sys.stdout or sys.stderr. |
115 data: a string containing the data | 108 data: a string containing the data |
116 | 109 |
117 Note: The data read is buffered in memory, so do not use this | 110 Note: The data read is buffered in memory, so do not use this |
118 method if the data size is large or unlimited. | 111 method if the data size is large or unlimited. |
119 | 112 |
120 Args: | 113 Args: |
121 output: Function to call with each fragment of output. | 114 operation: Operation to use for this subprocess. |
122 | 115 |
123 Returns: | 116 Returns: |
124 A tuple (stdout, stderr, combined) which is the data received on | 117 A tuple (stdout, stderr, combined) which is the data received on |
125 stdout, stderr and the combined data (interleaved stdout and stderr). | 118 stdout, stderr and the combined data (interleaved stdout and stderr). |
126 | |
127 Note that the interleaved output will only be sensible if you have | |
128 set both stdout and stderr to PIPE or PIPE_PTY. Even then it depends on | |
129 the timing of the output in the subprocess. If a subprocess flips | |
130 between stdout and stderr quickly in succession, by the time we come to | |
131 read the output from each we may see several lines in each, and will read | |
132 all the stdout lines, then all the stderr lines. So the interleaving | |
133 may not be correct. In this case you might want to pass | |
134 stderr=cros_subprocess.STDOUT to the constructor. | |
135 | |
136 This feature is still useful for subprocesses where stderr is | |
137 rarely used and indicates an error. | |
138 | |
139 Note also that if you set stderr to STDOUT, then stderr will be empty | |
140 and the combined output will just be the same as stdout. | |
141 """ | 119 """ |
142 | 120 |
143 read_set = [] | 121 read_set = [] |
144 write_set = [] | 122 write_set = [] |
145 stdout = None # Return | 123 stdout = None # Return |
146 stderr = None # Return | 124 stderr = None # Return |
147 | 125 |
148 if self.stdin: | 126 if self.stdin: |
149 # Flush stdio buffer. This might block, if the user has | 127 # Flush stdio buffer. This might block, if the user has |
150 # been writing to .stdin in an uncontrolled fashion. | 128 # been writing to .stdin in an uncontrolled fashion. |
151 self.stdin.flush() | 129 self.stdin.flush() |
152 if input: | 130 if input: |
153 write_set.append(self.stdin) | 131 write_set.append(self.stdin) |
154 else: | 132 else: |
155 self.stdin.close() | 133 self.stdin.close() |
156 if self.stdout: | 134 if self.stdout: |
157 read_set.append(self.stdout) | 135 read_set.append(self.stdout) |
158 stdout = [] | 136 stdout = [] |
159 if self.stderr and self.stderr != self.stdout: | 137 if self.stderr: |
160 read_set.append(self.stderr) | 138 read_set.append(self.stderr) |
161 stderr = [] | 139 stderr = [] |
162 combined = [] | 140 combined = [] |
163 | 141 |
164 input_offset = 0 | 142 input_offset = 0 |
165 while read_set or write_set: | 143 while read_set or write_set: |
166 try: | 144 try: |
167 rlist, wlist, _ = select.select(read_set, write_set, []) | 145 rlist, wlist, _ = select.select(read_set, write_set, []) |
168 except select.error, e: | 146 except select.error, e: |
169 if e.args[0] == errno.EINTR: | 147 if e.args[0] == errno.EINTR: |
(...skipping 17 matching lines...) Expand all Loading... |
187 try: | 165 try: |
188 data = os.read(self.stdout.fileno(), 1024) | 166 data = os.read(self.stdout.fileno(), 1024) |
189 except OSError: | 167 except OSError: |
190 pass | 168 pass |
191 if data == "": | 169 if data == "": |
192 self.stdout.close() | 170 self.stdout.close() |
193 read_set.remove(self.stdout) | 171 read_set.remove(self.stdout) |
194 else: | 172 else: |
195 stdout.append(data) | 173 stdout.append(data) |
196 combined.append(data) | 174 combined.append(data) |
197 output(sys.stdout, data) | 175 operation.Output(sys.stdout, data) |
198 if self.stderr in rlist: | 176 if self.stderr in rlist: |
199 data = "" | 177 data = "" |
200 # We will get an error on read if the pty is closed | 178 # We will get an error on read if the pty is closed |
201 try: | 179 try: |
202 data = os.read(self.stderr.fileno(), 1024) | 180 data = os.read(self.stderr.fileno(), 1024) |
203 except OSError: | 181 except OSError: |
204 pass | 182 pass |
205 if data == "": | 183 if data == "": |
206 self.stderr.close() | 184 self.stderr.close() |
207 read_set.remove(self.stderr) | 185 read_set.remove(self.stderr) |
208 else: | 186 else: |
209 stderr.append(data) | 187 stderr.append(data) |
210 combined.append(data) | 188 combined.append(data) |
211 output(sys.stderr, data) | 189 operation.Output(sys.stderr, data) |
212 | 190 |
213 # All data exchanged. Translate lists into strings. | 191 # All data exchanged. Translate lists into strings. |
214 if stdout is not None: | 192 if stdout is not None: |
215 stdout = ''.join(stdout) | 193 stdout = ''.join(stdout) |
216 if stderr is not None: | 194 if stderr is not None: |
217 stderr = ''.join(stderr) | 195 stderr = ''.join(stderr) |
218 combined = ''.join(combined) | 196 combined = ''.join(combined) |
219 | 197 |
220 # Translate newlines, if requested. We cannot let the file | 198 # Translate newlines, if requested. We cannot let the file |
221 # object do the translation: It is based on stdio, which is | 199 # object do the translation: It is based on stdio, which is |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
276 self.assertEqual(plist[0], oper.stdout_data) | 254 self.assertEqual(plist[0], oper.stdout_data) |
277 self.assertEqual(plist[1], oper.stderr_data) | 255 self.assertEqual(plist[1], oper.stderr_data) |
278 self.assertEqual(plist[2], oper.combined_data) | 256 self.assertEqual(plist[2], oper.combined_data) |
279 | 257 |
280 # The total length of stdout and stderr should equal the combined length | 258 # The total length of stdout and stderr should equal the combined length |
281 self.assertEqual(len(plist[0]) + len(plist[1]), len(plist[2])) | 259 self.assertEqual(len(plist[0]) + len(plist[1]), len(plist[2])) |
282 | 260 |
283 def test_simple(self): | 261 def test_simple(self): |
284 """Simple redirection: Get process list""" | 262 """Simple redirection: Get process list""" |
285 oper = TestSubprocess.MyOperation() | 263 oper = TestSubprocess.MyOperation() |
286 plist = Popen(['ps']).CommunicateFilter(oper.Output) | 264 plist = Popen(['ps']).CommunicateFilter(oper) |
287 self._BasicCheck(plist, oper) | 265 self._BasicCheck(plist, oper) |
288 | 266 |
289 def test_stderr(self): | 267 def test_stderr(self): |
290 """Check stdout and stderr""" | 268 """Check stdout and stderr""" |
291 oper = TestSubprocess.MyOperation() | 269 oper = TestSubprocess.MyOperation() |
292 cmd = 'echo fred >/dev/stderr && false || echo bad' | 270 cmd = 'echo fred >/dev/stderr && false || echo bad' |
293 plist = Popen([cmd], shell=True).CommunicateFilter(oper.Output) | 271 plist = Popen([cmd], shell=True).CommunicateFilter(oper) |
294 self._BasicCheck(plist, oper) | 272 self._BasicCheck(plist, oper) |
295 self.assertEqual(plist [0], 'bad\r\n') | 273 self.assertEqual(plist [0], 'bad\r\n') |
296 self.assertEqual(plist [1], 'fred\r\n') | 274 self.assertEqual(plist [1], 'fred\r\n') |
297 | 275 |
298 def test_shell(self): | 276 def test_shell(self): |
299 """Check with and without shell works""" | 277 """Check with and without shell works""" |
300 oper = TestSubprocess.MyOperation() | 278 oper = TestSubprocess.MyOperation() |
301 cmd = 'echo test >/dev/stderr' | 279 cmd = 'echo test >/dev/stderr' |
302 self.assertRaises(OSError, Popen, [cmd], shell=False) | 280 self.assertRaises(OSError, Popen, [cmd], shell=False) |
303 plist = Popen([cmd], shell=True).CommunicateFilter(oper.Output) | 281 plist = Popen([cmd], shell=True).CommunicateFilter(oper) |
304 self._BasicCheck(plist, oper) | 282 self._BasicCheck(plist, oper) |
305 self.assertEqual(len(plist [0]), 0) | 283 self.assertEqual(len(plist [0]), 0) |
306 self.assertEqual(plist [1], 'test\r\n') | 284 self.assertEqual(plist [1], 'test\r\n') |
307 | 285 |
308 def test_list_args(self): | 286 def test_list_args(self): |
309 """Check with and without shell works using list arguments""" | 287 """Check with and without shell works using list arguments""" |
310 oper = TestSubprocess.MyOperation() | 288 oper = TestSubprocess.MyOperation() |
311 cmd = ['echo', 'test', '>/dev/stderr'] | 289 cmd = ['echo', 'test', '>/dev/stderr'] |
312 plist = Popen(cmd, shell=False).CommunicateFilter(oper.Output) | 290 plist = Popen(cmd, shell=False).CommunicateFilter(oper) |
313 self._BasicCheck(plist, oper) | 291 self._BasicCheck(plist, oper) |
314 self.assertEqual(plist [0], ' '.join(cmd[1:]) + '\r\n') | 292 self.assertEqual(plist [0], ' '.join(cmd[1:]) + '\r\n') |
315 self.assertEqual(len(plist [1]), 0) | 293 self.assertEqual(len(plist [1]), 0) |
316 | 294 |
317 oper = TestSubprocess.MyOperation() | 295 oper = TestSubprocess.MyOperation() |
318 | 296 |
319 # this should be interpreted as 'echo' with the other args dropped | 297 # this should be interpreted as 'echo' with the other args dropped |
320 cmd = ['echo', 'test', '>/dev/stderr'] | 298 cmd = ['echo', 'test', '>/dev/stderr'] |
321 plist = Popen(cmd, shell=True).CommunicateFilter(oper.Output) | 299 plist = Popen(cmd, shell=True).CommunicateFilter(oper) |
322 self._BasicCheck(plist, oper) | 300 self._BasicCheck(plist, oper) |
323 self.assertEqual(plist [0], '\r\n') | 301 self.assertEqual(plist [0], '\r\n') |
324 | 302 |
325 def test_cwd(self): | 303 def test_cwd(self): |
326 """Check we can change directory""" | 304 """Check we can change directory""" |
327 for shell in (False, True): | 305 for shell in (False, True): |
328 oper = TestSubprocess.MyOperation() | 306 oper = TestSubprocess.MyOperation() |
329 plist = Popen('pwd', shell=shell, cwd='/tmp').CommunicateFilter(oper.Outpu
t) | 307 plist = Popen('pwd', shell=shell, cwd='/tmp').CommunicateFilter(oper) |
330 self._BasicCheck(plist, oper) | 308 self._BasicCheck(plist, oper) |
331 self.assertEqual(plist [0], '/tmp\r\n') | 309 self.assertEqual(plist [0], '/tmp\r\n') |
332 | 310 |
333 def test_env(self): | 311 def test_env(self): |
334 """Check we can change environment""" | 312 """Check we can change environment""" |
335 for add in (False, True): | 313 for add in (False, True): |
336 oper = TestSubprocess.MyOperation() | 314 oper = TestSubprocess.MyOperation() |
337 env = os.environ | 315 env = os.environ |
338 if add: | 316 if add: |
339 env ['FRED'] = 'fred' | 317 env ['FRED'] = 'fred' |
340 cmd = 'echo $FRED' | 318 cmd = 'echo $FRED' |
341 plist = Popen(cmd, shell=True, env=env).CommunicateFilter(oper.Output) | 319 plist = Popen(cmd, shell=True, env=env).CommunicateFilter(oper) |
342 self._BasicCheck(plist, oper) | 320 self._BasicCheck(plist, oper) |
343 self.assertEqual(plist [0], add and 'fred\r\n' or '\r\n') | 321 self.assertEqual(plist [0], add and 'fred\r\n' or '\r\n') |
344 | 322 |
345 def test_extra_args(self): | 323 def test_extra_args(self): |
346 """Check we can't add extra arguments""" | 324 """Check we can't add extra arguments""" |
347 self.assertRaises(ValueError, Popen, 'true', close_fds=False) | 325 self.assertRaises(ValueError, Popen, 'true', close_fds=False) |
348 | 326 |
349 def test_basic_input(self): | 327 def test_basic_input(self): |
350 """Check that incremental input works | 328 """Check that incremental input works |
351 | 329 |
352 We set up a subprocess which will prompt for name. When we see this prompt | 330 We set up a subprocess which will prompt for name. When we see this prompt |
353 we send the name as input to the process. It should then print the name | 331 we send the name as input to the process. It should then print the name |
354 properly to stdout. | 332 properly to stdout. |
355 """ | 333 """ |
356 oper = TestSubprocess.MyOperation('Flash') | 334 oper = TestSubprocess.MyOperation('Flash') |
357 prompt = 'What is your name?: ' | 335 prompt = 'What is your name?: ' |
358 cmd = 'echo -n "%s"; read name; echo Hello $name' % prompt | 336 cmd = 'echo -n "%s"; read name; echo Hello $name' % prompt |
359 plist = Popen([cmd], stdin=oper.stdin_read_pipe, | 337 plist = Popen([cmd], stdin=oper.stdin_read_pipe, |
360 shell=True).CommunicateFilter(oper.Output) | 338 shell=True).CommunicateFilter(oper) |
361 self._BasicCheck(plist, oper) | 339 self._BasicCheck(plist, oper) |
362 self.assertEqual(len(plist [1]), 0) | 340 self.assertEqual(len(plist [1]), 0) |
363 self.assertEqual(plist [0], prompt + 'Hello Flash\r\r\n') | 341 self.assertEqual(plist [0], prompt + 'Hello Flash\r\r\n') |
364 | 342 |
365 #TODO(sjg): Add test for passing PIPE in case underlying subprocess breaks. | 343 #TODO(sjg): Add test for passing PIPE in case underlying subprocess breaks. |
366 #TODO(sjg): Add test for passing a file handle also. | 344 #TODO(sjg): Add test for passing a file handle also. |
367 | 345 |
368 def test_isatty(self): | 346 def test_isatty(self): |
369 """Check that ptys appear as terminals to the subprocess""" | 347 """Check that ptys appear as terminals to the subprocess""" |
370 oper = TestSubprocess.MyOperation() | 348 oper = TestSubprocess.MyOperation() |
371 cmd = ('if [ -t %d ]; then echo "terminal %d" >&%d; ' | 349 cmd = ('if [ -t %d ]; then echo "terminal %d" >&%d; ' |
372 'else echo "not %d" >&%d; fi;') | 350 'else echo "not %d" >&%d; fi;') |
373 both_cmds = '' | 351 both_cmds = '' |
374 for fd in (1, 2): | 352 for fd in (1, 2): |
375 both_cmds += cmd % (fd, fd, fd, fd, fd) | 353 both_cmds += cmd % (fd, fd, fd, fd, fd) |
376 plist = Popen(both_cmds, shell=True).CommunicateFilter(oper.Output) | 354 plist = Popen(both_cmds, shell=True).CommunicateFilter(oper) |
377 self._BasicCheck(plist, oper) | 355 self._BasicCheck(plist, oper) |
378 self.assertEqual(plist [0], 'terminal 1\r\n') | 356 self.assertEqual(plist [0], 'terminal 1\r\n') |
379 self.assertEqual(plist [1], 'terminal 2\r\n') | 357 self.assertEqual(plist [1], 'terminal 2\r\n') |
380 | 358 |
381 # Now try with PIPE and make sure it is not a terminal | 359 # Now try with PIPE and make sure it is not a terminal |
382 oper = TestSubprocess.MyOperation() | 360 oper = TestSubprocess.MyOperation() |
383 plist = Popen(both_cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | 361 plist = Popen(both_cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
384 shell=True).CommunicateFilter(oper.Output) | 362 shell=True).CommunicateFilter(oper) |
385 self._BasicCheck(plist, oper) | 363 self._BasicCheck(plist, oper) |
386 self.assertEqual(plist [0], 'not 1\n') | 364 self.assertEqual(plist [0], 'not 1\n') |
387 self.assertEqual(plist [1], 'not 2\n') | 365 self.assertEqual(plist [1], 'not 2\n') |
388 | 366 |
389 if __name__ == '__main__': | 367 if __name__ == '__main__': |
390 unittest.main() | 368 unittest.main() |
OLD | NEW |