324 lines
10 KiB
Python
324 lines
10 KiB
Python
# Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import urlparse
|
|
|
|
from hooks import install
|
|
|
|
from paste import fileapp
|
|
from paste import httpserver
|
|
|
|
import webapp2
|
|
from webapp2 import Route, RedirectHandler
|
|
|
|
from dashboard_build import dashboard_dev_server_config
|
|
from tracing_build import tracing_dev_server_config
|
|
from netlog_viewer_build import netlog_viewer_dev_server_config
|
|
|
|
_MAIN_HTML = """<html><body>
|
|
<h1>Run Unit Tests</h1>
|
|
<ul>
|
|
%s
|
|
</ul>
|
|
<h1>Quick links</h1>
|
|
<ul>
|
|
%s
|
|
</ul>
|
|
</body></html>
|
|
"""
|
|
|
|
_QUICK_LINKS = [
|
|
('Trace File Viewer',
|
|
'/tracing_examples/trace_viewer.html'),
|
|
('Metrics debugger',
|
|
'/tracing_examples/metrics_debugger.html'),
|
|
]
|
|
|
|
_LINK_ITEM = '<li><a href="%s">%s</a></li>'
|
|
|
|
def _GetFilesIn(basedir):
|
|
data_files = []
|
|
for dirpath, dirnames, filenames in os.walk(basedir, followlinks=True):
|
|
new_dirnames = [d for d in dirnames if not d.startswith('.')]
|
|
del dirnames[:]
|
|
dirnames += new_dirnames
|
|
|
|
for f in filenames:
|
|
if f.startswith('.'):
|
|
continue
|
|
if f == 'README.md':
|
|
continue
|
|
full_f = os.path.join(dirpath, f)
|
|
rel_f = os.path.relpath(full_f, basedir)
|
|
data_files.append(rel_f)
|
|
|
|
data_files.sort()
|
|
return data_files
|
|
|
|
|
|
def _RelPathToUnixPath(p):
|
|
return p.replace(os.sep, '/')
|
|
|
|
|
|
class TestResultHandler(webapp2.RequestHandler):
|
|
def post(self, *args, **kwargs): # pylint: disable=unused-argument
|
|
msg = self.request.body
|
|
ostream = sys.stdout if 'PASSED' in msg else sys.stderr
|
|
ostream.write(msg + '\n')
|
|
return self.response.write('')
|
|
|
|
|
|
class TestsCompletedHandler(webapp2.RequestHandler):
|
|
def post(self, *args, **kwargs): # pylint: disable=unused-argument
|
|
msg = self.request.body
|
|
sys.stdout.write(msg + '\n')
|
|
exit_code = 0 if 'ALL_PASSED' in msg else 1
|
|
if hasattr(self.app.server, 'please_exit'):
|
|
self.app.server.please_exit(exit_code)
|
|
return self.response.write('')
|
|
|
|
|
|
class DirectoryListingHandler(webapp2.RequestHandler):
|
|
def get(self, *args, **kwargs): # pylint: disable=unused-argument
|
|
source_path = kwargs.pop('_source_path', None)
|
|
mapped_path = kwargs.pop('_mapped_path', None)
|
|
assert mapped_path.endswith('/')
|
|
|
|
data_files_relative_to_top = _GetFilesIn(source_path)
|
|
data_files = [mapped_path + x
|
|
for x in data_files_relative_to_top]
|
|
|
|
files_as_json = json.dumps(data_files)
|
|
self.response.content_type = 'application/json'
|
|
return self.response.write(files_as_json)
|
|
|
|
|
|
class FileAppWithGZipHandling(fileapp.FileApp):
|
|
def guess_type(self):
|
|
content_type, content_encoding = \
|
|
super(FileAppWithGZipHandling, self).guess_type()
|
|
if not self.filename.endswith('.gz'):
|
|
return content_type, content_encoding
|
|
# By default, FileApp serves gzip files as their underlying type with
|
|
# Content-Encoding of gzip. That causes them to show up on the client
|
|
# decompressed. That ends up being surprising to our xhr.html system.
|
|
return None, None
|
|
|
|
class SourcePathsHandler(webapp2.RequestHandler):
|
|
def get(self, *args, **kwargs): # pylint: disable=unused-argument
|
|
source_paths = kwargs.pop('_source_paths', [])
|
|
|
|
path = self.request.path
|
|
# This is how we do it. Its... strange, but its what we've done since
|
|
# the dawn of time. Aka 4 years ago, lol.
|
|
for mapped_path in source_paths:
|
|
rel = os.path.relpath(path, '/')
|
|
candidate = os.path.join(mapped_path, rel)
|
|
if os.path.exists(candidate):
|
|
app = FileAppWithGZipHandling(candidate)
|
|
app.cache_control(no_cache=True)
|
|
return app
|
|
self.abort(404)
|
|
|
|
@staticmethod
|
|
def GetServingPathForAbsFilename(source_paths, filename):
|
|
if not os.path.isabs(filename):
|
|
raise Exception('filename must be an absolute path')
|
|
|
|
for mapped_path in source_paths:
|
|
if not filename.startswith(mapped_path):
|
|
continue
|
|
rel = os.path.relpath(filename, mapped_path)
|
|
unix_rel = _RelPathToUnixPath(rel)
|
|
return unix_rel
|
|
return None
|
|
|
|
|
|
class SimpleDirectoryHandler(webapp2.RequestHandler):
|
|
def get(self, *args, **kwargs): # pylint: disable=unused-argument
|
|
top_path = os.path.abspath(kwargs.pop('_top_path', None))
|
|
if not top_path.endswith(os.path.sep):
|
|
top_path += os.path.sep
|
|
|
|
joined_path = os.path.abspath(
|
|
os.path.join(top_path, kwargs.pop('rest_of_path')))
|
|
if not joined_path.startswith(top_path):
|
|
self.response.set_status(403)
|
|
return
|
|
app = FileAppWithGZipHandling(joined_path)
|
|
app.cache_control(no_cache=True)
|
|
return app
|
|
|
|
|
|
class TestOverviewHandler(webapp2.RequestHandler):
|
|
def get(self, *args, **kwargs): # pylint: disable=unused-argument
|
|
test_links = []
|
|
for name, path in kwargs.pop('pds').iteritems():
|
|
test_links.append(_LINK_ITEM % (path, name))
|
|
quick_links = []
|
|
for name, path in _QUICK_LINKS:
|
|
quick_links.append(_LINK_ITEM % (path, name))
|
|
self.response.out.write(_MAIN_HTML % ('\n'.join(test_links),
|
|
'\n'.join(quick_links)))
|
|
|
|
class DevServerApp(webapp2.WSGIApplication):
|
|
def __init__(self, pds, args):
|
|
super(DevServerApp, self).__init__(debug=True)
|
|
self.pds = pds
|
|
self._server = None
|
|
self._all_source_paths = []
|
|
self._all_mapped_test_data_paths = []
|
|
self._InitFromArgs(args)
|
|
|
|
@property
|
|
def server(self):
|
|
return self._server
|
|
|
|
@server.setter
|
|
def server(self, server):
|
|
self._server = server
|
|
|
|
def _InitFromArgs(self, args):
|
|
default_tests = dict((pd.GetName(), pd.GetRunUnitTestsUrl())
|
|
for pd in self.pds)
|
|
routes = [
|
|
Route('/tests.html', TestOverviewHandler,
|
|
defaults={'pds': default_tests}),
|
|
Route('', RedirectHandler, defaults={'_uri': '/tests.html'}),
|
|
Route('/', RedirectHandler, defaults={'_uri': '/tests.html'}),
|
|
]
|
|
for pd in self.pds:
|
|
routes += pd.GetRoutes(args)
|
|
routes += [
|
|
Route('/%s/notify_test_result' % pd.GetName(),
|
|
TestResultHandler),
|
|
Route('/%s/notify_tests_completed' % pd.GetName(),
|
|
TestsCompletedHandler)
|
|
]
|
|
|
|
for pd in self.pds:
|
|
# Test data system.
|
|
for mapped_path, source_path in pd.GetTestDataPaths(args):
|
|
self._all_mapped_test_data_paths.append((mapped_path, source_path))
|
|
routes.append(Route('%s__file_list__' % mapped_path,
|
|
DirectoryListingHandler,
|
|
defaults={
|
|
'_source_path': source_path,
|
|
'_mapped_path': mapped_path
|
|
}))
|
|
routes.append(Route('%s<rest_of_path:.+>' % mapped_path,
|
|
SimpleDirectoryHandler,
|
|
defaults={'_top_path': source_path}))
|
|
|
|
# This must go last, because its catch-all.
|
|
#
|
|
# Its funky that we have to add in the root path. The long term fix is to
|
|
# stop with the crazy multi-source-pathing thing.
|
|
for pd in self.pds:
|
|
self._all_source_paths += pd.GetSourcePaths(args)
|
|
routes.append(
|
|
Route('/<:.+>', SourcePathsHandler,
|
|
defaults={'_source_paths': self._all_source_paths}))
|
|
|
|
for route in routes:
|
|
self.router.add(route)
|
|
|
|
def GetAbsFilenameForHref(self, href):
|
|
for source_path in self._all_source_paths:
|
|
full_source_path = os.path.abspath(source_path)
|
|
expanded_href_path = os.path.abspath(os.path.join(full_source_path,
|
|
href.lstrip('/')))
|
|
if (os.path.exists(expanded_href_path) and
|
|
os.path.commonprefix([full_source_path,
|
|
expanded_href_path]) == full_source_path):
|
|
return expanded_href_path
|
|
return None
|
|
|
|
def GetURLForAbsFilename(self, filename):
|
|
assert self.server is not None
|
|
for mapped_path, source_path in self._all_mapped_test_data_paths:
|
|
if not filename.startswith(source_path):
|
|
continue
|
|
rel = os.path.relpath(filename, source_path)
|
|
unix_rel = _RelPathToUnixPath(rel)
|
|
url = urlparse.urljoin(mapped_path, unix_rel)
|
|
return url
|
|
|
|
path = SourcePathsHandler.GetServingPathForAbsFilename(
|
|
self._all_source_paths, filename)
|
|
if path is None:
|
|
return None
|
|
return urlparse.urljoin('/', path)
|
|
|
|
|
|
def _AddPleaseExitMixinToServer(server):
|
|
# Shutting down httpserver gracefully and yielding a return code requires
|
|
# a bit of mixin code.
|
|
|
|
exit_code_attempt = []
|
|
def PleaseExit(exit_code):
|
|
if len(exit_code_attempt) > 0:
|
|
return
|
|
exit_code_attempt.append(exit_code)
|
|
server.running = False
|
|
|
|
real_serve_forever = server.serve_forever
|
|
|
|
def ServeForever():
|
|
try:
|
|
real_serve_forever()
|
|
except KeyboardInterrupt:
|
|
# allow CTRL+C to shutdown
|
|
return 255
|
|
|
|
if len(exit_code_attempt) == 1:
|
|
return exit_code_attempt[0]
|
|
# The serve_forever returned for some reason separate from
|
|
# exit_please.
|
|
return 0
|
|
|
|
server.please_exit = PleaseExit
|
|
server.serve_forever = ServeForever
|
|
|
|
|
|
def _AddCommandLineArguments(pds, argv):
|
|
parser = argparse.ArgumentParser(description='Run development server')
|
|
parser.add_argument(
|
|
'--no-install-hooks', dest='install_hooks', action='store_false')
|
|
parser.add_argument('-p', '--port', default=8003, type=int)
|
|
for pd in pds:
|
|
g = parser.add_argument_group(pd.GetName())
|
|
pd.AddOptionstToArgParseGroup(g)
|
|
args = parser.parse_args(args=argv[1:])
|
|
return args
|
|
|
|
|
|
def Main(argv):
|
|
pds = [
|
|
dashboard_dev_server_config.DashboardDevServerConfig(),
|
|
tracing_dev_server_config.TracingDevServerConfig(),
|
|
netlog_viewer_dev_server_config.NetlogViewerDevServerConfig(),
|
|
]
|
|
|
|
args = _AddCommandLineArguments(pds, argv)
|
|
|
|
if args.install_hooks:
|
|
install.InstallHooks()
|
|
|
|
app = DevServerApp(pds, args=args)
|
|
|
|
server = httpserver.serve(app, host='127.0.0.1', port=args.port,
|
|
start_loop=False, daemon_threads=True)
|
|
_AddPleaseExitMixinToServer(server)
|
|
# pylint: disable=no-member
|
|
server.urlbase = 'http://127.0.0.1:%i' % server.server_port
|
|
app.server = server
|
|
|
|
sys.stderr.write('Now running on %s\n' % server.urlbase)
|
|
|
|
return server.serve_forever()
|