Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(422)

Side by Side Diff: gm/rebaseline_server/server.py

Issue 26891003: rebaseline_server: allow client to pull all results, or just failures (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: todo_about_global Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « gm/rebaseline_server/results.py ('k') | gm/rebaseline_server/static/loader.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 """ 3 """
4 Copyright 2013 Google Inc. 4 Copyright 2013 Google Inc.
5 5
6 Use of this source code is governed by a BSD-style license that can be 6 Use of this source code is governed by a BSD-style license that can be
7 found in the LICENSE file. 7 found in the LICENSE file.
8 8
9 HTTP server for our HTML rebaseline viewer. 9 HTTP server for our HTML rebaseline viewer.
10 """ 10 """
11 11
12 # System-level imports 12 # System-level imports
13 import argparse 13 import argparse
14 import BaseHTTPServer 14 import BaseHTTPServer
15 import json 15 import json
16 import logging
16 import os 17 import os
17 import posixpath 18 import posixpath
18 import re 19 import re
19 import shutil 20 import shutil
20 import sys 21 import sys
22 import urlparse
21 23
22 # Imports from within Skia 24 # Imports from within Skia
23 # 25 #
24 # We need to add the 'tools' directory, so that we can import svn.py within 26 # We need to add the 'tools' directory, so that we can import svn.py within
25 # that directory. 27 # that directory.
26 # Make sure that the 'tools' dir is in the PYTHONPATH, but add it at the *end* 28 # Make sure that the 'tools' dir is in the PYTHONPATH, but add it at the *end*
27 # so any dirs that are already in the PYTHONPATH will be preferred. 29 # so any dirs that are already in the PYTHONPATH will be preferred.
28 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) 30 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
29 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) 31 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY))
30 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools') 32 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools')
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
84 return self._export 86 return self._export
85 87
86 def fetch_results(self): 88 def fetch_results(self):
87 """ Create self.results, based on the expectations in 89 """ Create self.results, based on the expectations in
88 self._expectations_dir and the latest actuals from skia-autogen. 90 self._expectations_dir and the latest actuals from skia-autogen.
89 91
90 TODO(epoger): Add a new --browseonly mode setting. In that mode, 92 TODO(epoger): Add a new --browseonly mode setting. In that mode,
91 the gm-actuals and expectations will automatically be updated every few 93 the gm-actuals and expectations will automatically be updated every few
92 minutes. See discussion in https://codereview.chromium.org/24274003/ . 94 minutes. See discussion in https://codereview.chromium.org/24274003/ .
93 """ 95 """
94 print 'Checking out latest actual GM results from %s into %s ...' % ( 96 logging.info('Checking out latest actual GM results from %s into %s ...' % (
95 ACTUALS_SVN_REPO, self._actuals_dir) 97 ACTUALS_SVN_REPO, self._actuals_dir))
96 actuals_repo = svn.Svn(self._actuals_dir) 98 actuals_repo = svn.Svn(self._actuals_dir)
97 if not os.path.isdir(self._actuals_dir): 99 if not os.path.isdir(self._actuals_dir):
98 os.makedirs(self._actuals_dir) 100 os.makedirs(self._actuals_dir)
99 actuals_repo.Checkout(ACTUALS_SVN_REPO, '.') 101 actuals_repo.Checkout(ACTUALS_SVN_REPO, '.')
100 else: 102 else:
101 actuals_repo.Update('.') 103 actuals_repo.Update('.')
102 print 'Parsing results from actuals in %s and expectations in %s ...' % ( 104 logging.info(
103 self._actuals_dir, self._expectations_dir) 105 'Parsing results from actuals in %s and expectations in %s ...' % (
106 self._actuals_dir, self._expectations_dir))
104 self.results = results.Results( 107 self.results = results.Results(
105 actuals_root=self._actuals_dir, 108 actuals_root=self._actuals_dir,
106 expected_root=self._expectations_dir) 109 expected_root=self._expectations_dir)
107 110
108 def run(self): 111 def run(self):
109 self.fetch_results() 112 self.fetch_results()
110 if self._export: 113 if self._export:
111 server_address = ('', self._port) 114 server_address = ('', self._port)
112 print ('WARNING: Running in "export" mode. Users on other machines will ' 115 logging.warning('Running in "export" mode. Users on other machines will '
113 'be able to modify your GM expectations!') 116 'be able to modify your GM expectations!')
114 else: 117 else:
115 server_address = ('127.0.0.1', self._port) 118 server_address = ('127.0.0.1', self._port)
116 http_server = BaseHTTPServer.HTTPServer(server_address, HTTPRequestHandler) 119 http_server = BaseHTTPServer.HTTPServer(server_address, HTTPRequestHandler)
117 print 'Ready for requests on http://%s:%d' % ( 120 logging.info('Ready for requests on http://%s:%d' % (
118 http_server.server_name, http_server.server_port) 121 http_server.server_name, http_server.server_port))
119 http_server.serve_forever() 122 http_server.serve_forever()
120 123
121 124
122 class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 125 class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
123 """ HTTP request handlers for various types of queries this server knows 126 """ HTTP request handlers for various types of queries this server knows
124 how to handle (static HTML and Javascript, expected/actual results, etc.) 127 how to handle (static HTML and Javascript, expected/actual results, etc.)
125 """ 128 """
126 def do_GET(self): 129 def do_GET(self):
127 """ Handles all GET requests, forwarding them to the appropriate 130 """ Handles all GET requests, forwarding them to the appropriate
128 do_GET_* dispatcher. """ 131 do_GET_* dispatcher. """
129 if self.path == '' or self.path == '/' or self.path == '/index.html' : 132 if self.path == '' or self.path == '/' or self.path == '/index.html' :
130 self.redirect_to('/static/view.html') 133 self.redirect_to('/static/view.html?resultsToLoad=all')
131 return 134 return
132 if self.path == '/favicon.ico' : 135 if self.path == '/favicon.ico' :
133 self.redirect_to('/static/favicon.ico') 136 self.redirect_to('/static/favicon.ico')
134 return 137 return
135 138
136 # All requests must be of this form: 139 # All requests must be of this form:
137 # /dispatcher/remainder 140 # /dispatcher/remainder
138 # where 'dispatcher' indicates which do_GET_* dispatcher to run 141 # where 'dispatcher' indicates which do_GET_* dispatcher to run
139 # and 'remainder' is the remaining path sent to that dispatcher. 142 # and 'remainder' is the remaining path sent to that dispatcher.
140 normpath = posixpath.normpath(self.path) 143 normpath = posixpath.normpath(self.path)
141 (dispatcher_name, remainder) = PATHSPLIT_RE.match(normpath).groups() 144 (dispatcher_name, remainder) = PATHSPLIT_RE.match(normpath).groups()
142 dispatchers = { 145 dispatchers = {
143 'results': self.do_GET_results, 146 'results': self.do_GET_results,
144 'static': self.do_GET_static, 147 'static': self.do_GET_static,
145 } 148 }
146 dispatcher = dispatchers[dispatcher_name] 149 dispatcher = dispatchers[dispatcher_name]
147 dispatcher(remainder) 150 dispatcher(remainder)
148 151
149 def do_GET_results(self, result_type): 152 def do_GET_results(self, type):
150 """ Handle a GET request for GM results. 153 """ Handle a GET request for GM results.
151 For now, we ignore the remaining path info, because we only know how to
152 return all results.
153 154
154 Args: 155 Args:
155 result_type: currently unused 156 type: string indicating which set of results to return;
156 157 must be one of the results.RESULTS_* constants
157 TODO(epoger): Unless we start making use of result_type, remove that 158 """
158 parameter.""" 159 logging.debug('do_GET_results: sending results of type "%s"' % type)
159 print 'do_GET_results: sending results of type "%s"' % result_type 160 try:
160 # TODO(epoger): Cache response_dict rather than the results object, to save 161 # TODO(epoger): Rather than using a global variable for the handler
161 # time on subsequent fetches (no need to regenerate the header, etc.) 162 # to refer to the Server object, make Server a subclass of
162 response_dict = _SERVER.results.GetAll() 163 # HTTPServer, and then it could be available to the handler via
163 if response_dict: 164 # the handler's .server instance variable.
165 response_dict = _SERVER.results.get_results_of_type(type)
164 response_dict['header'] = { 166 response_dict['header'] = {
165 # Hash of testData, which the client must return with any edits-- 167 # Hash of testData, which the client must return with any edits--
166 # this ensures that the edits were made to a particular dataset. 168 # this ensures that the edits were made to a particular dataset.
167 'data-hash': str(hash(repr(response_dict['testData']))), 169 'data-hash': str(hash(repr(response_dict['testData']))),
168 170
169 # Whether the server will accept edits back. 171 # Whether the server will accept edits back.
170 # TODO(epoger): Not yet implemented, so hardcoding to False; 172 # TODO(epoger): Not yet implemented, so hardcoding to False;
171 # once we implement the 'browseonly' mode discussed in 173 # once we implement the 'browseonly' mode discussed in
172 # https://codereview.chromium.org/24274003/#msg6 , this value will vary. 174 # https://codereview.chromium.org/24274003/#msg6 , this value will vary.
173 'isEditable': False, 175 'isEditable': False,
174 176
175 # Whether the service is accessible from other hosts. 177 # Whether the service is accessible from other hosts.
176 'isExported': _SERVER.is_exported(), 178 'isExported': _SERVER.is_exported(),
177 } 179 }
178 self.send_json_dict(response_dict) 180 self.send_json_dict(response_dict)
179 else: 181 except:
180 self.send_error(404) 182 self.send_error(404)
181 183
182 def do_GET_static(self, path): 184 def do_GET_static(self, path):
183 """ Handle a GET request for a file under the 'static' directory. 185 """ Handle a GET request for a file under the 'static' directory.
184 Only allow serving of files within the 'static' directory that is a 186 Only allow serving of files within the 'static' directory that is a
185 filesystem sibling of this script. 187 filesystem sibling of this script.
186 188
187 Args: 189 Args:
188 path: path to file (under static directory) to retrieve 190 path: path to file (under static directory) to retrieve
189 """ 191 """
190 print 'do_GET_static: sending file "%s"' % path 192 # Strip arguments ('?resultsToLoad=all') from the path
193 path = urlparse.urlparse(path).path
194
195 logging.debug('do_GET_static: sending file "%s"' % path)
191 static_dir = os.path.realpath(os.path.join(PARENT_DIRECTORY, 'static')) 196 static_dir = os.path.realpath(os.path.join(PARENT_DIRECTORY, 'static'))
192 full_path = os.path.realpath(os.path.join(static_dir, path)) 197 full_path = os.path.realpath(os.path.join(static_dir, path))
193 if full_path.startswith(static_dir): 198 if full_path.startswith(static_dir):
194 self.send_file(full_path) 199 self.send_file(full_path)
195 else: 200 else:
196 print ('Attempted do_GET_static() of path [%s] outside of static dir [%s]' 201 logging.error(
197 % (full_path, static_dir)) 202 'Attempted do_GET_static() of path [%s] outside of static dir [%s]'
203 % (full_path, static_dir))
198 self.send_error(404) 204 self.send_error(404)
199 205
200 def redirect_to(self, url): 206 def redirect_to(self, url):
201 """ Redirect the HTTP client to a different url. 207 """ Redirect the HTTP client to a different url.
202 208
203 Args: 209 Args:
204 url: URL to redirect the HTTP client to 210 url: URL to redirect the HTTP client to
205 """ 211 """
206 self.send_response(301) 212 self.send_response(301)
207 self.send_header('Location', url) 213 self.send_header('Location', url)
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
239 Args: 245 Args:
240 json_dict: dictionary to send 246 json_dict: dictionary to send
241 """ 247 """
242 self.send_response(200) 248 self.send_response(200)
243 self.send_header('Content-type', 'application/json') 249 self.send_header('Content-type', 'application/json')
244 self.end_headers() 250 self.end_headers()
245 json.dump(json_dict, self.wfile) 251 json.dump(json_dict, self.wfile)
246 252
247 253
248 def main(): 254 def main():
255 logging.basicConfig(level=logging.INFO)
249 parser = argparse.ArgumentParser() 256 parser = argparse.ArgumentParser()
250 parser.add_argument('--actuals-dir', 257 parser.add_argument('--actuals-dir',
251 help=('Directory into which we will check out the latest ' 258 help=('Directory into which we will check out the latest '
252 'actual GM results. If this directory does not ' 259 'actual GM results. If this directory does not '
253 'exist, it will be created. Defaults to %(default)s'), 260 'exist, it will be created. Defaults to %(default)s'),
254 default=DEFAULT_ACTUALS_DIR) 261 default=DEFAULT_ACTUALS_DIR)
255 parser.add_argument('--expectations-dir', 262 parser.add_argument('--expectations-dir',
256 help=('Directory under which to find GM expectations; ' 263 help=('Directory under which to find GM expectations; '
257 'defaults to %(default)s'), 264 'defaults to %(default)s'),
258 default=DEFAULT_EXPECTATIONS_DIR) 265 default=DEFAULT_EXPECTATIONS_DIR)
259 parser.add_argument('--export', action='store_true', 266 parser.add_argument('--export', action='store_true',
260 help=('Instead of only allowing access from HTTP clients ' 267 help=('Instead of only allowing access from HTTP clients '
261 'on localhost, allow HTTP clients on other hosts ' 268 'on localhost, allow HTTP clients on other hosts '
262 'to access this server. WARNING: doing so will ' 269 'to access this server. WARNING: doing so will '
263 'allow users on other hosts to modify your ' 270 'allow users on other hosts to modify your '
264 'GM expectations!')) 271 'GM expectations!'))
265 parser.add_argument('--port', type=int, 272 parser.add_argument('--port', type=int,
266 help=('Which TCP port to listen on for HTTP requests; ' 273 help=('Which TCP port to listen on for HTTP requests; '
267 'defaults to %(default)s'), 274 'defaults to %(default)s'),
268 default=DEFAULT_PORT) 275 default=DEFAULT_PORT)
269 args = parser.parse_args() 276 args = parser.parse_args()
270 global _SERVER 277 global _SERVER
271 _SERVER = Server(expectations_dir=args.expectations_dir, 278 _SERVER = Server(expectations_dir=args.expectations_dir,
272 port=args.port, export=args.export) 279 port=args.port, export=args.export)
273 _SERVER.run() 280 _SERVER.run()
274 281
275 if __name__ == '__main__': 282 if __name__ == '__main__':
276 main() 283 main()
OLDNEW
« no previous file with comments | « gm/rebaseline_server/results.py ('k') | gm/rebaseline_server/static/loader.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698