Bug 943481 - Mirror mozprocess 0.14 from mozbase github;r=wlach

This commit is contained in:
Jeff Hammel 2013-12-09 09:51:24 -05:00
parent e62e9b8dc4
commit 374dd9f02b
5 changed files with 96 additions and 23 deletions

View File

@ -2,8 +2,6 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import logging
import mozinfo
import os
import select
import signal
@ -19,7 +17,11 @@ __all__ = ['ProcessHandlerMixin', 'ProcessHandler']
# Set the MOZPROCESS_DEBUG environment variable to 1 to see some debugging output
MOZPROCESS_DEBUG = os.getenv("MOZPROCESS_DEBUG")
if mozinfo.isWin:
# We dont use mozinfo because it is expensive to import, see bug 933558.
isWin = os.name == "nt"
isPosix = os.name == "posix" # includes MacOS X
if isWin:
import ctypes, ctypes.wintypes, msvcrt
from ctypes import sizeof, addressof, c_ulong, byref, POINTER, WinError, c_longlong
import winprocess
@ -31,8 +33,8 @@ class ProcessHandlerMixin(object):
"""
A class for launching and manipulating local processes.
:param cmd: command to run.
:param args: is a list of arguments to pass to the command (defaults to None).
:param cmd: command to run. May be a string or a list. If specified as a list, the first element will be interpreted as the command, and all additional elements will be interpreted as arguments to that command.
:param args: list of arguments to pass to the command (defaults to None). Must not be set when `cmd` is specified as a list.
:param cwd: working directory for command (defaults to None).
:param env: is the environment to use for the process (defaults to os.environ).
:param ignore_children: causes system to ignore child processes when True, defaults to False (which tracks child processes).
@ -77,7 +79,7 @@ class ProcessHandlerMixin(object):
# Parameter for whether or not we should attempt to track child processes
self._ignore_children = ignore_children
if not self._ignore_children and not mozinfo.isWin:
if not self._ignore_children and not isWin:
# Set the process group id for linux systems
# Sets process group id to the pid of the parent process
# NOTE: This prevents you from using preexec_fn and managing
@ -97,7 +99,7 @@ class ProcessHandlerMixin(object):
raise
def __del__(self, _maxint=sys.maxint):
if mozinfo.isWin:
if isWin:
if self._handle:
if hasattr(self, '_internal_poll'):
self._internal_poll(_deadstate=_maxint)
@ -110,7 +112,7 @@ class ProcessHandlerMixin(object):
def kill(self):
self.returncode = 0
if mozinfo.isWin:
if isWin:
if not self._ignore_children and self._handle and self._job:
winprocess.TerminateJobObject(self._job, winprocess.ERROR_CONTROL_C_EXIT)
self.returncode = winprocess.GetExitCodeProcess(self._handle)
@ -155,7 +157,7 @@ class ProcessHandlerMixin(object):
""" Private Members of Process class """
if mozinfo.isWin:
if isWin:
# Redefine the execute child so that we can track process groups
def _execute_child(self, args, executable, preexec_fn, close_fds,
cwd, env, universal_newlines, startupinfo,
@ -510,7 +512,7 @@ falling back to not using job objects for managing child processes"""
else:
self._handle = None
elif mozinfo.isMac or mozinfo.isUnix:
elif isPosix:
def _wait(self):
""" Haven't found any reason to differentiate between these platforms
@ -521,8 +523,20 @@ falling back to not using job objects for managing child processes"""
if not self._ignore_children:
try:
# os.waitpid returns a (pid, status) tuple
return os.waitpid(self.pid, 0)[1]
# os.waitpid return value:
# > [...] a tuple containing its pid and exit status
# > indication: a 16-bit number, whose low byte is the
# > signal number that killed the process, and whose
# > high byte is the exit status (if the signal number
# > is zero)
# - http://docs.python.org/2/library/os.html#os.wait
status = os.waitpid(self.pid, 0)[1]
# For consistency, format status the same as subprocess'
# returncode attribute
if status > 255:
return status >> 8
return -status
except OSError, e:
if getattr(e, "errno", None) != 10:
# Error 10 is "no child process", which could indicate normal
@ -582,11 +596,12 @@ falling back to not using job objects for managing child processes"""
# It is common for people to pass in the entire array with the cmd and
# the args together since this is how Popen uses it. Allow for that.
if not isinstance(self.cmd, list):
self.cmd = [self.cmd]
if self.args:
self.cmd = self.cmd + self.args
if isinstance(self.cmd, list):
if self.args != None:
raise TypeError("cmd and args must not both be lists")
(self.cmd, self.args) = (self.cmd[0], self.cmd[1:])
elif self.args is None:
self.args = []
@property
def timedOut(self):
@ -624,7 +639,7 @@ falling back to not using job objects for managing child processes"""
args.update(self.keywordargs)
# launch the process
self.proc = self.Process(self.cmd, **args)
self.proc = self.Process([self.cmd] + self.args, **args)
self.processOutput(timeout=timeout, outputTimeout=outputTimeout)
@ -701,9 +716,13 @@ falling back to not using job objects for managing child processes"""
lineReadTimeout = outputTimeout
(lines, self.didTimeout) = self.readWithTimeout(logsource, lineReadTimeout)
while lines != "" and not self.didTimeout:
while lines != "":
for line in lines.splitlines():
self.processOutputLine(line.rstrip())
if self.didTimeout:
break
if timeout:
lineReadTimeout = timeout - (datetime.now() - self.startTime).seconds
(lines, self.didTimeout) = self.readWithTimeout(logsource, lineReadTimeout)
@ -732,6 +751,10 @@ falling back to not using job objects for managing child processes"""
If timeout is not None, will return after timeout seconds.
This timeout only causes the wait function to return and
does not kill the process.
Returns the process' exit code. A None value indicates the
process hasn't terminated yet. A negative value -N indicates
the process was killed by signal N (Unix only).
"""
if self.outThread:
# Thread.join() blocks the main thread until outThread is finished
@ -754,7 +777,7 @@ falling back to not using job objects for managing child processes"""
### Private methods from here on down. Thar be dragons.
if mozinfo.isWin:
if isWin:
# Windows Specific private functions are defined in this block
PeekNamedPipe = ctypes.windll.kernel32.PeekNamedPipe
GetLastError = ctypes.windll.kernel32.GetLastError
@ -795,7 +818,9 @@ falling back to not using job objects for managing child processes"""
output = os.read(f.fileno(), 4096)
if not output:
return (self.read_buffer, False)
output = self.read_buffer
self.read_buffer = ''
return (output, False)
self.read_buffer += output
if '\n' not in self.read_buffer:
time.sleep(0.01)

View File

@ -4,7 +4,7 @@
from setuptools import setup
PACKAGE_VERSION = '0.13'
PACKAGE_VERSION = '0.14'
setup(name='mozprocess',
version=PACKAGE_VERSION,

View File

@ -12,3 +12,4 @@ disabled = bug 877864
disabled = bug 921632
[test_mozprocess_misc.py]
[test_mozprocess_wait.py]
[test_mozprocess_nonewline.py]

View File

@ -136,6 +136,48 @@ class ProcTest(unittest.TestCase):
p.proc.returncode,
p.didTimeout)
def test_commandline_no_args(self):
"""Command line is reported correctly when no arguments are specified"""
p = processhandler.ProcessHandler(self.proclaunch, cwd=here)
self.assertEqual(p.commandline, self.proclaunch)
def test_commandline_overspecified(self):
"""Command line raises an exception when the arguments are specified ambiguously"""
err = None
try:
p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"],
args=["1", "2", "3"],
cwd=here)
except TypeError, e:
err = e
self.assertTrue(err)
def test_commandline_from_list(self):
"""Command line is reported correctly when command and arguments are specified in a list"""
p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"],
cwd=here)
self.assertEqual(p.commandline, self.proclaunch + ' process_normal_finish.ini')
def test_commandline_over_specified(self):
"""Command line raises an exception when the arguments are specified ambiguously"""
err = None
try:
p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"],
args=["1", "2", "3"],
cwd=here)
except TypeError, e:
err = e
self.assertTrue(err)
def test_commandline_from_args(self):
"""Command line is reported correctly when arguments are specified in a dedicated list"""
p = processhandler.ProcessHandler(self.proclaunch,
args=["1", "2", "3"],
cwd=here)
self.assertEqual(p.commandline, self.proclaunch + ' 1 2 3')
def test_process_wait(self):
"""Process is started runs to completion while we wait indefinitely"""

View File

@ -1,9 +1,9 @@
#!/usr/bin/env python
import os
import time
import unittest
import proctest
import mozinfo
from mozprocess import processhandler
here = os.path.dirname(os.path.abspath(__file__))
@ -51,6 +51,11 @@ class ProcTestWait(proctest.ProcTest):
p.wait()
detected, output = proctest.check_for_process(self.proclaunch)
if mozinfo.isUnix:
# process was killed, so returncode should be negative
self.assertLess(p.proc.returncode, 0)
self.determine_status(detected,
output,
p.proc.returncode,