зеркало из https://github.com/mozilla/gecko-dev.git
Bug 929667 - Mirror mozprocess,mozrunner, r=ahal
This commit is contained in:
Родитель
eb035c3fde
Коммит
672e0179ef
|
@ -49,7 +49,13 @@ def running_processes(name, psarg=psarg, defunct=True):
|
|||
"""
|
||||
retval = []
|
||||
for process in ps(psarg):
|
||||
command = process['COMMAND']
|
||||
# Support for both BSD and UNIX syntax
|
||||
# `ps aux` returns COMMAND, `ps -ef` returns CMD
|
||||
try:
|
||||
command = process['COMMAND']
|
||||
except KeyError:
|
||||
command = process['CMD']
|
||||
|
||||
command = shlex.split(command)
|
||||
if command[-1] == '<defunct>':
|
||||
command = command[:-1]
|
||||
|
|
|
@ -28,7 +28,24 @@ if mozinfo.isWin:
|
|||
JOBOBJECT_BASIC_LIMIT_INFORMATION, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, IO_COUNTERS
|
||||
|
||||
class ProcessHandlerMixin(object):
|
||||
"""Class which represents a process to be executed."""
|
||||
"""
|
||||
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 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).
|
||||
:param processOutputLine: function to be called for each line of output produced by the process (defaults to None).
|
||||
:param onTimeout: function to be called when the process times out.
|
||||
:param onFinish: function to be called when the process terminates normally without timing out.
|
||||
:param kwargs: additional keyword args to pass directly into Popen.
|
||||
|
||||
NOTE: Child processes will be tracked by default. If for any reason
|
||||
we are unable to track child processes and ignore_children is set to False,
|
||||
then we will fall back to only tracking the root process. The fallback
|
||||
will be logged.
|
||||
"""
|
||||
|
||||
class Process(subprocess.Popen):
|
||||
"""
|
||||
|
@ -542,22 +559,6 @@ falling back to not using job objects for managing child processes"""
|
|||
onTimeout=(),
|
||||
onFinish=(),
|
||||
**kwargs):
|
||||
"""
|
||||
cmd = Command to run
|
||||
args = array of arguments (defaults to None)
|
||||
cwd = working directory for cmd (defaults to None)
|
||||
env = environment to use for the process (defaults to os.environ)
|
||||
ignore_children = when True, causes system to ignore child processes,
|
||||
defaults to False (which tracks child processes)
|
||||
processOutputLine = handlers to process the output line
|
||||
onTimeout = handlers for timeout event
|
||||
kwargs = keyword args to pass directly into Popen
|
||||
|
||||
NOTE: Child processes will be tracked by default. If for any reason
|
||||
we are unable to track child processes and ignore_children is set to False,
|
||||
then we will fall back to only tracking the root process. The fallback
|
||||
will be logged.
|
||||
"""
|
||||
self.cmd = cmd
|
||||
self.args = args
|
||||
self.cwd = cwd
|
||||
|
@ -565,6 +566,7 @@ falling back to not using job objects for managing child processes"""
|
|||
self._ignore_children = ignore_children
|
||||
self.keywordargs = kwargs
|
||||
self.outThread = None
|
||||
self.read_buffer = ''
|
||||
|
||||
if env is None:
|
||||
env = os.environ.copy()
|
||||
|
@ -590,7 +592,7 @@ falling back to not using job objects for managing child processes"""
|
|||
|
||||
@property
|
||||
def commandline(self):
|
||||
"""the string value of the command line"""
|
||||
"""the string value of the command line (command + args)"""
|
||||
return subprocess.list2cmdline([self.cmd] + self.args)
|
||||
|
||||
def run(self, timeout=None, outputTimeout=None):
|
||||
|
@ -598,7 +600,8 @@ falling back to not using job objects for managing child processes"""
|
|||
Starts the process.
|
||||
|
||||
If timeout is not None, the process will be allowed to continue for
|
||||
that number of seconds before being killed.
|
||||
that number of seconds before being killed. If the process is killed
|
||||
due to a timeout, the onTimeout handler will be called.
|
||||
|
||||
If outputTimeout is not None, the process will be allowed to continue
|
||||
for that number of seconds without producing any output before
|
||||
|
@ -624,14 +627,15 @@ falling back to not using job objects for managing child processes"""
|
|||
|
||||
def kill(self):
|
||||
"""
|
||||
Kills the managed process and if you created the process with
|
||||
'ignore_children=False' (the default) then it will also
|
||||
also kill all child processes spawned by it.
|
||||
If you specified 'ignore_children=True' when creating the process,
|
||||
only the root process will be killed.
|
||||
Kills the managed process.
|
||||
|
||||
Note that this does not manage any state, save any output etc,
|
||||
it immediately kills the process.
|
||||
If you created the process with 'ignore_children=False' (the
|
||||
default) then it will also also kill all child processes spawned by
|
||||
it. If you specified 'ignore_children=True' when creating the
|
||||
process, only the root process will be killed.
|
||||
|
||||
Note that this does not manage any state, save any output etc,
|
||||
it immediately kills the process.
|
||||
"""
|
||||
try:
|
||||
return self.proc.kill()
|
||||
|
@ -644,21 +648,21 @@ falling back to not using job objects for managing child processes"""
|
|||
|
||||
def readWithTimeout(self, f, timeout):
|
||||
"""
|
||||
Try to read a line of output from the file object |f|.
|
||||
|f| must be a pipe, like the |stdout| member of a subprocess.Popen
|
||||
object created with stdout=PIPE. If no output
|
||||
is received within |timeout| seconds, return a blank line.
|
||||
Returns a tuple (line, did_timeout), where |did_timeout| is True
|
||||
if the read timed out, and False otherwise.
|
||||
Try to read a line of output from the file object *f*.
|
||||
|
||||
Calls a private member because this is a different function based on
|
||||
the OS
|
||||
*f* must be a pipe, like the *stdout* member of a subprocess.Popen
|
||||
object created with stdout=PIPE. If no output
|
||||
is received within *timeout* seconds, return a blank line.
|
||||
|
||||
Returns a tuple (line, did_timeout), where *did_timeout* is True
|
||||
if the read timed out, and False otherwise.
|
||||
"""
|
||||
# Calls a private member because this is a different function based on
|
||||
# the OS
|
||||
return self._readWithTimeout(f, timeout)
|
||||
|
||||
def processOutputLine(self, line):
|
||||
"""Called for each line of output that a process sends to stdout/stderr.
|
||||
"""
|
||||
"""Called for each line of output that a process sends to stdout/stderr."""
|
||||
for handler in self.processOutputLineHandlers:
|
||||
handler(line)
|
||||
|
||||
|
@ -693,12 +697,13 @@ falling back to not using job objects for managing child processes"""
|
|||
elif outputTimeout:
|
||||
lineReadTimeout = outputTimeout
|
||||
|
||||
(line, self.didTimeout) = self.readWithTimeout(logsource, lineReadTimeout)
|
||||
while line != "" and not self.didTimeout:
|
||||
self.processOutputLine(line.rstrip())
|
||||
(lines, self.didTimeout) = self.readWithTimeout(logsource, lineReadTimeout)
|
||||
while lines != "" and not self.didTimeout:
|
||||
for line in lines.splitlines():
|
||||
self.processOutputLine(line.rstrip())
|
||||
if timeout:
|
||||
lineReadTimeout = timeout - (datetime.now() - self.startTime).seconds
|
||||
(line, self.didTimeout) = self.readWithTimeout(logsource, lineReadTimeout)
|
||||
(lines, self.didTimeout) = self.readWithTimeout(logsource, lineReadTimeout)
|
||||
|
||||
if self.didTimeout:
|
||||
self.proc.kill()
|
||||
|
@ -774,15 +779,31 @@ falling back to not using job objects for managing child processes"""
|
|||
else:
|
||||
# Generic
|
||||
def _readWithTimeout(self, f, timeout):
|
||||
try:
|
||||
(r, w, e) = select.select([f], [], [], timeout)
|
||||
except:
|
||||
# return a blank line
|
||||
return ('', True)
|
||||
while True:
|
||||
try:
|
||||
(r, w, e) = select.select([f], [], [], timeout)
|
||||
except:
|
||||
# return a blank line
|
||||
return ('', True)
|
||||
|
||||
if len(r) == 0:
|
||||
return ('', True)
|
||||
return (f.readline(), False)
|
||||
if len(r) == 0:
|
||||
return ('', True)
|
||||
|
||||
output = os.read(f.fileno(), 4096)
|
||||
if not output:
|
||||
return (self.read_buffer, False)
|
||||
self.read_buffer += output
|
||||
if '\n' not in self.read_buffer:
|
||||
time.sleep(0.01)
|
||||
continue
|
||||
tmp = self.read_buffer.split('\n')
|
||||
lines, self.read_buffer = tmp[:-1], tmp[-1]
|
||||
real_lines = [x for x in lines if x != '']
|
||||
if not real_lines:
|
||||
time.sleep(0.01)
|
||||
continue
|
||||
break
|
||||
return ('\n'.join(lines), False)
|
||||
|
||||
@property
|
||||
def pid(self):
|
||||
|
@ -824,16 +845,22 @@ class LogOutput(object):
|
|||
### front end class with the default handlers
|
||||
|
||||
class ProcessHandler(ProcessHandlerMixin):
|
||||
"""
|
||||
Convenience class for handling processes with default output handlers.
|
||||
|
||||
If no processOutputLine keyword argument is specified, write all
|
||||
output to stdout. Otherwise, the function specified by this argument
|
||||
will be called for each line of output; the output will not be written
|
||||
to stdout automatically.
|
||||
|
||||
If storeOutput==True, the output produced by the process will be saved
|
||||
as self.output.
|
||||
|
||||
If logfile is not None, the output produced by the process will be
|
||||
appended to the given file.
|
||||
"""
|
||||
|
||||
def __init__(self, cmd, logfile=None, storeOutput=True, **kwargs):
|
||||
"""
|
||||
If storeOutput=True, the output produced by the process will be saved
|
||||
as self.output.
|
||||
|
||||
If logfile is not None, the output produced by the process will be
|
||||
appended to the given file.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('processOutputLine', [])
|
||||
|
||||
# Print to standard output only if no outputline provided
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
from setuptools import setup
|
||||
|
||||
PACKAGE_VERSION = '0.11'
|
||||
PACKAGE_VERSION = '0.13'
|
||||
|
||||
setup(name='mozprocess',
|
||||
version=PACKAGE_VERSION,
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
# does not currently work on windows
|
||||
# see https://bugzilla.mozilla.org/show_bug.cgi?id=790765#c51
|
||||
|
||||
[DEFAULT]
|
||||
# bug https://bugzilla.mozilla.org/show_bug.cgi?id=778267#c26
|
||||
skip-if = (os == "win")
|
||||
|
||||
[test_mozprocess.py]
|
||||
disabled = bug 877864
|
||||
[test_mozprocess_kill.py]
|
||||
[test_mozprocess_kill_broad_wait.py]
|
||||
disabled = bug 921632
|
||||
[test_mozprocess_misc.py]
|
||||
[test_mozprocess_wait.py]
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
; Generate a Broad Process Tree
|
||||
; This generates a Tree of the form:
|
||||
;
|
||||
; main
|
||||
; \_ c1
|
||||
; | \_ c2
|
||||
; | \_ c2
|
||||
; | \_ c2
|
||||
; | \_ c2
|
||||
; | \_ c2
|
||||
; |
|
||||
; \_ c1
|
||||
; | \_ c2
|
||||
; | \_ c2
|
||||
; | \_ c2
|
||||
; | \_ c2
|
||||
; | \_ c2
|
||||
; |
|
||||
; \_ ... 23 more times
|
||||
|
||||
[main]
|
||||
children=25*c1
|
||||
maxtime=10
|
||||
|
||||
[c1]
|
||||
children=5*c2
|
||||
maxtime=10
|
||||
|
||||
[c2]
|
||||
maxtime=5
|
|
@ -0,0 +1,65 @@
|
|||
; Deep Process Tree
|
||||
; Should generate a process tree of the form:
|
||||
;
|
||||
; main
|
||||
; \_ c2
|
||||
; | \_ c5
|
||||
; | | \_ c6
|
||||
; | | \_ c7
|
||||
; | | \_ c8
|
||||
; | | \_ c1
|
||||
; | | \_ c4
|
||||
; | \_ c5
|
||||
; | \_ c6
|
||||
; | \_ c7
|
||||
; | \_ c8
|
||||
; | \_ c1
|
||||
; | \_ c4
|
||||
; \_ c2
|
||||
; | \_ c5
|
||||
; | | \_ c6
|
||||
; | | \_ c7
|
||||
; | | \_ c8
|
||||
; | | \_ c1
|
||||
; | | \_ c4
|
||||
; | \_ c5
|
||||
; | \_ c6
|
||||
; | \_ c7
|
||||
; | \_ c8
|
||||
; | \_ c1
|
||||
; | \_ c4
|
||||
; \_ c1
|
||||
; | \_ c4
|
||||
; \_ c1
|
||||
; \_ c4
|
||||
|
||||
[main]
|
||||
children=2*c1, 2*c2
|
||||
maxtime=20
|
||||
|
||||
[c1]
|
||||
children=c4
|
||||
maxtime=20
|
||||
|
||||
[c2]
|
||||
children=2*c5
|
||||
maxtime=20
|
||||
|
||||
[c4]
|
||||
maxtime=20
|
||||
|
||||
[c5]
|
||||
children=c6
|
||||
maxtime=20
|
||||
|
||||
[c6]
|
||||
children=c7
|
||||
maxtime=20
|
||||
|
||||
[c7]
|
||||
children=c8
|
||||
maxtime=20
|
||||
|
||||
[c8]
|
||||
children=c1
|
||||
maxtime=20
|
|
@ -0,0 +1,17 @@
|
|||
; Generates a normal process tree
|
||||
; Tree is of the form:
|
||||
; main
|
||||
; \_ c1
|
||||
; \_ c2
|
||||
|
||||
[main]
|
||||
children=c1,c2
|
||||
maxtime=10
|
||||
|
||||
[c1]
|
||||
children=c2
|
||||
maxtime=5
|
||||
|
||||
[c2]
|
||||
maxtime=5
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
; Generate a normal process tree
|
||||
; Tree is of the form:
|
||||
; main
|
||||
; \_ c1
|
||||
; \_ c2
|
||||
|
||||
[main]
|
||||
children=c1
|
||||
maxtime=10
|
||||
|
||||
[c1]
|
||||
children=2*c2
|
||||
maxtime=5
|
||||
|
||||
[c2]
|
||||
maxtime=5
|
|
@ -0,0 +1,16 @@
|
|||
; Generates a normal process tree
|
||||
; Tree is of the form:
|
||||
; main
|
||||
; \_ c1
|
||||
; \_ c2
|
||||
|
||||
[main]
|
||||
children=2*c1
|
||||
maxtime=300
|
||||
|
||||
[c1]
|
||||
children=2*c2
|
||||
maxtime=300
|
||||
|
||||
[c2]
|
||||
maxtime=300
|
|
@ -0,0 +1,192 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import ConfigParser
|
||||
import multiprocessing
|
||||
import time
|
||||
|
||||
ProcessNode = collections.namedtuple('ProcessNode', ['maxtime', 'children'])
|
||||
|
||||
class ProcessLauncher(object):
|
||||
|
||||
""" Create and Launch process trees specified by a '.ini' file
|
||||
|
||||
Typical .ini file accepted by this class :
|
||||
|
||||
[main]
|
||||
children=c1, 1*c2, 4*c3
|
||||
maxtime=10
|
||||
|
||||
[c1]
|
||||
children= 2*c2, c3
|
||||
maxtime=20
|
||||
|
||||
[c2]
|
||||
children=3*c3
|
||||
maxtime=5
|
||||
|
||||
[c3]
|
||||
maxtime=3
|
||||
|
||||
This generates a process tree of the form:
|
||||
[main]
|
||||
|---[c1]
|
||||
| |---[c2]
|
||||
| | |---[c3]
|
||||
| | |---[c3]
|
||||
| | |---[c3]
|
||||
| |
|
||||
| |---[c2]
|
||||
| | |---[c3]
|
||||
| | |---[c3]
|
||||
| | |---[c3]
|
||||
| |
|
||||
| |---[c3]
|
||||
|
|
||||
|---[c2]
|
||||
| |---[c3]
|
||||
| |---[c3]
|
||||
| |---[c3]
|
||||
|
|
||||
|---[c3]
|
||||
|---[c3]
|
||||
|---[c3]
|
||||
|
||||
Caveat: The section names cannot contain a '*'(asterisk) or a ','(comma)
|
||||
character as these are used as delimiters for parsing.
|
||||
"""
|
||||
|
||||
# Unit time for processes in seconds
|
||||
UNIT_TIME = 1
|
||||
|
||||
def __init__(self, manifest, verbose=False):
|
||||
"""
|
||||
Parses the manifest and stores the information about the process tree
|
||||
in a format usable by the class.
|
||||
|
||||
Raises IOError if :
|
||||
- The path does not exist
|
||||
- The file cannot be read
|
||||
Raises ConfigParser.*Error if:
|
||||
- Files does not contain section headers
|
||||
- File cannot be parsed because of incorrect specification
|
||||
|
||||
:param manifest: Path to the manifest file that contains the
|
||||
configuration for the process tree to be launched
|
||||
:verbose: Print the process start and end information.
|
||||
Genrates a lot of output. Disabled by default.
|
||||
"""
|
||||
|
||||
self.verbose=verbose
|
||||
|
||||
# Children is a dictionary used to store information from the,
|
||||
# Configuration file in a more usable format.
|
||||
# Key : string contain the name of child process
|
||||
# Value : A Named tuple of the form (max_time, (list of child processes of Key))
|
||||
# Where each child process is a list of type: [count to run, name of child]
|
||||
self.children = {}
|
||||
|
||||
|
||||
cfgparser = ConfigParser.ConfigParser()
|
||||
|
||||
if not cfgparser.read(manifest):
|
||||
raise IOError('The manifest %s could not be found/opened', manifest)
|
||||
|
||||
sections = cfgparser.sections()
|
||||
for section in sections:
|
||||
# Maxtime is a mandatory option
|
||||
# ConfigParser.NoOptionError is raised if maxtime does not exist
|
||||
if '*' in section or ',' in section:
|
||||
raise ConfigParser.ParsingError('%s is not a valid section name. Section names cannot contain a \'*\' or \',\'.' % section)
|
||||
m_time = cfgparser.get(section, 'maxtime')
|
||||
try:
|
||||
m_time = int(m_time)
|
||||
except ValueError:
|
||||
raise ValueError('Expected maxtime to be an integer, specified %s' % m_time)
|
||||
|
||||
# No children option implies there are no further children
|
||||
# Leaving the children option blank is an error.
|
||||
try:
|
||||
c = cfgparser.get(section, 'children')
|
||||
if not c:
|
||||
# If children is an empty field, assume no children
|
||||
children = None
|
||||
|
||||
else:
|
||||
# Tokenize chilren field, ignore empty strings
|
||||
children = [[y.strip() for y in x.strip().split('*', 1)]
|
||||
for x in c.split(',') if x]
|
||||
try:
|
||||
for i, child in enumerate(children):
|
||||
# No multiplicate factor infront of a process implies 1
|
||||
if len(child) == 1:
|
||||
children[i] = [1, child[0]]
|
||||
else:
|
||||
children[i][0] = int(child[0])
|
||||
|
||||
if children[i][1] not in sections:
|
||||
raise ConfigParser.ParsingError('No section corresponding to child %s' % child[1])
|
||||
except ValueError:
|
||||
raise ValueError('Expected process count to be an integer, specified %s' % child[0])
|
||||
|
||||
except ConfigParser.NoOptionError:
|
||||
children = None
|
||||
pn = ProcessNode(maxtime=m_time,
|
||||
children=children)
|
||||
self.children[section] = pn
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
This function launches the process tree.
|
||||
"""
|
||||
self._run('main', 0)
|
||||
|
||||
def _run(self, proc_name, level):
|
||||
"""
|
||||
Runs the process specified by the section-name `proc_name` in the manifest file.
|
||||
Then makes calls to launch the child processes of `proc_name`
|
||||
|
||||
:param proc_name: File name of the manifest as a string.
|
||||
:param level: Depth of the current process in the tree.
|
||||
"""
|
||||
if proc_name not in self.children.keys():
|
||||
raise IOError("%s is not a valid process" % proc_name)
|
||||
|
||||
maxtime = self.children[proc_name].maxtime
|
||||
if self.verbose:
|
||||
print "%sLaunching %s for %d*%d seconds" % (" "*level, proc_name, maxtime, self.UNIT_TIME)
|
||||
|
||||
while self.children[proc_name].children:
|
||||
child = self.children[proc_name].children.pop()
|
||||
|
||||
count, child_proc = child
|
||||
for i in range(count):
|
||||
p = multiprocessing.Process(target=self._run, args=(child[1], level+1))
|
||||
p.start()
|
||||
|
||||
self._launch(maxtime)
|
||||
if self.verbose:
|
||||
print "%sFinished %s" % (" "*level, proc_name)
|
||||
|
||||
def _launch(self, running_time):
|
||||
"""
|
||||
Create and launch a process and idles for the time specified by
|
||||
`running_time`
|
||||
|
||||
:param running_time: Running time of the process in seconds.
|
||||
"""
|
||||
elapsed_time = 0
|
||||
|
||||
while elapsed_time < running_time:
|
||||
time.sleep(self.UNIT_TIME)
|
||||
elapsed_time += self.UNIT_TIME
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("manifest", help="Specify the configuration .ini file")
|
||||
args = parser.parse_args()
|
||||
|
||||
proclaunch = ProcessLauncher(args.manifest)
|
||||
proclaunch.run()
|
|
@ -0,0 +1,84 @@
|
|||
import mozinfo
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
def check_for_process(processName):
|
||||
"""
|
||||
Use to determine if process of the given name is still running.
|
||||
|
||||
Returns:
|
||||
detected -- True if process is detected to exist, False otherwise
|
||||
output -- if process exists, stdout of the process, '' otherwise
|
||||
"""
|
||||
# TODO: replace with
|
||||
# https://github.com/mozilla/mozbase/blob/master/mozprocess/mozprocess/pid.py
|
||||
# which should be augmented from talos
|
||||
# see https://bugzilla.mozilla.org/show_bug.cgi?id=705864
|
||||
output = ''
|
||||
if mozinfo.isWin:
|
||||
# On windows we use tasklist
|
||||
p1 = subprocess.Popen(["tasklist"], stdout=subprocess.PIPE)
|
||||
output = p1.communicate()[0]
|
||||
detected = False
|
||||
for line in output.splitlines():
|
||||
if processName in line:
|
||||
detected = True
|
||||
break
|
||||
else:
|
||||
p1 = subprocess.Popen(["ps", "-ef"], stdout=subprocess.PIPE)
|
||||
p2 = subprocess.Popen(["grep", processName], stdin=p1.stdout, stdout=subprocess.PIPE)
|
||||
p1.stdout.close()
|
||||
output = p2.communicate()[0]
|
||||
detected = False
|
||||
for line in output.splitlines():
|
||||
if "grep %s" % processName in line:
|
||||
continue
|
||||
elif processName in line and not 'defunct' in line:
|
||||
detected = True
|
||||
break
|
||||
|
||||
return detected, output
|
||||
|
||||
class ProcTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.proclaunch = os.path.join(here, "proclaunch.py")
|
||||
cls.python = sys.executable
|
||||
|
||||
def determine_status(self,
|
||||
detected=False,
|
||||
output='',
|
||||
returncode=0,
|
||||
didtimeout=False,
|
||||
isalive=False,
|
||||
expectedfail=()):
|
||||
"""
|
||||
Use to determine if the situation has failed.
|
||||
Parameters:
|
||||
detected -- value from check_for_process to determine if the process is detected
|
||||
output -- string of data from detected process, can be ''
|
||||
returncode -- return code from process, defaults to 0
|
||||
didtimeout -- True if process timed out, defaults to False
|
||||
isalive -- Use True to indicate we pass if the process exists; however, by default
|
||||
the test will pass if the process does not exist (isalive == False)
|
||||
expectedfail -- Defaults to [], used to indicate a list of fields that are expected to fail
|
||||
"""
|
||||
if 'returncode' in expectedfail:
|
||||
self.assertTrue(returncode, "Detected an unexpected return code of: %s" % returncode)
|
||||
elif not isalive:
|
||||
self.assertTrue(returncode == 0, "Detected non-zero return code of: %d" % returncode)
|
||||
|
||||
if 'didtimeout' in expectedfail:
|
||||
self.assertTrue(didtimeout, "Detected that process didn't time out")
|
||||
else:
|
||||
self.assertTrue(not didtimeout, "Detected that process timed out")
|
||||
|
||||
if isalive:
|
||||
self.assertTrue(detected, "Detected process is not running, process output: %s" % output)
|
||||
else:
|
||||
self.assertTrue(not detected, "Detected process is still running, process output: %s" % output)
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
import proctest
|
||||
from mozprocess import processhandler
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
class ProcTestKill(proctest.ProcTest):
|
||||
""" Class to test various process tree killing scenatios """
|
||||
|
||||
def test_process_kill(self):
|
||||
"""Process is started, we kill it"""
|
||||
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_finish_python.ini"],
|
||||
cwd=here)
|
||||
p.run()
|
||||
p.kill()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout)
|
||||
|
||||
def test_process_kill_deep(self):
|
||||
"""Process is started, we kill it, we use a deep process tree"""
|
||||
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_deep_python.ini"],
|
||||
cwd=here)
|
||||
p.run()
|
||||
p.kill()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout)
|
||||
|
||||
def test_process_kill_deep_wait(self):
|
||||
"""Process is started, we use a deep process tree, we let it spawn
|
||||
for a bit, we kill it"""
|
||||
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_deep_python.ini"],
|
||||
cwd=here)
|
||||
p.run()
|
||||
# Let the tree spawn a bit, before attempting to kill
|
||||
time.sleep(3)
|
||||
p.kill()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout)
|
||||
|
||||
def test_process_kill_broad(self):
|
||||
"""Process is started, we kill it, we use a broad process tree"""
|
||||
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_broad_python.ini"],
|
||||
cwd=here)
|
||||
p.run()
|
||||
p.kill()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
import proctest
|
||||
from mozprocess import processhandler
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
class ProcTestKill(proctest.ProcTest):
|
||||
""" Class to test various process tree killing scenatios """
|
||||
|
||||
# This test should ideally be a part of test_mozprocess_kill.py
|
||||
# It has been separated for the purpose of tempporarily disabling it.
|
||||
# See https://bugzilla.mozilla.org/show_bug.cgi?id=921632
|
||||
def test_process_kill_broad_wait(self):
|
||||
"""Process is started, we use a broad process tree, we let it spawn
|
||||
for a bit, we kill it"""
|
||||
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_broad_python.ini"],
|
||||
cwd=here)
|
||||
p.run()
|
||||
# Let the tree spawn a bit, before attempting to kill
|
||||
time.sleep(3)
|
||||
p.kill()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
import proctest
|
||||
from mozprocess import processhandler
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
class ProcTestMisc(proctest.ProcTest):
|
||||
""" Class to test misc operations """
|
||||
|
||||
def test_process_output_twice(self):
|
||||
"""
|
||||
Process is started, then processOutput is called a second time explicitly
|
||||
"""
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch,
|
||||
"process_waittimeout_10s_python.ini"],
|
||||
cwd=here)
|
||||
|
||||
p.run()
|
||||
p.processOutput(timeout=5)
|
||||
p.wait()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout,
|
||||
False,
|
||||
())
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1,97 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
import proctest
|
||||
from mozprocess import processhandler
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
class ProcTestWait(proctest.ProcTest):
|
||||
""" Class to test process waits and timeouts """
|
||||
|
||||
def test_process_normal_finish(self):
|
||||
"""Process is started, runs to completion while we wait for it"""
|
||||
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_finish_python.ini"],
|
||||
cwd=here)
|
||||
p.run()
|
||||
p.wait()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout)
|
||||
|
||||
def test_process_wait(self):
|
||||
"""Process is started runs to completion while we wait indefinitely"""
|
||||
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch,
|
||||
"process_waittimeout_10s_python.ini"],
|
||||
cwd=here)
|
||||
p.run()
|
||||
p.wait()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout)
|
||||
|
||||
|
||||
def test_process_timeout(self):
|
||||
""" Process is started, runs but we time out waiting on it
|
||||
to complete
|
||||
"""
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_waittimeout_python.ini"],
|
||||
cwd=here)
|
||||
p.run(timeout=10)
|
||||
p.wait()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout,
|
||||
False,
|
||||
['returncode', 'didtimeout'])
|
||||
|
||||
def test_process_waittimeout(self):
|
||||
"""
|
||||
Process is started, then wait is called and times out.
|
||||
Process is still running and didn't timeout
|
||||
"""
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch,
|
||||
"process_waittimeout_10s_python.ini"],
|
||||
cwd=here)
|
||||
|
||||
p.run()
|
||||
p.wait(timeout=5)
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout,
|
||||
True,
|
||||
())
|
||||
|
||||
def test_process_waitnotimeout(self):
|
||||
""" Process is started, runs to completion before our wait times out
|
||||
"""
|
||||
p = processhandler.ProcessHandler([self.python, self.proclaunch,
|
||||
"process_waittimeout_10s_python.ini"],
|
||||
cwd=here)
|
||||
p.run(timeout=30)
|
||||
p.wait()
|
||||
|
||||
detected, output = proctest.check_for_process(self.proclaunch)
|
||||
self.determine_status(detected,
|
||||
output,
|
||||
p.proc.returncode,
|
||||
p.didTimeout)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -21,6 +21,9 @@ def abstractmethod(method):
|
|||
(repr(method), filename, line))
|
||||
return not_implemented
|
||||
|
||||
class RunnerNotStartedError(Exception):
|
||||
"""Exception handler in case the runner is not started."""
|
||||
|
||||
class Runner(object):
|
||||
|
||||
def __init__(self, profile, clean_profile=True, process_class=None,
|
||||
|
@ -30,6 +33,7 @@ class Runner(object):
|
|||
self.kp_kwargs = kp_kwargs or {}
|
||||
self.process_class = process_class or ProcessHandler
|
||||
self.process_handler = None
|
||||
self.returncode = None
|
||||
self.profile = profile
|
||||
self.log = mozlog.getLogger('MozRunner')
|
||||
self.symbols_path = symbols_path
|
||||
|
@ -72,17 +76,18 @@ class Runner(object):
|
|||
Use is_running() to determine whether or not a timeout occured.
|
||||
Timeout is ignored if interactive was set to True.
|
||||
"""
|
||||
if self.process_handler is None:
|
||||
return
|
||||
if self.process_handler is not None:
|
||||
if isinstance(self.process_handler, subprocess.Popen):
|
||||
self.returncode = self.process_handler.wait()
|
||||
else:
|
||||
self.process_handler.wait(timeout)
|
||||
self.returncode = self.process_handler.proc.poll()
|
||||
if self.returncode is not None:
|
||||
self.process_handler = None
|
||||
elif self.returncode is None:
|
||||
raise RunnerNotStartedError("Wait called before runner started")
|
||||
|
||||
if isinstance(self.process_handler, subprocess.Popen):
|
||||
return_code = self.process_handler.wait()
|
||||
else:
|
||||
self.process_handler.wait(timeout)
|
||||
return_code = self.process_handler.proc.poll()
|
||||
if return_code is not None:
|
||||
self.process_handler = None
|
||||
return return_code
|
||||
return self.returncode
|
||||
|
||||
def is_running(self):
|
||||
"""
|
||||
|
@ -117,6 +122,16 @@ class Runner(object):
|
|||
traceback.print_exc()
|
||||
return crashed
|
||||
|
||||
def check_for_crashes(self, dump_directory, test_name=None):
|
||||
crashed = False
|
||||
try:
|
||||
crashed = mozcrash.check_for_crashes(dump_directory,
|
||||
self.symbols_path,
|
||||
test_name=test_name)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return crashed
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Cleanup all runner state
|
||||
|
|
|
@ -6,7 +6,7 @@ import sys
|
|||
from setuptools import setup
|
||||
|
||||
PACKAGE_NAME = "mozrunner"
|
||||
PACKAGE_VERSION = '5.25'
|
||||
PACKAGE_VERSION = '5.26'
|
||||
|
||||
desc = """Reliable start/stop/configuration of Mozilla Applications (Firefox, Thunderbird, etc.)"""
|
||||
|
||||
|
@ -14,7 +14,7 @@ deps = ['mozcrash >= 0.10',
|
|||
'mozdevice >= 0.28',
|
||||
'mozinfo >= 0.7',
|
||||
'mozlog >= 1.3',
|
||||
'mozprocess >= 0.8',
|
||||
'mozprocess >= 0.13',
|
||||
'mozprofile >= 0.16',
|
||||
]
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче