Commit ef77f9e6 authored by maruel@google.com's avatar maruel@google.com

Make subprocess2.Popen a class instead of a function.

This will be necessary to override member functions eventually. It also better
replicates what subprocess.Popen is.

R=dpranke@chromium.org
BUG=
TEST=


Review URL: http://codereview.chromium.org/8570005

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@111530 0039d316-1c4b-4281-b951-d872f2087c98
parent 4e07567f
......@@ -131,11 +131,9 @@ def get_english_env(env):
return env
def Popen(args, **kwargs):
class Popen(subprocess.Popen):
"""Wraps subprocess.Popen() with various workarounds.
Returns a subprocess.Popen object.
- Forces English output since it's easier to parse the stdout if it is always
in English.
- Sets shell=True on windows by default. You can override this by forcing
......@@ -145,57 +143,60 @@ def Popen(args, **kwargs):
Note: Popen() can throw OSError when cwd or args[0] doesn't exist. Translate
exceptions generated by cygwin when it fails trying to emulate fork().
"""
# Make sure we hack subprocess if necessary.
hack_subprocess()
add_kill()
env = get_english_env(kwargs.get('env'))
if env:
kwargs['env'] = env
if kwargs.get('shell') is None:
# *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the
# executable, but shell=True makes subprocess on Linux fail when it's called
# with a list because it only tries to execute the first item in the list.
kwargs['shell'] = bool(sys.platform=='win32')
if isinstance(args, basestring):
tmp_str = args
elif isinstance(args, (list, tuple)):
tmp_str = ' '.join(args)
else:
raise CalledProcessError(None, args, kwargs.get('cwd'), None, None)
if kwargs.get('cwd', None):
tmp_str += '; cwd=%s' % kwargs['cwd']
logging.debug(tmp_str)
def fix(stream):
if kwargs.get(stream) in (VOID, os.devnull):
# Replaces VOID with handle to /dev/null.
# Create a temporary file to workaround python's deadlock.
# http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
# When the pipe fills up, it will deadlock this process. Using a real file
# works around that issue.
kwargs[stream] = open(os.devnull, 'w')
fix('stdout')
fix('stderr')
try:
return subprocess.Popen(args, **kwargs)
except OSError, e:
if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
# Convert fork() emulation failure into a CygwinRebaseError().
raise CygwinRebaseError(
e.errno,
args,
kwargs.get('cwd'),
None,
'Visit '
'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to '
'learn how to fix this error; you need to rebase your cygwin dlls')
# Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
# through
raise
def __init__(self, args, **kwargs):
# Make sure we hack subprocess if necessary.
hack_subprocess()
add_kill()
env = get_english_env(kwargs.get('env'))
if env:
kwargs['env'] = env
if kwargs.get('shell') is None:
# *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
# the executable, but shell=True makes subprocess on Linux fail when it's
# called with a list because it only tries to execute the first item in
# the list.
kwargs['shell'] = bool(sys.platform=='win32')
if isinstance(args, basestring):
tmp_str = args
elif isinstance(args, (list, tuple)):
tmp_str = ' '.join(args)
else:
raise CalledProcessError(None, args, kwargs.get('cwd'), None, None)
if kwargs.get('cwd', None):
tmp_str += '; cwd=%s' % kwargs['cwd']
logging.debug(tmp_str)
def fix(stream):
if kwargs.get(stream) in (VOID, os.devnull):
# Replaces VOID with handle to /dev/null.
# Create a temporary file to workaround python's deadlock.
# http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
# When the pipe fills up, it will deadlock this process. Using a real
# file works around that issue.
kwargs[stream] = open(os.devnull, 'w')
fix('stdout')
fix('stderr')
try:
super(Popen, self).__init__(args, **kwargs)
except OSError, e:
if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
# Convert fork() emulation failure into a CygwinRebaseError().
raise CygwinRebaseError(
e.errno,
args,
kwargs.get('cwd'),
None,
'Visit '
'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure '
'to learn how to fix this error; you need to rebase your cygwin '
'dlls')
# Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
# through
raise
def communicate(args, timeout=None, **kwargs):
......
......@@ -47,7 +47,7 @@ class DefaultsTest(unittest.TestCase):
TO_SAVE = {
subprocess2: [
'Popen', 'communicate', 'call', 'check_call', 'capture', 'check_output'],
subprocess2.subprocess: ['Popen'],
subprocess2.subprocess.Popen: ['__init__', 'communicate'],
}
def setUp(self):
......@@ -65,17 +65,19 @@ class DefaultsTest(unittest.TestCase):
@staticmethod
def _fake_communicate():
"""Mocks subprocess2.communicate()."""
results = {}
def fake_communicate(args, **kwargs):
assert not results
results.update(kwargs)
results['args'] = args
return ['stdout', 'stderr'], 0
return ('stdout', 'stderr'), 0
subprocess2.communicate = fake_communicate
return results
@staticmethod
def _fake_Popen():
"""Mocks the whole subprocess2.Popen class."""
results = {}
class fake_Popen(object):
returncode = -8
......@@ -91,23 +93,22 @@ class DefaultsTest(unittest.TestCase):
@staticmethod
def _fake_subprocess_Popen():
"""Mocks the base class subprocess.Popen only."""
results = {}
class fake_Popen(object):
returncode = -8
def __init__(self, args, **kwargs):
assert not results
results.update(kwargs)
results['args'] = args
@staticmethod
def communicate():
return None, None
subprocess2.subprocess.Popen = fake_Popen
def __init__(self, args, **kwargs):
assert not results
results.update(kwargs)
results['args'] = args
def communicate():
return None, None
subprocess2.subprocess.Popen.__init__ = __init__
subprocess2.subprocess.Popen.communicate = communicate
return results
def test_check_call_defaults(self):
results = self._fake_communicate()
self.assertEquals(
['stdout', 'stderr'], subprocess2.check_call_out(['foo'], a=True))
('stdout', 'stderr'), subprocess2.check_call_out(['foo'], a=True))
expected = {
'args': ['foo'],
'a':True,
......@@ -139,7 +140,12 @@ class DefaultsTest(unittest.TestCase):
def test_Popen_defaults(self):
results = self._fake_subprocess_Popen()
proc = subprocess2.Popen(['foo'], a=True)
self.assertEquals(-8, proc.returncode)
# Cleanup code in subprocess.py needs this member to be set.
# pylint: disable=W0201
proc._child_created = None
# Since subprocess.Popen.__init__() is not called, proc.returncode shouldn't
# be present.
self.assertFalse(hasattr(proc, 'returncode'))
expected = {
'args': ['foo'],
'a': True,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment