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