mirror of
https://chromium.googlesource.com/chromium/tools/depot_tools.git
synced 2026-01-11 18:51:29 +00:00
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
This commit is contained in:
@@ -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()
|
||||
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')
|
||||
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)
|
||||
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')
|
||||
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')
|
||||
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
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user