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:
holger krekel 2009-10-14 23:54:01 +02:00
parent df8aedba47
commit 5e21e39125
2 changed files with 11 additions and 155 deletions

View File

@ -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

View File

@ -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