Recently, I developed an acceptance test framework using Python. I am not expert in Python, but with help of Google search I can almost build anything I want. Except one: How to run an external program and interact with it with Python?
At first, it seems easy. Python's subprocess module let you run an external command and get its output. But it turns out you only get the outputs after the external program ended. In my application, I wanted to capture external programs outputs as soon as possible and as soon as some key words appear or timeout I can make some control flow decision.
I finally found the answer at ... http://code.activestate.com/recipes/440554-module-to-allow-asynchronous-subprocess-use-on-win/
Anyway, the code was for Python 2. With some amendment it is listed below. Enjoy!
#!/usr/bin/env python3 import os import subprocess import errno import time import sys if sys.platform == 'win32': from win32file import ReadFile, WriteFile from win32pipe import PeekNamedPipe import msvcrt import pywintypes else: import select import fcntl class AsyncPopen(subprocess.Popen): def recv(self, maxsize=None): return self._recv('stdout', maxsize) def recv_err(self, maxsize=None): return self._recv('stderr', maxsize) def send_recv(self, input='', maxsize=None): return self.send(input), self.recv(maxsize), self.recv_err(maxsize) def get_conn_maxsize(self, which, maxsize): if maxsize is None: maxsize = 1024 elif maxsize < 1: maxsize = 1 return getattr(self, which), maxsize def _close(self, which): getattr(self, which).close() setattr(self, which, None) if sys.platform == 'win32': def send(self, input): if not self.stdin: return None try: x = msvcrt.get_osfhandle(self.stdin.fileno()) (errCode, written) = WriteFile(x, input.encode()) except ValueError: return self._close('stdin') except (pywintypes.error, Exception) as why: print(why) if why.args[0] in (109, errno.ESHUTDOWN): return self._close('stdin') raise return written def _recv(self, which, maxsize): conn, maxsize = self.get_conn_maxsize(which, maxsize) if conn is None: return None try: x = msvcrt.get_osfhandle(conn.fileno()) (read, nAvail, nMessage) = PeekNamedPipe(x, 0) if maxsize < nAvail: nAvail = maxsize if nAvail > 0: (errCode, read) = ReadFile(x, nAvail, None) except ValueError: return self._close(which) except (pywintypes.error, Exception) as why: print(why) if why.args[0] in (109, errno.ESHUTDOWN): return self._close(which) raise if self.universal_newlines: read = self._translate_newlines(read) return read else: def send(self, input): if not self.stdin: return None if not select.select([], [self.stdin], [], 0)[1]: return 0 try: written = os.write(self.stdin.fileno(), input.encode()) except OSError as why: if why[0] == errno.EPIPE: #broken pipe return self._close('stdin') raise return written def _recv(self, which, maxsize): conn, maxsize = self.get_conn_maxsize(which, maxsize) if conn is None: return None flags = fcntl.fcntl(conn, fcntl.F_GETFL) if not conn.closed: fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK) try: if not select.select([conn], [], [], 0)[0]: return '' r = conn.read(maxsize) if not r: return self._close(which) if self.universal_newlines: r = self._translate_newlines(r) return r finally: if not conn.closed: fcntl.fcntl(conn, fcntl.F_SETFL, flags) message = "Other end disconnected!" def async_recv(p, t=.1, e=1, tr=5, stderr=0): if tr < 1: tr = 1 x = time.time()+t y = [] r = '' pr = p.recv if stderr: pr = p.recv_err while time.time() < x or r: r = pr() if r is None: if e: raise Exception(message) else: break elif r: y.append(r) else: time.sleep(max((x-time.time())/tr, 0)) return b''.join(y) def async_send(p, data): while len(data): sent = p.send(data) if sent is None: raise Exception(message) data = data[sent:] if __name__ == '__main__': if sys.platform == 'win32': shell, commands, tail = ('cmd', ('dir /w', 'echo HELLO WORLD'), '\r\n') else: shell, commands, tail = ('sh', ('ls', 'echo HELLO WORLD'), '\n') p = AsyncPopen(shell, stdin=subprocess.PIPE, stdout=subprocess.PIPE) print (async_recv(p), end="") for cmd in commands: async_send(p, cmd + tail) print (async_recv(p), end="") async_send(p, 'exit' + tail) print (async_recv(p, e=0)) p.wait()