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 subprocess
|
||||
import py
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
#-----------------------------------------------------------
|
||||
# posix external command execution
|
||||
#-----------------------------------------------------------
|
||||
def posix_exec_cmd(cmd):
|
||||
""" return output of executing 'cmd'.
|
||||
def cmdexec(cmd):
|
||||
""" return output of executing 'cmd' in a separate process.
|
||||
|
||||
raise ExecutionFailed exeception if the command failed.
|
||||
raise cmdexec.ExecutionFailed exeception if the command failed.
|
||||
the exception will provide an 'err' attribute containing
|
||||
the error-output from the command.
|
||||
"""
|
||||
#__tracebackhide__ = True
|
||||
|
||||
import errno
|
||||
|
||||
child = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=True)
|
||||
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()
|
||||
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out, err = process.communicate()
|
||||
out = py.builtin._totext(out, sys.getdefaultencoding())
|
||||
err = py.builtin._totext(err, sys.getdefaultencoding())
|
||||
status = process.poll()
|
||||
if status:
|
||||
raise ExecutionFailed(status, status, cmd, out, err)
|
||||
return out
|
||||
|
||||
def pypy_exec_cmd(cmd):
|
||||
return popen3_exec_cmd(cmd)
|
||||
|
||||
class ExecutionFailed(py.error.Error):
|
||||
def __init__(self, status, systemstatus, cmd, out, err):
|
||||
Exception.__init__(self)
|
||||
|
@ -157,16 +34,6 @@ class ExecutionFailed(py.error.Error):
|
|||
|
||||
def __str__(self):
|
||||
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'
|
||||
cmdexec.Error = ExecutionFailed
|
||||
|
|
|
@ -18,6 +18,8 @@ class Test_exec_cmd:
|
|||
except cmdexec.Error:
|
||||
e = exvalue()
|
||||
assert e.status == 1
|
||||
assert py.builtin._istext(e.out)
|
||||
assert py.builtin._istext(e.err)
|
||||
|
||||
def test_err(self):
|
||||
try:
|
||||
|
@ -28,16 +30,3 @@ class Test_exec_cmd:
|
|||
assert hasattr(e, 'err')
|
||||
assert hasattr(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