[svn r37274] merging of file:///svn/py/branch/config/py/test/rsession/web.py
revisions 36936 to 37267: ------------------------------------------------------------------------ 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. ------------------------------------------------------------------------ r36993 | fijal | 2007-01-19 14:54:25 +0100 (Fri, 19 Jan 2007) | 2 lines Add possibility to scroll or not to scroll to web frontend. This checkin makes me believe that there is lot of stuff to do in JS backend. ------------------------------------------------------------------------ r36955 | hpk | 2007-01-18 17:44:21 +0100 (Thu, 18 Jan 2007) | 4 lines some mods to defaultconfest dist options and use "_dist_import_pypy", i.e. a leading underscore at least ------------------------------------------------------------------------ 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
7c2ccd8bc5
commit
a1f2e0a1e0
|
@ -0,0 +1,430 @@
|
|||
|
||||
""" web server for py.test
|
||||
"""
|
||||
|
||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
import thread, threading
|
||||
import re
|
||||
import time
|
||||
import random
|
||||
import Queue
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
|
||||
import py
|
||||
from py.__.test.rsession.rsession import RSession
|
||||
from py.__.test.rsession import report
|
||||
from py.__.test import collect
|
||||
from py.__.test.rsession.webdata import json
|
||||
|
||||
DATADIR = py.path.local(__file__).dirpath("webdata")
|
||||
FUNCTION_LIST = ["main", "show_skip", "show_traceback", "show_info", "hide_info",
|
||||
"show_host", "hide_host", "hide_messagebox", "opt_scroll"]
|
||||
|
||||
try:
|
||||
try:
|
||||
if not py.test.config.getvalue('_dist_import_pypy'):
|
||||
raise ImportError
|
||||
except AttributeError:
|
||||
pass
|
||||
from pypy.rpython.ootypesystem.bltregistry import MethodDesc, BasicExternal,\
|
||||
described
|
||||
from pypy.translator.js.main import rpython2javascript
|
||||
from pypy.translator.js import commproxy
|
||||
from pypy.rpython.extfunc import _callable
|
||||
|
||||
commproxy.USE_MOCHIKIT = False
|
||||
IMPORTED_PYPY = True
|
||||
except (ImportError, NameError):
|
||||
class BasicExternal(object):
|
||||
pass
|
||||
|
||||
def described(*args, **kwargs):
|
||||
def decorator(func):
|
||||
return func
|
||||
return decorator
|
||||
|
||||
def _callable(*args, **kwargs):
|
||||
pass
|
||||
|
||||
IMPORTED_PYPY = False
|
||||
|
||||
def add_item(event):
|
||||
""" A little helper
|
||||
"""
|
||||
item = event.item
|
||||
itemtype = item.__class__.__name__
|
||||
itemname = item.name
|
||||
fullitemname = "/".join(item.listnames())
|
||||
d = {'fullitemname': fullitemname, 'itemtype': itemtype,
|
||||
'itemname': itemname}
|
||||
#if itemtype == 'Module':
|
||||
try:
|
||||
d['length'] = str(len(list(event.item.tryiter())))
|
||||
except:
|
||||
d['length'] = "?"
|
||||
return d
|
||||
|
||||
class MultiQueue(object):
|
||||
""" a tailor-made queue (internally using Queue) for py.test.rsession.web
|
||||
|
||||
API-wise the main difference is that the get() method gets a sessid
|
||||
argument, which is used to determine what data to feed to the client
|
||||
|
||||
when a data queue for a sessid doesn't yet exist, it is created, and
|
||||
filled with data that has already been fed to the other clients
|
||||
"""
|
||||
def __init__(self):
|
||||
self._cache = []
|
||||
self._session_queues = {}
|
||||
self._lock = py.std.thread.allocate_lock()
|
||||
|
||||
def put(self, item):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
self._cache.append(item)
|
||||
for key, q in self._session_queues.items():
|
||||
q.put(item)
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def _del(self, sessid):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
del self._session_queues[sessid]
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def get(self, sessid):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if not sessid in self._session_queues:
|
||||
self._create_session_queue(sessid)
|
||||
finally:
|
||||
self._lock.release()
|
||||
return self._session_queues[sessid].get(sessid)
|
||||
|
||||
def empty(self):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if not self._session_queues:
|
||||
return not len(self._cache)
|
||||
for q in self._session_queues.values():
|
||||
if not q.empty():
|
||||
return False
|
||||
finally:
|
||||
self._lock.release()
|
||||
return True
|
||||
|
||||
def empty_queue(self, sessid):
|
||||
return self._session_queues[sessid].empty()
|
||||
|
||||
def _create_session_queue(self, sessid):
|
||||
self._session_queues[sessid] = q = Queue.Queue()
|
||||
for item in self._cache:
|
||||
q.put(item)
|
||||
|
||||
class ExportedMethods(BasicExternal):
|
||||
_render_xmlhttp = True
|
||||
def __init__(self):
|
||||
self.pending_events = MultiQueue()
|
||||
self.start_event = threading.Event()
|
||||
self.end_event = threading.Event()
|
||||
self.skip_reasons = {}
|
||||
self.fail_reasons = {}
|
||||
self.stdout = {}
|
||||
self.stderr = {}
|
||||
self.all = 0
|
||||
|
||||
def findmodule(self, item):
|
||||
# find the most outwards parent which is module
|
||||
current = item
|
||||
while current:
|
||||
if isinstance(current, collect.Module):
|
||||
break
|
||||
current = current.parent
|
||||
|
||||
if current is not None:
|
||||
return str(current.name), str("/".join(current.listnames()))
|
||||
else:
|
||||
return str(item.parent.name), str("/".join(item.parent.listnames()))
|
||||
|
||||
def show_hosts(self):
|
||||
self.start_event.wait()
|
||||
to_send = {}
|
||||
for host in self.hosts:
|
||||
to_send[host.hostid] = host.hostname
|
||||
return to_send
|
||||
show_hosts = described(retval={str:str}, args=[('callback',
|
||||
_callable([{str:str}]))])(show_hosts)
|
||||
|
||||
def show_skip(self, item_name="aa"):
|
||||
return {'item_name': item_name,
|
||||
'reason': self.skip_reasons[item_name]}
|
||||
show_skip = described(retval={str:str}, args=[('item_name',str),('callback',
|
||||
_callable([{str:str}]))])(show_skip)
|
||||
|
||||
def show_fail(self, item_name="aa"):
|
||||
return {'item_name':item_name,
|
||||
'traceback':str(self.fail_reasons[item_name]),
|
||||
'stdout':self.stdout[item_name],
|
||||
'stderr':self.stderr[item_name]}
|
||||
show_fail = described(retval={str:str}, args=[('item_name',str),('callback',
|
||||
_callable([{str:str}]))])(show_fail)
|
||||
|
||||
_sessids = None
|
||||
_sesslock = py.std.thread.allocate_lock()
|
||||
def show_sessid(self):
|
||||
if not self._sessids:
|
||||
self._sessids = []
|
||||
self._sesslock.acquire()
|
||||
try:
|
||||
while 1:
|
||||
chars = list(py.std.string.lowercase)
|
||||
py.std.random.shuffle(chars)
|
||||
sessid = ''.join(chars[:8])
|
||||
if sessid not in self._sessids:
|
||||
self._sessids.append(sessid)
|
||||
break
|
||||
finally:
|
||||
self._sesslock.release()
|
||||
return sessid
|
||||
show_sessid = described(retval=str, args=[('callback',
|
||||
_callable([str]))])(show_sessid)
|
||||
|
||||
def failed(self, **kwargs):
|
||||
if not 'sessid' in kwargs:
|
||||
return
|
||||
sessid = kwargs['sessid']
|
||||
to_del = -1
|
||||
for num, i in enumerate(self._sessids):
|
||||
if i == sessid:
|
||||
to_del = num
|
||||
if to_del != -1:
|
||||
del self._sessids[to_del]
|
||||
self.pending_events._del(kwargs['sessid'])
|
||||
|
||||
def show_all_statuses(self, sessid=-1):
|
||||
retlist = [self.show_status_change(sessid)]
|
||||
while not self.pending_events.empty_queue(sessid):
|
||||
retlist.append(self.show_status_change(sessid))
|
||||
retval = retlist
|
||||
return retval
|
||||
show_all_statuses = described(retval=[{str:str}],args=
|
||||
[('sessid',str), ('callback',_callable([[{str:str}]]))])(show_all_statuses)
|
||||
|
||||
def show_status_change(self, sessid):
|
||||
event = self.pending_events.get(sessid)
|
||||
if event is None:
|
||||
self.end_event.set()
|
||||
return {}
|
||||
# some dispatcher here
|
||||
if isinstance(event, report.ReceivedItemOutcome):
|
||||
args = {}
|
||||
outcome = event.outcome
|
||||
for key, val in outcome.__dict__.iteritems():
|
||||
args[key] = str(val)
|
||||
args.update(add_item(event))
|
||||
mod_name, mod_fullname = self.findmodule(event.item)
|
||||
args['modulename'] = str(mod_name)
|
||||
args['fullmodulename'] = str(mod_fullname)
|
||||
fullitemname = args['fullitemname']
|
||||
if outcome.skipped:
|
||||
self.skip_reasons[fullitemname] = outcome.skipped
|
||||
elif outcome.excinfo:
|
||||
self.fail_reasons[fullitemname] = self.repr_failure_tblong(
|
||||
event.item, outcome.excinfo, outcome.excinfo.traceback)
|
||||
self.stdout[fullitemname] = outcome.stdout
|
||||
self.stderr[fullitemname] = outcome.stderr
|
||||
elif outcome.signal:
|
||||
self.fail_reasons[fullitemname] = "Received signal %d" % outcome.signal
|
||||
self.stdout[fullitemname] = outcome.stdout
|
||||
self.stderr[fullitemname] = outcome.stderr
|
||||
if event.channel:
|
||||
args['hostkey'] = event.channel.gateway.host.hostid
|
||||
else:
|
||||
args['hostkey'] = ''
|
||||
elif isinstance(event, report.ItemStart):
|
||||
args = add_item(event)
|
||||
elif isinstance(event, report.TestFinished):
|
||||
args = {}
|
||||
args['run'] = str(self.all)
|
||||
args['fails'] = str(len(self.fail_reasons))
|
||||
args['skips'] = str(len(self.skip_reasons))
|
||||
elif isinstance(event, report.SendItem):
|
||||
args = add_item(event)
|
||||
args['hostkey'] = event.channel.gateway.host.hostid
|
||||
elif isinstance(event, report.HostReady):
|
||||
self.ready_hosts[event.host] = True
|
||||
args = {'hostname' : event.host.hostname, 'hostkey' : event.host.hostid}
|
||||
elif isinstance(event, report.FailedTryiter):
|
||||
args = add_item(event)
|
||||
elif isinstance(event, report.SkippedTryiter):
|
||||
args = add_item(event)
|
||||
args['reason'] = str(event.excinfo.value)
|
||||
else:
|
||||
args = {}
|
||||
args['event'] = str(event)
|
||||
args['type'] = event.__class__.__name__
|
||||
return args
|
||||
|
||||
def repr_failure_tblong(self, item, excinfo, traceback):
|
||||
lines = []
|
||||
for index, entry in py.builtin.enumerate(traceback):
|
||||
lines.append('----------')
|
||||
lines.append("%s: %s" % (entry.path, entry.lineno))
|
||||
lines += self.repr_source(entry.relline, entry.source)
|
||||
lines.append("%s: %s" % (excinfo.typename, excinfo.value))
|
||||
return "\n".join(lines)
|
||||
|
||||
def repr_source(self, relline, source):
|
||||
lines = []
|
||||
for num, line in enumerate(source.split("\n")):
|
||||
if num == relline:
|
||||
lines.append(">>>>" + line)
|
||||
else:
|
||||
lines.append(" " + line)
|
||||
return lines
|
||||
|
||||
def report_ReceivedItemOutcome(self, event):
|
||||
self.all += 1
|
||||
self.pending_events.put(event)
|
||||
|
||||
def report_ItemStart(self, event):
|
||||
if isinstance(event.item, py.test.collect.Module):
|
||||
self.pending_events.put(event)
|
||||
|
||||
def report_unknown(self, event):
|
||||
# XXX: right now, we just pass it for showing
|
||||
self.pending_events.put(event)
|
||||
|
||||
def report_TestStarted(self, event):
|
||||
# XXX: It overrides out self.hosts
|
||||
self.hosts = {}
|
||||
self.ready_hosts = {}
|
||||
for host in event.hosts:
|
||||
self.hosts[host] = host
|
||||
self.ready_hosts[host] = False
|
||||
self.start_event.set()
|
||||
self.pending_events.put(event)
|
||||
|
||||
def report(self, what):
|
||||
repfun = getattr(self, "report_" + what.__class__.__name__,
|
||||
self.report_unknown)
|
||||
try:
|
||||
repfun(what)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
print "Internal reporting problem"
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
for i in excinfo.traceback:
|
||||
print str(i)[2:-1]
|
||||
print excinfo
|
||||
|
||||
## try:
|
||||
## self.wait_flag.acquire()
|
||||
## self.pending_events.insert(0, event)
|
||||
## self.wait_flag.notify()
|
||||
## finally:
|
||||
## self.wait_flag.release()
|
||||
|
||||
exported_methods = ExportedMethods()
|
||||
|
||||
class TestHandler(BaseHTTPRequestHandler):
|
||||
exported_methods = exported_methods
|
||||
|
||||
def do_GET(self):
|
||||
path = self.path
|
||||
if path.endswith("/"):
|
||||
path = path[:-1]
|
||||
if path.startswith("/"):
|
||||
path = path[1:]
|
||||
m = re.match('^(.*)\?(.*)$', path)
|
||||
if m:
|
||||
path = m.group(1)
|
||||
getargs = m.group(2)
|
||||
else:
|
||||
getargs = ""
|
||||
name_path = path.replace(".", "_")
|
||||
method_to_call = getattr(self, "run_" + name_path, None)
|
||||
if method_to_call is None:
|
||||
exec_meth = getattr(self.exported_methods, name_path, None)
|
||||
if exec_meth is None:
|
||||
self.send_error(404, "File %s not found" % path)
|
||||
else:
|
||||
try:
|
||||
self.serve_data('text/json',
|
||||
json.write(exec_meth(**self.parse_args(getargs))))
|
||||
except socket.error:
|
||||
# client happily disconnected
|
||||
exported_methods.failed(**self.parse_args(getargs))
|
||||
else:
|
||||
method_to_call()
|
||||
|
||||
def parse_args(self, getargs):
|
||||
# parse get argument list
|
||||
if getargs == "":
|
||||
return {}
|
||||
|
||||
unquote = py.std.urllib.unquote
|
||||
args = {}
|
||||
arg_pairs = getargs.split("&")
|
||||
for arg in arg_pairs:
|
||||
key, value = arg.split("=")
|
||||
args[unquote(key)] = unquote(value)
|
||||
return args
|
||||
|
||||
def log_message(self, format, *args):
|
||||
# XXX just discard it
|
||||
pass
|
||||
|
||||
do_POST = do_GET
|
||||
|
||||
def run_(self):
|
||||
self.run_index()
|
||||
|
||||
def run_index(self):
|
||||
data = py.path.local(DATADIR).join("index.html").open().read()
|
||||
self.serve_data("text/html", data)
|
||||
|
||||
def run_jssource(self):
|
||||
js_name = py.path.local(__file__).dirpath("webdata").join("source.js")
|
||||
web_name = py.path.local(__file__).dirpath().join("webjs.py")
|
||||
if IMPORTED_PYPY and web_name.mtime() > js_name.mtime():
|
||||
from py.__.test.rsession import webjs
|
||||
|
||||
javascript_source = rpython2javascript(webjs,
|
||||
FUNCTION_LIST, use_pdb=False)
|
||||
open(str(js_name), "w").write(javascript_source)
|
||||
self.serve_data("text/javascript", javascript_source)
|
||||
else:
|
||||
js_source = open(str(js_name), "r").read()
|
||||
self.serve_data("text/javascript", js_source)
|
||||
|
||||
def serve_data(self, content_type, data):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", content_type)
|
||||
self.send_header("Content-length", len(data))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def start_server(server_address = ('', 8000), handler=TestHandler, start_new=True):
|
||||
httpd = HTTPServer(server_address, handler)
|
||||
|
||||
if start_new:
|
||||
thread.start_new_thread(httpd.serve_forever, ())
|
||||
print "Server started, listening on port %d" % (httpd.server_port,)
|
||||
return httpd
|
||||
else:
|
||||
print "Server started, listening on port %d" % (httpd.server_port,)
|
||||
httpd.serve_forever()
|
||||
|
||||
def kill_server():
|
||||
exported_methods.pending_events.put(None)
|
||||
while not exported_methods.pending_events.empty():
|
||||
time.sleep(.1)
|
||||
exported_methods.end_event.wait()
|
||||
|
Loading…
Reference in New Issue