resolve issue 54
triggered by @haypo's issue and patch the process.cmdexec function now always uses subprocess under the hood. Also fixed some 3k related encoding issues. --HG-- branch : trunk
This commit is contained in:
		
							parent
							
								
									df8aedba47
								
							
						
					
					
						commit
						5e21e39125
					
				|  | @ -1,151 +1,28 @@ | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| module defining basic hook for executing commands |  | ||||||
| in a - as much as possible - platform independent way. |  | ||||||
| 
 |  | ||||||
| Current list: |  | ||||||
| 
 |  | ||||||
|     exec_cmd(cmd)       executes the given command and returns output |  | ||||||
|                         or ExecutionFailed exception (if exit status!=0) |  | ||||||
| 
 |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| import os, sys | import os, sys | ||||||
|  | import subprocess | ||||||
| import py | import py | ||||||
| from subprocess import Popen, PIPE | from subprocess import Popen, PIPE | ||||||
| 
 | 
 | ||||||
| #----------------------------------------------------------- | def cmdexec(cmd): | ||||||
| # posix external command execution |     """ return output of executing 'cmd' in a separate process. | ||||||
| #----------------------------------------------------------- |  | ||||||
| def posix_exec_cmd(cmd): |  | ||||||
|     """ return output of executing 'cmd'. |  | ||||||
| 
 | 
 | ||||||
|     raise ExecutionFailed exeception if the command failed. |     raise cmdexec.ExecutionFailed exeception if the command failed. | ||||||
|     the exception will provide an 'err' attribute containing |     the exception will provide an 'err' attribute containing | ||||||
|     the error-output from the command. |     the error-output from the command. | ||||||
|     """ |     """ | ||||||
|     #__tracebackhide__ = True |     process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||||||
| 
 |     out, err = process.communicate() | ||||||
|     import errno |     out = py.builtin._totext(out, sys.getdefaultencoding()) | ||||||
| 
 |     err = py.builtin._totext(err, sys.getdefaultencoding()) | ||||||
|     child = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=True) |     status = process.poll() | ||||||
|     stdin, stdout, stderr = child.stdin, child.stdout, child.stderr |  | ||||||
| 
 |  | ||||||
|     # XXX sometimes we get a blocked r.read() call (see below) |  | ||||||
|     #     although select told us there is something to read. |  | ||||||
|     #     only the next three lines appear to prevent |  | ||||||
|     #     the read call from blocking infinitely. |  | ||||||
|     import fcntl |  | ||||||
|     def set_non_block(fd): |  | ||||||
|         flags = fcntl.fcntl(fd, fcntl.F_GETFL) |  | ||||||
|         flags = flags | os.O_NONBLOCK |  | ||||||
|         fcntl.fcntl(fd, fcntl.F_SETFL, flags) |  | ||||||
|     set_non_block(stdout.fileno()) |  | ||||||
|     set_non_block(stderr.fileno()) |  | ||||||
|     #fcntl.fcntl(stdout, fcntl.F_SETFL, os.O_NONBLOCK) |  | ||||||
|     #fcntl.fcntl(stderr, fcntl.F_SETFL, os.O_NONBLOCK) |  | ||||||
| 
 |  | ||||||
|     import select |  | ||||||
|     out, err = [], [] |  | ||||||
|     while 1: |  | ||||||
|         r_list = [x for x in [stdout, stderr] if x and not x.closed] |  | ||||||
|         if not r_list: |  | ||||||
|             break |  | ||||||
|         try: |  | ||||||
|             r_list = select.select(r_list, [], [])[0] |  | ||||||
|         except (select.error, IOError): |  | ||||||
|             se = sys.exc_info()[1] |  | ||||||
|             if se.args[0] == errno.EINTR: |  | ||||||
|                 continue |  | ||||||
|             else: |  | ||||||
|                 raise |  | ||||||
|         for r  in r_list: |  | ||||||
|             try: |  | ||||||
|                 data = r.read()   # XXX see XXX above |  | ||||||
|             except IOError: |  | ||||||
|                 io = sys.exc_info()[1] |  | ||||||
|                 if io.args[0] == errno.EAGAIN: |  | ||||||
|                     continue |  | ||||||
|                 # Connection Lost |  | ||||||
|                 raise |  | ||||||
|             except OSError: |  | ||||||
|                 ose = sys.exc_info()[1] |  | ||||||
|                 if ose.errno == errno.EPIPE: |  | ||||||
|                     # Connection Lost |  | ||||||
|                     raise |  | ||||||
|                 if ose.errno == errno.EAGAIN: # MacOS-X does this |  | ||||||
|                     continue |  | ||||||
|                 raise |  | ||||||
| 
 |  | ||||||
|             if not data: |  | ||||||
|                 r.close() |  | ||||||
|                 continue |  | ||||||
|             if r is stdout: |  | ||||||
|                 out.append(data) |  | ||||||
|             else: |  | ||||||
|                 err.append(data) |  | ||||||
|     pid, systemstatus = os.waitpid(child.pid, 0) |  | ||||||
|     if pid != child.pid: |  | ||||||
|         raise ExecutionFailed("child process disappeared during: "+ cmd) |  | ||||||
|     if systemstatus: |  | ||||||
|         if os.WIFSIGNALED(systemstatus): |  | ||||||
|             status = os.WTERMSIG(systemstatus) + 128 |  | ||||||
|         else: |  | ||||||
|             status = os.WEXITSTATUS(systemstatus) |  | ||||||
|         raise ExecutionFailed(status, systemstatus, cmd, |  | ||||||
|                               joiner(out), joiner(err)) |  | ||||||
|     return joiner(out) |  | ||||||
| 
 |  | ||||||
| def joiner(out): |  | ||||||
|     encoding = sys.getdefaultencoding() |  | ||||||
|     return "".join([py.builtin._totext(x, encoding) for x in out]) |  | ||||||
| 
 |  | ||||||
| #----------------------------------------------------------- |  | ||||||
| # simple win32 external command execution |  | ||||||
| #----------------------------------------------------------- |  | ||||||
| def win32_exec_cmd(cmd): |  | ||||||
|     """ return output of executing 'cmd'. |  | ||||||
| 
 |  | ||||||
|     raise ExecutionFailed exeception if the command failed. |  | ||||||
|     the exception will provide an 'err' attribute containing |  | ||||||
|     the error-output from the command. |  | ||||||
| 
 |  | ||||||
|     Note that this method can currently deadlock because |  | ||||||
|     we don't have WaitForMultipleObjects in the std-python api. |  | ||||||
| 
 |  | ||||||
|     Further note that the rules for quoting are very special |  | ||||||
|     under Windows. Do a HELP CMD in a shell, and tell me if |  | ||||||
|     you understand this. For now, I try to do a fix. |  | ||||||
|     """ |  | ||||||
|     #print "*****", cmd |  | ||||||
| 
 |  | ||||||
|     # the following quoting is only valid for CMD.EXE, not COMMAND.COM |  | ||||||
|     cmd_quoting = True |  | ||||||
|     try: |  | ||||||
|         if os.environ['COMSPEC'].upper().endswith('COMMAND.COM'): |  | ||||||
|             cmd_quoting = False |  | ||||||
|     except KeyError: |  | ||||||
|         pass |  | ||||||
|     if cmd_quoting: |  | ||||||
|         if '"' in cmd and not cmd.startswith('""'): |  | ||||||
|             cmd = '"%s"' % cmd |  | ||||||
| 
 |  | ||||||
|     return popen3_exec_cmd(cmd) |  | ||||||
| 
 |  | ||||||
| def popen3_exec_cmd(cmd): |  | ||||||
|     stdin, stdout, stderr = os.popen3(cmd) |  | ||||||
|     out = stdout.read() |  | ||||||
|     err = stderr.read() |  | ||||||
|     stdout.close() |  | ||||||
|     stderr.close() |  | ||||||
|     status = stdin.close() |  | ||||||
|     if status: |     if status: | ||||||
|         raise ExecutionFailed(status, status, cmd, out, err) |         raise ExecutionFailed(status, status, cmd, out, err) | ||||||
|     return out |     return out | ||||||
| 
 | 
 | ||||||
| def pypy_exec_cmd(cmd): |  | ||||||
|     return popen3_exec_cmd(cmd) |  | ||||||
| 
 |  | ||||||
| class ExecutionFailed(py.error.Error): | class ExecutionFailed(py.error.Error): | ||||||
|     def __init__(self, status, systemstatus, cmd, out, err): |     def __init__(self, status, systemstatus, cmd, out, err): | ||||||
|         Exception.__init__(self) |         Exception.__init__(self) | ||||||
|  | @ -157,16 +34,6 @@ class ExecutionFailed(py.error.Error): | ||||||
| 
 | 
 | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return "ExecutionFailed: %d  %s\n%s" %(self.status, self.cmd, self.err) |         return "ExecutionFailed: %d  %s\n%s" %(self.status, self.cmd, self.err) | ||||||
| # |  | ||||||
| # choose correct platform-version |  | ||||||
| # |  | ||||||
| 
 |  | ||||||
| if sys.platform == 'win32': |  | ||||||
|     cmdexec = win32_exec_cmd |  | ||||||
| elif hasattr(sys, 'pypy') or hasattr(sys, 'pypy_objspaceclass'): |  | ||||||
|     cmdexec = popen3_exec_cmd |  | ||||||
| else: |  | ||||||
|     cmdexec = posix_exec_cmd |  | ||||||
| 
 | 
 | ||||||
| # export the exception under the name 'py.process.cmdexec.Error' | # export the exception under the name 'py.process.cmdexec.Error' | ||||||
| cmdexec.Error = ExecutionFailed | cmdexec.Error = ExecutionFailed | ||||||
|  |  | ||||||
|  | @ -18,6 +18,8 @@ class Test_exec_cmd: | ||||||
|         except cmdexec.Error: |         except cmdexec.Error: | ||||||
|             e = exvalue() |             e = exvalue() | ||||||
|             assert e.status == 1 |             assert e.status == 1 | ||||||
|  |             assert py.builtin._istext(e.out) | ||||||
|  |             assert py.builtin._istext(e.err) | ||||||
| 
 | 
 | ||||||
|     def test_err(self): |     def test_err(self): | ||||||
|         try: |         try: | ||||||
|  | @ -28,16 +30,3 @@ class Test_exec_cmd: | ||||||
|             assert hasattr(e, 'err') |             assert hasattr(e, 'err') | ||||||
|             assert hasattr(e, 'out') |             assert hasattr(e, 'out') | ||||||
|             assert e.err or e.out |             assert e.err or e.out | ||||||
| 
 |  | ||||||
| def test_cmdexec_selection(): |  | ||||||
|     from _py.process import cmdexec  |  | ||||||
|     if py.std.sys.platform == "win32": |  | ||||||
|         assert py.process.cmdexec == cmdexec.win32_exec_cmd |  | ||||||
|     elif hasattr(py.std.sys, 'pypy') or hasattr(py.std.sys, 'pypy_objspaceclass'): |  | ||||||
|         assert py.process.cmdexec == cmdexec.popen3_exec_cmd |  | ||||||
|     else: |  | ||||||
|         assert py.process.cmdexec == cmdexec.posix_exec_cmd |  | ||||||
|          |  | ||||||
|          |  | ||||||
|          |  | ||||||
|      |  | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue