[svn r37273] merging of file:///svn/py/branch/config/py/test/rsession/rsession.py
revisions 36936 to 37267: ------------------------------------------------------------------------ r37173 | fijal | 2007-01-23 11:26:32 +0100 (Tue, 23 Jan 2007) | 2 lines Adhere to --box option. ------------------------------------------------------------------------ r37127 | fijal | 2007-01-22 13:48:33 +0100 (Mon, 22 Jan 2007) | 2 lines Add a few docstrings. ------------------------------------------------------------------------ r37110 | hpk | 2007-01-22 00:39:24 +0100 (Mon, 22 Jan 2007) | 2 lines renaming some variables for more clarity ------------------------------------------------------------------------ r37065 | arigo | 2007-01-20 17:15:10 +0100 (Sat, 20 Jan 2007) | 3 lines Choose a random free port when using the --runbrowser option, but stick to the fixed 8000 when using --startserver only. ------------------------------------------------------------------------ r37063 | arigo | 2007-01-20 17:00:31 +0100 (Sat, 20 Jan 2007) | 5 lines webbrowser.open() may block until the browser finishes or not, so better start it from a background thread. An case where it blocks is if you define the $BROWSER environment variable. ------------------------------------------------------------------------ r37015 | fijal | 2007-01-19 17:24:32 +0100 (Fri, 19 Jan 2007) | 2 lines Major cleanup of hostmanage. Got rid of global functions in favor of some better-structured classes. ------------------------------------------------------------------------ r36988 | fijal | 2007-01-19 13:25:05 +0100 (Fri, 19 Jan 2007) | 2 lines Improve the C-c support, also keep web interface informed about crashes/interrupts. ------------------------------------------------------------------------ r36969 | fijal | 2007-01-18 20:10:36 +0100 (Thu, 18 Jan 2007) | 3 lines Move teardown hosts out of finally (needs further refactoring) Kill adddefaultoptions call for now. ------------------------------------------------------------------------ r36964 | hpk | 2007-01-18 19:30:02 +0100 (Thu, 18 Jan 2007) | 8 lines move rconfig functionality to general Config object, with docstrings, and trying to change rsession accordingly. note that it's called "make_repr" and "merge_repr" the latter to make it obvious that we are changing the instance with the given repr. ------------------------------------------------------------------------ r36962 | fijal | 2007-01-18 19:06:13 +0100 (Thu, 18 Jan 2007) | 3 lines Huge refactoring. Got rid of remote_options and session_options, two tests still fail, so it's intermediate checkin. ------------------------------------------------------------------------ r36943 | fijal | 2007-01-18 16:09:30 +0100 (Thu, 18 Jan 2007) | 3 lines at least LSession should work now. Did not touched yet the remote options stuff, but some simplifications were done. ------------------------------------------------------------------------ r36937 | fijal | 2007-01-18 14:22:54 +0100 (Thu, 18 Jan 2007) | 2 lines Create a branch for further config cleanups. ------------------------------------------------------------------------ --HG-- branch : trunk
This commit is contained in:
parent
6dbd42b60e
commit
7c2ccd8bc5
|
@ -0,0 +1,297 @@
|
|||
|
||||
""" Remote session base class
|
||||
"""
|
||||
|
||||
import os
|
||||
import py
|
||||
import sys
|
||||
import re
|
||||
import time
|
||||
|
||||
from py.__.test.rsession import report
|
||||
from py.__.test.rsession.master import \
|
||||
setup_slave, MasterNode, dispatch_loop, itemgen, randomgen
|
||||
from py.__.test.rsession.hostmanage import HostInfo, HostOptions, HostManager
|
||||
|
||||
from py.__.test.rsession.local import local_loop, plain_runner, apigen_runner,\
|
||||
box_runner
|
||||
from py.__.test.rsession.reporter import LocalReporter, RemoteReporter
|
||||
|
||||
class AbstractSession(object):
|
||||
"""
|
||||
An abstract session executes collectors/items through a runner.
|
||||
|
||||
"""
|
||||
def __init__(self, config, optimise_localhost=True):
|
||||
self.config = config
|
||||
self.optimise_localhost = optimise_localhost
|
||||
|
||||
def make_colitems(paths, baseon):
|
||||
# we presume that from the base we can simply get to
|
||||
# the target paths by joining the basenames
|
||||
res = []
|
||||
for x in paths:
|
||||
x = py.path.local(x)
|
||||
current = py.test.collect.Directory(baseon)
|
||||
relparts = x.relto(baseon).split(x.sep)
|
||||
assert relparts
|
||||
for part in relparts:
|
||||
next = current.join(part)
|
||||
assert next is not None, (current, part)
|
||||
current = next
|
||||
res.append(current)
|
||||
return res
|
||||
make_colitems = staticmethod(make_colitems)
|
||||
|
||||
def getpkgdir(path):
|
||||
path = py.path.local(path)
|
||||
pkgpath = path.pypkgpath()
|
||||
if pkgpath is None:
|
||||
pkgpath = path
|
||||
return pkgpath
|
||||
getpkgdir = staticmethod(getpkgdir)
|
||||
|
||||
def init_reporter(self, reporter, sshhosts, reporter_class, arg=""):
|
||||
""" This initialises so called `reporter` class, which will
|
||||
handle all event presenting to user. Does not get called
|
||||
if main received custom reporter
|
||||
"""
|
||||
startserverflag = self.config.option.startserver
|
||||
restflag = self.config.option.restreport
|
||||
|
||||
if startserverflag and reporter is None:
|
||||
from py.__.test.rsession.web import start_server, exported_methods
|
||||
if self.config.option.runbrowser:
|
||||
from socket import INADDR_ANY
|
||||
port = INADDR_ANY # pick a random port when starting browser
|
||||
else:
|
||||
port = 8000 # stick to a fixed port otherwise
|
||||
|
||||
reporter = exported_methods.report
|
||||
httpd = start_server(server_address = ('', port))
|
||||
port = httpd.server_port
|
||||
if self.config.option.runbrowser:
|
||||
import webbrowser, thread
|
||||
# webbrowser.open() may block until the browser finishes or not
|
||||
url = "http://localhost:%d" % (port,)
|
||||
thread.start_new_thread(webbrowser.open, (url,))
|
||||
elif reporter is None:
|
||||
if restflag:
|
||||
from py.__.test.rsession.rest import RestReporter
|
||||
reporter_class = RestReporter
|
||||
if arg:
|
||||
reporter_instance = reporter_class(self.config, sshhosts, self.getpkgdir(arg))
|
||||
else:
|
||||
reporter_instance = reporter_class(self.config, sshhosts)
|
||||
reporter = reporter_instance.report
|
||||
else:
|
||||
startserverflag = False
|
||||
|
||||
return reporter, startserverflag
|
||||
|
||||
def reporterror(reporter, data):
|
||||
excinfo, item = data
|
||||
if excinfo is None:
|
||||
reporter(report.ItemStart(item))
|
||||
elif excinfo.type is py.test.Item.Skipped:
|
||||
reporter(report.SkippedTryiter(excinfo, item))
|
||||
else:
|
||||
reporter(report.FailedTryiter(excinfo, item))
|
||||
reporterror = staticmethod(reporterror)
|
||||
|
||||
def kill_server(self, startserverflag):
|
||||
""" Kill web server
|
||||
"""
|
||||
if startserverflag:
|
||||
from py.__.test.rsession.web import kill_server
|
||||
kill_server()
|
||||
|
||||
def wrap_reporter(self, reporter):
|
||||
""" We wrap reporter around, which makes it possible to us to track
|
||||
number of failures
|
||||
"""
|
||||
self.was_failure = False
|
||||
def new_reporter(event):
|
||||
if isinstance(event, report.ReceivedItemOutcome) and \
|
||||
not event.outcome.passed and \
|
||||
not event.outcome.skipped:
|
||||
self.was_failure = True
|
||||
return reporter(event)
|
||||
checkfun = lambda : self.config.option.exitfirst and \
|
||||
self.was_failure
|
||||
# for tests
|
||||
self.checkfun = checkfun
|
||||
return new_reporter, checkfun
|
||||
|
||||
def parse_directories(sshhosts):
|
||||
""" Parse sshadresses of hosts to have distinct hostname/hostdir
|
||||
"""
|
||||
directories = {}
|
||||
for host in sshhosts:
|
||||
m = re.match("^(.*?):(.*)$", host.hostname)
|
||||
if m:
|
||||
host.hostname = m.group(1)
|
||||
host.relpath = m.group(2) + "-" + host.hostname
|
||||
else:
|
||||
host.relpath = "pytestcache-%s" % host.hostname
|
||||
|
||||
class RSession(AbstractSession):
|
||||
""" Remote version of session
|
||||
"""
|
||||
def main(self, reporter=None):
|
||||
""" main loop for running tests. """
|
||||
args = self.config.args
|
||||
|
||||
sshhosts, remotepython, rsync_roots = self.read_distributed_config()
|
||||
reporter, startserverflag = self.init_reporter(reporter,
|
||||
sshhosts, RemoteReporter)
|
||||
reporter, checkfun = self.wrap_reporter(reporter)
|
||||
|
||||
reporter(report.TestStarted(sshhosts))
|
||||
|
||||
pkgdir = self.getpkgdir(args[0])
|
||||
done_dict = {}
|
||||
hostopts = HostOptions(rsync_roots=rsync_roots,
|
||||
remote_python=remotepython,
|
||||
optimise_localhost=self.optimise_localhost)
|
||||
hostmanager = HostManager(sshhosts, self.config, pkgdir, hostopts)
|
||||
try:
|
||||
nodes = hostmanager.init_hosts(reporter, done_dict)
|
||||
reporter(report.RsyncFinished())
|
||||
try:
|
||||
self.dispatch_tests(nodes, args, pkgdir, reporter, checkfun, done_dict)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
print >>sys.stderr, "C-c pressed waiting for gateways to teardown..."
|
||||
channels = [node.channel for node in nodes]
|
||||
hostmanager.kill_channels(channels)
|
||||
hostmanager.teardown_gateways(reporter, channels)
|
||||
print >>sys.stderr, "... Done"
|
||||
raise
|
||||
|
||||
channels = [node.channel for node in nodes]
|
||||
hostmanager.teardown_hosts(reporter, channels, nodes,
|
||||
exitfirst=self.config.option.exitfirst)
|
||||
reporter(report.Nodes(nodes))
|
||||
retval = reporter(report.TestFinished())
|
||||
self.kill_server(startserverflag)
|
||||
return retval
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
reporter(report.InterruptedExecution())
|
||||
self.kill_server(startserverflag)
|
||||
raise
|
||||
except:
|
||||
reporter(report.CrashedExecution())
|
||||
self.kill_server(startserverflag)
|
||||
raise
|
||||
|
||||
def read_distributed_config(self):
|
||||
""" Read from conftest file the configuration of distributed testing
|
||||
"""
|
||||
try:
|
||||
rsync_roots = self.config.getvalue("distrsync_roots")
|
||||
except:
|
||||
rsync_roots = None # all files and directories in the pkgdir
|
||||
sshhosts = [HostInfo(i) for i in
|
||||
self.config.getvalue("disthosts")]
|
||||
parse_directories(sshhosts)
|
||||
remotepython = self.config.getvalue("dist_remotepython")
|
||||
return sshhosts, remotepython, rsync_roots
|
||||
|
||||
def dispatch_tests(self, nodes, args, pkgdir, reporter, checkfun, done_dict):
|
||||
colitems = self.make_colitems(args, baseon=pkgdir.dirpath())
|
||||
keyword = self.config.option.keyword
|
||||
itemgenerator = itemgen(colitems, reporter, keyword, self.reporterror)
|
||||
|
||||
all_tests = dispatch_loop(nodes, itemgenerator, checkfun)
|
||||
#if all_tests:
|
||||
# todo = {}
|
||||
# for key, value in all_tests.items():
|
||||
# if key not in done_dict:
|
||||
# todo[key] = True
|
||||
# rg = randomgen(todo, done_dict)
|
||||
# dispatch_loop(nodes, rg, lambda:False, max_tasks_per_node=1)
|
||||
|
||||
|
||||
class LSession(AbstractSession):
|
||||
""" Local version of session
|
||||
"""
|
||||
def main(self, reporter=None, runner=None):
|
||||
# check out if used options makes any sense
|
||||
args = self.config.args
|
||||
|
||||
sshhosts = [HostInfo('localhost')] # this is just an info to reporter
|
||||
|
||||
if not self.config.option.nomagic:
|
||||
py.magic.invoke(assertion=1)
|
||||
|
||||
reporter, startserverflag = self.init_reporter(reporter,
|
||||
sshhosts, LocalReporter, args[0])
|
||||
reporter, checkfun = self.wrap_reporter(reporter)
|
||||
|
||||
reporter(report.TestStarted(sshhosts))
|
||||
pkgdir = self.getpkgdir(args[0])
|
||||
colitems = self.make_colitems(args, baseon=pkgdir.dirpath())
|
||||
reporter(report.RsyncFinished())
|
||||
|
||||
if runner is None:
|
||||
runner = self.init_runner(pkgdir)
|
||||
|
||||
keyword = self.config.option.keyword
|
||||
|
||||
itemgenerator = itemgen(colitems, reporter, keyword, self.reporterror)
|
||||
local_loop(self, reporter, itemgenerator, checkfun, self.config, runner=runner)
|
||||
|
||||
retval = reporter(report.TestFinished())
|
||||
self.kill_server(startserverflag)
|
||||
|
||||
if not self.config.option.nomagic:
|
||||
py.magic.revoke(assertion=1)
|
||||
|
||||
self.write_docs(pkgdir)
|
||||
return retval
|
||||
|
||||
def write_docs(self, pkgdir):
|
||||
if self.config.option.apigen:
|
||||
from py.__.apigen.tracer.docstorage import DocStorageAccessor
|
||||
apigen = py.path.local(self.config.option.apigen).pyimport()
|
||||
print >>sys.stderr, 'building documentation'
|
||||
capture = py.io.OutErrCapture()
|
||||
try:
|
||||
try:
|
||||
apigen.build(pkgdir, DocStorageAccessor(self.docstorage))
|
||||
except (ValueError, AttributeError):
|
||||
#import traceback
|
||||
#exc, e, tb = sys.exc_info()
|
||||
#print '%s - %s' % (exc, e)
|
||||
#print ''.join(traceback.format_tb(tb))
|
||||
#del tb
|
||||
#print '-' * 79
|
||||
raise NotImplementedError("Provided script does not seem "
|
||||
"to contain build function")
|
||||
finally:
|
||||
capture.reset()
|
||||
|
||||
def init_runner(self, pkgdir):
|
||||
if self.config.option.apigen:
|
||||
from py.__.apigen.tracer.tracer import Tracer, DocStorage
|
||||
module = py
|
||||
try:
|
||||
apigen = py.path.local(self.config.option.apigen).pyimport()
|
||||
items = apigen.get_documentable_items(pkgdir)
|
||||
if isinstance(items, dict):
|
||||
self.docstorage = DocStorage().from_dict(items)
|
||||
else:
|
||||
self.docstorage = DocStorage().from_pkg(items)
|
||||
except ImportError:
|
||||
raise ImportError("Provided script cannot be imported")
|
||||
except (ValueError, AttributeError):
|
||||
raise NotImplementedError("Provided script does not seem "
|
||||
"to contain get_documentable_items")
|
||||
self.tracer = Tracer(self.docstorage)
|
||||
return apigen_runner
|
||||
else:
|
||||
if (self.config.getvalue('dist_boxing') or self.config.option.boxing)\
|
||||
and not self.config.option.nocapture:
|
||||
return box_runner
|
||||
return plain_runner
|
||||
|
Loading…
Reference in New Issue