Bug 929667 - Mirror mozprocess,mozrunner, r=ahal

This commit is contained in:
Jonathan Griffin 2013-10-24 13:21:39 -07:00
Родитель eb035c3fde
Коммит 672e0179ef
17 изменённых файлов: 788 добавлений и 70 удалений

Просмотреть файл

@ -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',
]