171162e3cSSimon Glass# Copyright (c) 2012 The Chromium OS Authors. 271162e3cSSimon Glass# Use of this source code is governed by a BSD-style license that can be 371162e3cSSimon Glass# found in the LICENSE file. 471162e3cSSimon Glass# 571162e3cSSimon Glass# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se> 671162e3cSSimon Glass# Licensed to PSF under a Contributor Agreement. 771162e3cSSimon Glass# See http://www.python.org/2.4/license for licensing details. 871162e3cSSimon Glass 971162e3cSSimon Glass"""Subprocress execution 1071162e3cSSimon Glass 1171162e3cSSimon GlassThis module holds a subclass of subprocess.Popen with our own required 1271162e3cSSimon Glassfeatures, mainly that we get access to the subprocess output while it 1371162e3cSSimon Glassis running rather than just at the end. This makes it easiler to show 1471162e3cSSimon Glassprogress information and filter output in real time. 1571162e3cSSimon Glass""" 1671162e3cSSimon Glass 1771162e3cSSimon Glassimport errno 1871162e3cSSimon Glassimport os 1971162e3cSSimon Glassimport pty 2071162e3cSSimon Glassimport select 2171162e3cSSimon Glassimport subprocess 2271162e3cSSimon Glassimport sys 2371162e3cSSimon Glassimport unittest 2471162e3cSSimon Glass 2571162e3cSSimon Glass 2671162e3cSSimon Glass# Import these here so the caller does not need to import subprocess also. 2771162e3cSSimon GlassPIPE = subprocess.PIPE 2871162e3cSSimon GlassSTDOUT = subprocess.STDOUT 2971162e3cSSimon GlassPIPE_PTY = -3 # Pipe output through a pty 3071162e3cSSimon Glassstay_alive = True 3171162e3cSSimon Glass 3271162e3cSSimon Glass 3371162e3cSSimon Glassclass Popen(subprocess.Popen): 3471162e3cSSimon Glass """Like subprocess.Popen with ptys and incremental output 3571162e3cSSimon Glass 3671162e3cSSimon Glass This class deals with running a child process and filtering its output on 3771162e3cSSimon Glass both stdout and stderr while it is running. We do this so we can monitor 3871162e3cSSimon Glass progress, and possibly relay the output to the user if requested. 3971162e3cSSimon Glass 4071162e3cSSimon Glass The class is similar to subprocess.Popen, the equivalent is something like: 4171162e3cSSimon Glass 4271162e3cSSimon Glass Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 4371162e3cSSimon Glass 4471162e3cSSimon Glass But this class has many fewer features, and two enhancement: 4571162e3cSSimon Glass 4671162e3cSSimon Glass 1. Rather than getting the output data only at the end, this class sends it 4771162e3cSSimon Glass to a provided operation as it arrives. 4871162e3cSSimon Glass 2. We use pseudo terminals so that the child will hopefully flush its output 4971162e3cSSimon Glass to us as soon as it is produced, rather than waiting for the end of a 5071162e3cSSimon Glass line. 5171162e3cSSimon Glass 5271162e3cSSimon Glass Use CommunicateFilter() to handle output from the subprocess. 5371162e3cSSimon Glass 5471162e3cSSimon Glass """ 5571162e3cSSimon Glass 5671162e3cSSimon Glass def __init__(self, args, stdin=None, stdout=PIPE_PTY, stderr=PIPE_PTY, 5771162e3cSSimon Glass shell=False, cwd=None, env=None, **kwargs): 5871162e3cSSimon Glass """Cut-down constructor 5971162e3cSSimon Glass 6071162e3cSSimon Glass Args: 6171162e3cSSimon Glass args: Program and arguments for subprocess to execute. 6271162e3cSSimon Glass stdin: See subprocess.Popen() 6371162e3cSSimon Glass stdout: See subprocess.Popen(), except that we support the sentinel 6471162e3cSSimon Glass value of cros_subprocess.PIPE_PTY. 6571162e3cSSimon Glass stderr: See subprocess.Popen(), except that we support the sentinel 6671162e3cSSimon Glass value of cros_subprocess.PIPE_PTY. 6771162e3cSSimon Glass shell: See subprocess.Popen() 6871162e3cSSimon Glass cwd: Working directory to change to for subprocess, or None if none. 6971162e3cSSimon Glass env: Environment to use for this subprocess, or None to inherit parent. 7071162e3cSSimon Glass kwargs: No other arguments are supported at the moment. Passing other 7171162e3cSSimon Glass arguments will cause a ValueError to be raised. 7271162e3cSSimon Glass """ 7371162e3cSSimon Glass stdout_pty = None 7471162e3cSSimon Glass stderr_pty = None 7571162e3cSSimon Glass 7671162e3cSSimon Glass if stdout == PIPE_PTY: 7771162e3cSSimon Glass stdout_pty = pty.openpty() 7871162e3cSSimon Glass stdout = os.fdopen(stdout_pty[1]) 7971162e3cSSimon Glass if stderr == PIPE_PTY: 8071162e3cSSimon Glass stderr_pty = pty.openpty() 8171162e3cSSimon Glass stderr = os.fdopen(stderr_pty[1]) 8271162e3cSSimon Glass 8371162e3cSSimon Glass super(Popen, self).__init__(args, stdin=stdin, 8471162e3cSSimon Glass stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, env=env, 8571162e3cSSimon Glass **kwargs) 8671162e3cSSimon Glass 8771162e3cSSimon Glass # If we're on a PTY, we passed the slave half of the PTY to the subprocess. 8871162e3cSSimon Glass # We want to use the master half on our end from now on. Setting this here 8971162e3cSSimon Glass # does make some assumptions about the implementation of subprocess, but 9071162e3cSSimon Glass # those assumptions are pretty minor. 9171162e3cSSimon Glass 9271162e3cSSimon Glass # Note that if stderr is STDOUT, then self.stderr will be set to None by 9371162e3cSSimon Glass # this constructor. 9471162e3cSSimon Glass if stdout_pty is not None: 9571162e3cSSimon Glass self.stdout = os.fdopen(stdout_pty[0]) 9671162e3cSSimon Glass if stderr_pty is not None: 9771162e3cSSimon Glass self.stderr = os.fdopen(stderr_pty[0]) 9871162e3cSSimon Glass 9971162e3cSSimon Glass # Insist that unit tests exist for other arguments we don't support. 10071162e3cSSimon Glass if kwargs: 10171162e3cSSimon Glass raise ValueError("Unit tests do not test extra args - please add tests") 10271162e3cSSimon Glass 10371162e3cSSimon Glass def CommunicateFilter(self, output): 10471162e3cSSimon Glass """Interact with process: Read data from stdout and stderr. 10571162e3cSSimon Glass 10671162e3cSSimon Glass This method runs until end-of-file is reached, then waits for the 10771162e3cSSimon Glass subprocess to terminate. 10871162e3cSSimon Glass 10971162e3cSSimon Glass The output function is sent all output from the subprocess and must be 11071162e3cSSimon Glass defined like this: 11171162e3cSSimon Glass 11271162e3cSSimon Glass def Output([self,] stream, data) 11371162e3cSSimon Glass Args: 11471162e3cSSimon Glass stream: the stream the output was received on, which will be 11571162e3cSSimon Glass sys.stdout or sys.stderr. 11671162e3cSSimon Glass data: a string containing the data 11771162e3cSSimon Glass 11871162e3cSSimon Glass Note: The data read is buffered in memory, so do not use this 11971162e3cSSimon Glass method if the data size is large or unlimited. 12071162e3cSSimon Glass 12171162e3cSSimon Glass Args: 12271162e3cSSimon Glass output: Function to call with each fragment of output. 12371162e3cSSimon Glass 12471162e3cSSimon Glass Returns: 12571162e3cSSimon Glass A tuple (stdout, stderr, combined) which is the data received on 12671162e3cSSimon Glass stdout, stderr and the combined data (interleaved stdout and stderr). 12771162e3cSSimon Glass 12871162e3cSSimon Glass Note that the interleaved output will only be sensible if you have 12971162e3cSSimon Glass set both stdout and stderr to PIPE or PIPE_PTY. Even then it depends on 13071162e3cSSimon Glass the timing of the output in the subprocess. If a subprocess flips 13171162e3cSSimon Glass between stdout and stderr quickly in succession, by the time we come to 13271162e3cSSimon Glass read the output from each we may see several lines in each, and will read 13371162e3cSSimon Glass all the stdout lines, then all the stderr lines. So the interleaving 13471162e3cSSimon Glass may not be correct. In this case you might want to pass 13571162e3cSSimon Glass stderr=cros_subprocess.STDOUT to the constructor. 13671162e3cSSimon Glass 13771162e3cSSimon Glass This feature is still useful for subprocesses where stderr is 13871162e3cSSimon Glass rarely used and indicates an error. 13971162e3cSSimon Glass 14071162e3cSSimon Glass Note also that if you set stderr to STDOUT, then stderr will be empty 14171162e3cSSimon Glass and the combined output will just be the same as stdout. 14271162e3cSSimon Glass """ 14371162e3cSSimon Glass 14471162e3cSSimon Glass read_set = [] 14571162e3cSSimon Glass write_set = [] 14671162e3cSSimon Glass stdout = None # Return 14771162e3cSSimon Glass stderr = None # Return 14871162e3cSSimon Glass 14971162e3cSSimon Glass if self.stdin: 15071162e3cSSimon Glass # Flush stdio buffer. This might block, if the user has 15171162e3cSSimon Glass # been writing to .stdin in an uncontrolled fashion. 15271162e3cSSimon Glass self.stdin.flush() 15371162e3cSSimon Glass if input: 15471162e3cSSimon Glass write_set.append(self.stdin) 15571162e3cSSimon Glass else: 15671162e3cSSimon Glass self.stdin.close() 15771162e3cSSimon Glass if self.stdout: 15871162e3cSSimon Glass read_set.append(self.stdout) 15971162e3cSSimon Glass stdout = [] 16071162e3cSSimon Glass if self.stderr and self.stderr != self.stdout: 16171162e3cSSimon Glass read_set.append(self.stderr) 16271162e3cSSimon Glass stderr = [] 16371162e3cSSimon Glass combined = [] 16471162e3cSSimon Glass 16571162e3cSSimon Glass input_offset = 0 16671162e3cSSimon Glass while read_set or write_set: 16771162e3cSSimon Glass try: 16871162e3cSSimon Glass rlist, wlist, _ = select.select(read_set, write_set, [], 0.2) 169*ac3fde93SPaul Burton except select.error as e: 17071162e3cSSimon Glass if e.args[0] == errno.EINTR: 17171162e3cSSimon Glass continue 17271162e3cSSimon Glass raise 17371162e3cSSimon Glass 17471162e3cSSimon Glass if not stay_alive: 17571162e3cSSimon Glass self.terminate() 17671162e3cSSimon Glass 17771162e3cSSimon Glass if self.stdin in wlist: 17871162e3cSSimon Glass # When select has indicated that the file is writable, 17971162e3cSSimon Glass # we can write up to PIPE_BUF bytes without risk 18071162e3cSSimon Glass # blocking. POSIX defines PIPE_BUF >= 512 18171162e3cSSimon Glass chunk = input[input_offset : input_offset + 512] 18271162e3cSSimon Glass bytes_written = os.write(self.stdin.fileno(), chunk) 18371162e3cSSimon Glass input_offset += bytes_written 18471162e3cSSimon Glass if input_offset >= len(input): 18571162e3cSSimon Glass self.stdin.close() 18671162e3cSSimon Glass write_set.remove(self.stdin) 18771162e3cSSimon Glass 18871162e3cSSimon Glass if self.stdout in rlist: 18971162e3cSSimon Glass data = "" 19071162e3cSSimon Glass # We will get an error on read if the pty is closed 19171162e3cSSimon Glass try: 19271162e3cSSimon Glass data = os.read(self.stdout.fileno(), 1024) 19371162e3cSSimon Glass except OSError: 19471162e3cSSimon Glass pass 19571162e3cSSimon Glass if data == "": 19671162e3cSSimon Glass self.stdout.close() 19771162e3cSSimon Glass read_set.remove(self.stdout) 19871162e3cSSimon Glass else: 19971162e3cSSimon Glass stdout.append(data) 20071162e3cSSimon Glass combined.append(data) 20171162e3cSSimon Glass if output: 20271162e3cSSimon Glass output(sys.stdout, data) 20371162e3cSSimon Glass if self.stderr in rlist: 20471162e3cSSimon Glass data = "" 20571162e3cSSimon Glass # We will get an error on read if the pty is closed 20671162e3cSSimon Glass try: 20771162e3cSSimon Glass data = os.read(self.stderr.fileno(), 1024) 20871162e3cSSimon Glass except OSError: 20971162e3cSSimon Glass pass 21071162e3cSSimon Glass if data == "": 21171162e3cSSimon Glass self.stderr.close() 21271162e3cSSimon Glass read_set.remove(self.stderr) 21371162e3cSSimon Glass else: 21471162e3cSSimon Glass stderr.append(data) 21571162e3cSSimon Glass combined.append(data) 21671162e3cSSimon Glass if output: 21771162e3cSSimon Glass output(sys.stderr, data) 21871162e3cSSimon Glass 21971162e3cSSimon Glass # All data exchanged. Translate lists into strings. 22071162e3cSSimon Glass if stdout is not None: 22171162e3cSSimon Glass stdout = ''.join(stdout) 22271162e3cSSimon Glass else: 22371162e3cSSimon Glass stdout = '' 22471162e3cSSimon Glass if stderr is not None: 22571162e3cSSimon Glass stderr = ''.join(stderr) 22671162e3cSSimon Glass else: 22771162e3cSSimon Glass stderr = '' 22871162e3cSSimon Glass combined = ''.join(combined) 22971162e3cSSimon Glass 23071162e3cSSimon Glass # Translate newlines, if requested. We cannot let the file 23171162e3cSSimon Glass # object do the translation: It is based on stdio, which is 23271162e3cSSimon Glass # impossible to combine with select (unless forcing no 23371162e3cSSimon Glass # buffering). 23471162e3cSSimon Glass if self.universal_newlines and hasattr(file, 'newlines'): 23571162e3cSSimon Glass if stdout: 23671162e3cSSimon Glass stdout = self._translate_newlines(stdout) 23771162e3cSSimon Glass if stderr: 23871162e3cSSimon Glass stderr = self._translate_newlines(stderr) 23971162e3cSSimon Glass 24071162e3cSSimon Glass self.wait() 24171162e3cSSimon Glass return (stdout, stderr, combined) 24271162e3cSSimon Glass 24371162e3cSSimon Glass 24471162e3cSSimon Glass# Just being a unittest.TestCase gives us 14 public methods. Unless we 24571162e3cSSimon Glass# disable this, we can only have 6 tests in a TestCase. That's not enough. 24671162e3cSSimon Glass# 24771162e3cSSimon Glass# pylint: disable=R0904 24871162e3cSSimon Glass 24971162e3cSSimon Glassclass TestSubprocess(unittest.TestCase): 25071162e3cSSimon Glass """Our simple unit test for this module""" 25171162e3cSSimon Glass 25271162e3cSSimon Glass class MyOperation: 25371162e3cSSimon Glass """Provides a operation that we can pass to Popen""" 25471162e3cSSimon Glass def __init__(self, input_to_send=None): 25571162e3cSSimon Glass """Constructor to set up the operation and possible input. 25671162e3cSSimon Glass 25771162e3cSSimon Glass Args: 25871162e3cSSimon Glass input_to_send: a text string to send when we first get input. We will 25971162e3cSSimon Glass add \r\n to the string. 26071162e3cSSimon Glass """ 26171162e3cSSimon Glass self.stdout_data = '' 26271162e3cSSimon Glass self.stderr_data = '' 26371162e3cSSimon Glass self.combined_data = '' 26471162e3cSSimon Glass self.stdin_pipe = None 26571162e3cSSimon Glass self._input_to_send = input_to_send 26671162e3cSSimon Glass if input_to_send: 26771162e3cSSimon Glass pipe = os.pipe() 26871162e3cSSimon Glass self.stdin_read_pipe = pipe[0] 26971162e3cSSimon Glass self._stdin_write_pipe = os.fdopen(pipe[1], 'w') 27071162e3cSSimon Glass 27171162e3cSSimon Glass def Output(self, stream, data): 27271162e3cSSimon Glass """Output handler for Popen. Stores the data for later comparison""" 27371162e3cSSimon Glass if stream == sys.stdout: 27471162e3cSSimon Glass self.stdout_data += data 27571162e3cSSimon Glass if stream == sys.stderr: 27671162e3cSSimon Glass self.stderr_data += data 27771162e3cSSimon Glass self.combined_data += data 27871162e3cSSimon Glass 27971162e3cSSimon Glass # Output the input string if we have one. 28071162e3cSSimon Glass if self._input_to_send: 28171162e3cSSimon Glass self._stdin_write_pipe.write(self._input_to_send + '\r\n') 28271162e3cSSimon Glass self._stdin_write_pipe.flush() 28371162e3cSSimon Glass 28471162e3cSSimon Glass def _BasicCheck(self, plist, oper): 28571162e3cSSimon Glass """Basic checks that the output looks sane.""" 28671162e3cSSimon Glass self.assertEqual(plist[0], oper.stdout_data) 28771162e3cSSimon Glass self.assertEqual(plist[1], oper.stderr_data) 28871162e3cSSimon Glass self.assertEqual(plist[2], oper.combined_data) 28971162e3cSSimon Glass 29071162e3cSSimon Glass # The total length of stdout and stderr should equal the combined length 29171162e3cSSimon Glass self.assertEqual(len(plist[0]) + len(plist[1]), len(plist[2])) 29271162e3cSSimon Glass 29371162e3cSSimon Glass def test_simple(self): 29471162e3cSSimon Glass """Simple redirection: Get process list""" 29571162e3cSSimon Glass oper = TestSubprocess.MyOperation() 29671162e3cSSimon Glass plist = Popen(['ps']).CommunicateFilter(oper.Output) 29771162e3cSSimon Glass self._BasicCheck(plist, oper) 29871162e3cSSimon Glass 29971162e3cSSimon Glass def test_stderr(self): 30071162e3cSSimon Glass """Check stdout and stderr""" 30171162e3cSSimon Glass oper = TestSubprocess.MyOperation() 30271162e3cSSimon Glass cmd = 'echo fred >/dev/stderr && false || echo bad' 30371162e3cSSimon Glass plist = Popen([cmd], shell=True).CommunicateFilter(oper.Output) 30471162e3cSSimon Glass self._BasicCheck(plist, oper) 30571162e3cSSimon Glass self.assertEqual(plist [0], 'bad\r\n') 30671162e3cSSimon Glass self.assertEqual(plist [1], 'fred\r\n') 30771162e3cSSimon Glass 30871162e3cSSimon Glass def test_shell(self): 30971162e3cSSimon Glass """Check with and without shell works""" 31071162e3cSSimon Glass oper = TestSubprocess.MyOperation() 31171162e3cSSimon Glass cmd = 'echo test >/dev/stderr' 31271162e3cSSimon Glass self.assertRaises(OSError, Popen, [cmd], shell=False) 31371162e3cSSimon Glass plist = Popen([cmd], shell=True).CommunicateFilter(oper.Output) 31471162e3cSSimon Glass self._BasicCheck(plist, oper) 31571162e3cSSimon Glass self.assertEqual(len(plist [0]), 0) 31671162e3cSSimon Glass self.assertEqual(plist [1], 'test\r\n') 31771162e3cSSimon Glass 31871162e3cSSimon Glass def test_list_args(self): 31971162e3cSSimon Glass """Check with and without shell works using list arguments""" 32071162e3cSSimon Glass oper = TestSubprocess.MyOperation() 32171162e3cSSimon Glass cmd = ['echo', 'test', '>/dev/stderr'] 32271162e3cSSimon Glass plist = Popen(cmd, shell=False).CommunicateFilter(oper.Output) 32371162e3cSSimon Glass self._BasicCheck(plist, oper) 32471162e3cSSimon Glass self.assertEqual(plist [0], ' '.join(cmd[1:]) + '\r\n') 32571162e3cSSimon Glass self.assertEqual(len(plist [1]), 0) 32671162e3cSSimon Glass 32771162e3cSSimon Glass oper = TestSubprocess.MyOperation() 32871162e3cSSimon Glass 32971162e3cSSimon Glass # this should be interpreted as 'echo' with the other args dropped 33071162e3cSSimon Glass cmd = ['echo', 'test', '>/dev/stderr'] 33171162e3cSSimon Glass plist = Popen(cmd, shell=True).CommunicateFilter(oper.Output) 33271162e3cSSimon Glass self._BasicCheck(plist, oper) 33371162e3cSSimon Glass self.assertEqual(plist [0], '\r\n') 33471162e3cSSimon Glass 33571162e3cSSimon Glass def test_cwd(self): 33671162e3cSSimon Glass """Check we can change directory""" 33771162e3cSSimon Glass for shell in (False, True): 33871162e3cSSimon Glass oper = TestSubprocess.MyOperation() 33971162e3cSSimon Glass plist = Popen('pwd', shell=shell, cwd='/tmp').CommunicateFilter(oper.Output) 34071162e3cSSimon Glass self._BasicCheck(plist, oper) 34171162e3cSSimon Glass self.assertEqual(plist [0], '/tmp\r\n') 34271162e3cSSimon Glass 34371162e3cSSimon Glass def test_env(self): 34471162e3cSSimon Glass """Check we can change environment""" 34571162e3cSSimon Glass for add in (False, True): 34671162e3cSSimon Glass oper = TestSubprocess.MyOperation() 34771162e3cSSimon Glass env = os.environ 34871162e3cSSimon Glass if add: 34971162e3cSSimon Glass env ['FRED'] = 'fred' 35071162e3cSSimon Glass cmd = 'echo $FRED' 35171162e3cSSimon Glass plist = Popen(cmd, shell=True, env=env).CommunicateFilter(oper.Output) 35271162e3cSSimon Glass self._BasicCheck(plist, oper) 35371162e3cSSimon Glass self.assertEqual(plist [0], add and 'fred\r\n' or '\r\n') 35471162e3cSSimon Glass 35571162e3cSSimon Glass def test_extra_args(self): 35671162e3cSSimon Glass """Check we can't add extra arguments""" 35771162e3cSSimon Glass self.assertRaises(ValueError, Popen, 'true', close_fds=False) 35871162e3cSSimon Glass 35971162e3cSSimon Glass def test_basic_input(self): 36071162e3cSSimon Glass """Check that incremental input works 36171162e3cSSimon Glass 36271162e3cSSimon Glass We set up a subprocess which will prompt for name. When we see this prompt 36371162e3cSSimon Glass we send the name as input to the process. It should then print the name 36471162e3cSSimon Glass properly to stdout. 36571162e3cSSimon Glass """ 36671162e3cSSimon Glass oper = TestSubprocess.MyOperation('Flash') 36771162e3cSSimon Glass prompt = 'What is your name?: ' 36871162e3cSSimon Glass cmd = 'echo -n "%s"; read name; echo Hello $name' % prompt 36971162e3cSSimon Glass plist = Popen([cmd], stdin=oper.stdin_read_pipe, 37071162e3cSSimon Glass shell=True).CommunicateFilter(oper.Output) 37171162e3cSSimon Glass self._BasicCheck(plist, oper) 37271162e3cSSimon Glass self.assertEqual(len(plist [1]), 0) 37371162e3cSSimon Glass self.assertEqual(plist [0], prompt + 'Hello Flash\r\r\n') 37471162e3cSSimon Glass 37571162e3cSSimon Glass def test_isatty(self): 37671162e3cSSimon Glass """Check that ptys appear as terminals to the subprocess""" 37771162e3cSSimon Glass oper = TestSubprocess.MyOperation() 37871162e3cSSimon Glass cmd = ('if [ -t %d ]; then echo "terminal %d" >&%d; ' 37971162e3cSSimon Glass 'else echo "not %d" >&%d; fi;') 38071162e3cSSimon Glass both_cmds = '' 38171162e3cSSimon Glass for fd in (1, 2): 38271162e3cSSimon Glass both_cmds += cmd % (fd, fd, fd, fd, fd) 38371162e3cSSimon Glass plist = Popen(both_cmds, shell=True).CommunicateFilter(oper.Output) 38471162e3cSSimon Glass self._BasicCheck(plist, oper) 38571162e3cSSimon Glass self.assertEqual(plist [0], 'terminal 1\r\n') 38671162e3cSSimon Glass self.assertEqual(plist [1], 'terminal 2\r\n') 38771162e3cSSimon Glass 38871162e3cSSimon Glass # Now try with PIPE and make sure it is not a terminal 38971162e3cSSimon Glass oper = TestSubprocess.MyOperation() 39071162e3cSSimon Glass plist = Popen(both_cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 39171162e3cSSimon Glass shell=True).CommunicateFilter(oper.Output) 39271162e3cSSimon Glass self._BasicCheck(plist, oper) 39371162e3cSSimon Glass self.assertEqual(plist [0], 'not 1\n') 39471162e3cSSimon Glass self.assertEqual(plist [1], 'not 2\n') 39571162e3cSSimon Glass 39671162e3cSSimon Glassif __name__ == '__main__': 39771162e3cSSimon Glass unittest.main() 398