Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 """ |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 21 import socket | 21 import socket |
| 22 import subprocess | 22 import subprocess |
| 23 import sys | 23 import sys |
| 24 import thread | 24 import thread |
| 25 import threading | 25 import threading |
| 26 import time | 26 import time |
| 27 import urlparse | 27 import urlparse |
| 28 | 28 |
| 29 # Imports from within Skia | 29 # Imports from within Skia |
| 30 # | 30 # |
| 31 # We need to add the 'tools' directory, so that we can import svn.py within | 31 # We need to add the 'tools' directory for svn.py, and the 'gm' directory for |
| 32 # gm_json.py . | |
| 32 # that directory. | 33 # that directory. |
| 33 # Make sure that the 'tools' dir is in the PYTHONPATH, but add it at the *end* | 34 # Make sure that the 'tools' dir is in the PYTHONPATH, but add it at the *end* |
| 34 # so any dirs that are already in the PYTHONPATH will be preferred. | 35 # so any dirs that are already in the PYTHONPATH will be preferred. |
| 35 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) | 36 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) |
| 36 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) | 37 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY) |
| 38 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY) | |
| 37 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools') | 39 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools') |
| 38 if TOOLS_DIRECTORY not in sys.path: | 40 if TOOLS_DIRECTORY not in sys.path: |
| 39 sys.path.append(TOOLS_DIRECTORY) | 41 sys.path.append(TOOLS_DIRECTORY) |
| 40 import svn | 42 import svn |
| 43 if GM_DIRECTORY not in sys.path: | |
| 44 sys.path.append(GM_DIRECTORY) | |
| 45 import gm_json | |
| 41 | 46 |
| 42 # Imports from local dir | 47 # Imports from local dir |
| 43 # | 48 # |
| 44 # Note: we import results under a different name, to avoid confusion with the | 49 # Note: we import results under a different name, to avoid confusion with the |
| 45 # Server.results() property. See discussion at | 50 # Server.results() property. See discussion at |
| 46 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p y#newcode44 | 51 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p y#newcode44 |
| 52 import compare_configs | |
| 47 import compare_to_expectations | 53 import compare_to_expectations |
| 48 import imagepairset | 54 import imagepairset |
| 49 import results as results_mod | 55 import results as results_mod |
| 50 | 56 |
| 51 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') | 57 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') |
| 52 | 58 |
| 53 # A simple dictionary of file name extensions to MIME types. The empty string | 59 # A simple dictionary of file name extensions to MIME types. The empty string |
| 54 # entry is used as the default when no extension was given or if the extension | 60 # entry is used as the default when no extension was given or if the extension |
| 55 # has no entry in this dictionary. | 61 # has no entry in this dictionary. |
| 56 MIME_TYPE_MAP = {'': 'application/octet-stream', | 62 MIME_TYPE_MAP = {'': 'application/octet-stream', |
| 57 'html': 'text/html', | 63 'html': 'text/html', |
| 58 'css': 'text/css', | 64 'css': 'text/css', |
| 59 'png': 'image/png', | 65 'png': 'image/png', |
| 60 'js': 'application/javascript', | 66 'js': 'application/javascript', |
| 61 'json': 'application/json' | 67 'json': 'application/json' |
| 62 } | 68 } |
| 63 | 69 |
| 64 # Keys that server.py uses to create the toplevel content header. | 70 # Keys that server.py uses to create the toplevel content header. |
| 65 # NOTE: Keep these in sync with static/constants.js | 71 # NOTE: Keep these in sync with static/constants.js |
| 66 KEY__EDITS__MODIFICATIONS = 'modifications' | 72 KEY__EDITS__MODIFICATIONS = 'modifications' |
| 67 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' | 73 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' |
| 68 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' | 74 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' |
| 69 | 75 |
| 70 DEFAULT_ACTUALS_DIR = compare_to_expectations.DEFAULT_ACTUALS_DIR | 76 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR |
| 71 DEFAULT_ACTUALS_REPO_REVISION = 'HEAD' | 77 DEFAULT_ACTUALS_REPO_REVISION = 'HEAD' |
| 72 DEFAULT_ACTUALS_REPO_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual' | 78 DEFAULT_ACTUALS_REPO_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual' |
| 73 DEFAULT_PORT = 8888 | 79 DEFAULT_PORT = 8888 |
| 74 | 80 |
| 75 # Directory within which the server will serve out static files. | 81 # Directory, relative to PARENT_DIRECTORY, within which the server will serve |
| 76 STATIC_CONTENTS_SUBDIR = 'static' # within PARENT_DIR | 82 # out live results (not static files). |
| 77 GENERATED_IMAGES_SUBDIR = 'generated-images' # within STATIC_CONTENTS_SUBDIR | 83 RESULTS_SUBDIR = 'results' |
| 84 # Directory, relative to PARENT_DIRECTORY, within which the server will serve | |
| 85 # out static files. | |
| 86 STATIC_CONTENTS_SUBDIR = 'static' | |
| 87 # All of the GENERATED_*_SUBDIRS are relative to STATIC_CONTENTS_SUBDIR | |
| 88 GENERATED_HTML_SUBDIR = 'generated-html' | |
| 89 GENERATED_IMAGES_SUBDIR = 'generated-images' | |
| 90 GENERATED_JSON_SUBDIR = 'generated-json' | |
| 78 | 91 |
| 79 # How often (in seconds) clients should reload while waiting for initial | 92 # How often (in seconds) clients should reload while waiting for initial |
| 80 # results to load. | 93 # results to load. |
| 81 RELOAD_INTERVAL_UNTIL_READY = 10 | 94 RELOAD_INTERVAL_UNTIL_READY = 10 |
| 82 | 95 |
| 96 SUMMARY_TYPES = [ | |
| 97 results_mod.KEY__HEADER__RESULTS_FAILURES, | |
| 98 results_mod.KEY__HEADER__RESULTS_ALL, | |
| 99 ] | |
| 100 # If --compare-configs is specified, compare these configs. | |
| 101 CONFIG_PAIRS_TO_COMPARE = [('8888', 'gpu')] | |
|
epoger
2014/03/27 21:31:07
Someday the pairs will be configurable from the co
rmistry
2014/03/28 18:51:39
Acknowledged.
| |
| 102 | |
| 83 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length' | 103 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length' |
| 84 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type' | 104 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type' |
| 85 | 105 |
| 86 _SERVER = None # This gets filled in by main() | 106 _SERVER = None # This gets filled in by main() |
| 87 | 107 |
| 88 | 108 |
| 89 def _run_command(args, directory): | 109 def _run_command(args, directory): |
| 90 """Runs a command and returns stdout as a single string. | 110 """Runs a command and returns stdout as a single string. |
| 91 | 111 |
| 92 Args: | 112 Args: |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 130 checkout already exists) | 150 checkout already exists) |
| 131 Returns: an svn.Svn object referring to the local checkout. | 151 Returns: an svn.Svn object referring to the local checkout. |
| 132 """ | 152 """ |
| 133 local_checkout = svn.Svn(dir_path) | 153 local_checkout = svn.Svn(dir_path) |
| 134 if not os.path.isdir(dir_path): | 154 if not os.path.isdir(dir_path): |
| 135 os.makedirs(dir_path) | 155 os.makedirs(dir_path) |
| 136 local_checkout.Checkout(repo_url, '.') | 156 local_checkout.Checkout(repo_url, '.') |
| 137 return local_checkout | 157 return local_checkout |
| 138 | 158 |
| 139 | 159 |
| 160 def _create_index(file_path, config_pairs): | |
| 161 """Creates an index file linking to all results available from this server. | |
|
epoger
2014/03/27 21:31:07
I used to have an index.html file checked in (you'
rmistry
2014/03/28 18:51:39
Acknowledged.
| |
| 162 | |
| 163 Args: | |
| 164 file_path: path on local disk to write index to; any directory components | |
| 165 of this path that do not already exist will be created | |
| 166 config_pairs: what pairs of configs (if any) we compare actual results of | |
| 167 """ | |
| 168 dir_path = os.path.dirname(file_path) | |
| 169 if not os.path.isdir(dir_path): | |
| 170 os.makedirs(dir_path) | |
| 171 with open(file_path, 'w') as file_handle: | |
| 172 file_handle.write( | |
| 173 '<!DOCTYPE html><html>' | |
| 174 '<head><title>rebaseline_server</title></head>' | |
| 175 '<body><ul>') | |
| 176 if SUMMARY_TYPES: | |
| 177 file_handle.write('<li>Expectations vs Actuals</li><ul>') | |
| 178 for summary_type in SUMMARY_TYPES: | |
| 179 file_handle.write( | |
| 180 '<li>' | |
| 181 '<a href="/%s/view.html#/view.html?resultsToLoad=/%s/%s">' | |
| 182 '%s</a></li>' % ( | |
| 183 STATIC_CONTENTS_SUBDIR, RESULTS_SUBDIR, | |
| 184 summary_type, summary_type)) | |
| 185 file_handle.write('</ul>') | |
| 186 if config_pairs: | |
| 187 file_handle.write('<li>Comparing configs within actual results</li><ul>') | |
| 188 for config_pair in config_pairs: | |
| 189 file_handle.write('<li>%s vs %s:' % config_pair) | |
| 190 for summary_type in SUMMARY_TYPES: | |
| 191 file_handle.write( | |
| 192 ' <a href="/%s/view.html#/view.html?' | |
| 193 'resultsToLoad=/%s/%s/%s-vs-%s_%s.json">%s</a>' % ( | |
| 194 STATIC_CONTENTS_SUBDIR, STATIC_CONTENTS_SUBDIR, | |
| 195 GENERATED_JSON_SUBDIR, config_pair[0], config_pair[1], | |
| 196 summary_type, summary_type)) | |
|
rmistry
2014/03/28 18:51:39
Instead of writing raw HTML what do you think abou
epoger
2014/03/28 19:25:55
Created a TODO.
| |
| 197 file_handle.write('</li>') | |
| 198 file_handle.write('</ul>') | |
| 199 file_handle.write('</ul></body></html>') | |
| 200 | |
| 201 | |
| 140 class Server(object): | 202 class Server(object): |
| 141 """ HTTP server for our HTML rebaseline viewer. """ | 203 """ HTTP server for our HTML rebaseline viewer. """ |
| 142 | 204 |
| 143 def __init__(self, | 205 def __init__(self, |
| 144 actuals_dir=DEFAULT_ACTUALS_DIR, | 206 actuals_dir=DEFAULT_ACTUALS_DIR, |
| 145 actuals_repo_revision=DEFAULT_ACTUALS_REPO_REVISION, | 207 actuals_repo_revision=DEFAULT_ACTUALS_REPO_REVISION, |
| 146 actuals_repo_url=DEFAULT_ACTUALS_REPO_URL, | 208 actuals_repo_url=DEFAULT_ACTUALS_REPO_URL, |
| 147 port=DEFAULT_PORT, export=False, editable=True, | 209 port=DEFAULT_PORT, export=False, editable=True, |
| 148 reload_seconds=0): | 210 reload_seconds=0, config_pairs=None): |
| 149 """ | 211 """ |
| 150 Args: | 212 Args: |
| 151 actuals_dir: directory under which we will check out the latest actual | 213 actuals_dir: directory under which we will check out the latest actual |
| 152 GM results | 214 GM results |
| 153 actuals_repo_revision: revision of actual-results.json files to process | 215 actuals_repo_revision: revision of actual-results.json files to process |
| 154 actuals_repo_url: SVN repo to download actual-results.json files from | 216 actuals_repo_url: SVN repo to download actual-results.json files from |
| 155 port: which TCP port to listen on for HTTP requests | 217 port: which TCP port to listen on for HTTP requests |
| 156 export: whether to allow HTTP clients on other hosts to access this server | 218 export: whether to allow HTTP clients on other hosts to access this server |
| 157 editable: whether HTTP clients are allowed to submit new baselines | 219 editable: whether HTTP clients are allowed to submit new baselines |
| 158 reload_seconds: polling interval with which to check for new results; | 220 reload_seconds: polling interval with which to check for new results; |
| 159 if 0, don't check for new results at all | 221 if 0, don't check for new results at all |
| 222 config_pairs: List of (string, string) tuples; for each tuple, compare | |
| 223 actual results of these two configs. If None or empty, | |
| 224 don't compare configs at all. | |
| 160 """ | 225 """ |
| 161 self._actuals_dir = actuals_dir | 226 self._actuals_dir = actuals_dir |
| 162 self._actuals_repo_revision = actuals_repo_revision | 227 self._actuals_repo_revision = actuals_repo_revision |
| 163 self._actuals_repo_url = actuals_repo_url | 228 self._actuals_repo_url = actuals_repo_url |
| 164 self._port = port | 229 self._port = port |
| 165 self._export = export | 230 self._export = export |
| 166 self._editable = editable | 231 self._editable = editable |
| 167 self._reload_seconds = reload_seconds | 232 self._reload_seconds = reload_seconds |
| 233 self._config_pairs = config_pairs or [] | |
| 234 | |
| 235 _create_index( | |
| 236 file_path=os.path.join( | |
| 237 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_HTML_SUBDIR, | |
| 238 "index.html"), | |
| 239 config_pairs=config_pairs) | |
| 168 self._actuals_repo = _create_svn_checkout( | 240 self._actuals_repo = _create_svn_checkout( |
| 169 dir_path=actuals_dir, repo_url=actuals_repo_url) | 241 dir_path=actuals_dir, repo_url=actuals_repo_url) |
| 170 | 242 |
| 171 # Reentrant lock that must be held whenever updating EITHER of: | 243 # Reentrant lock that must be held whenever updating EITHER of: |
| 172 # 1. self._results | 244 # 1. self._results |
| 173 # 2. the expected or actual results on local disk | 245 # 2. the expected or actual results on local disk |
| 174 self.results_rlock = threading.RLock() | 246 self.results_rlock = threading.RLock() |
| 175 # self._results will be filled in by calls to update_results() | 247 # self._results will be filled in by calls to update_results() |
| 176 self._results = None | 248 self._results = None |
| 177 | 249 |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 230 # | 302 # |
| 231 # Because Skia uses depot_tools, we have to update using "gclient sync" | 303 # Because Skia uses depot_tools, we have to update using "gclient sync" |
| 232 # instead of raw git (or SVN) update. Happily, this will work whether | 304 # instead of raw git (or SVN) update. Happily, this will work whether |
| 233 # the checkout was created using git or SVN. | 305 # the checkout was created using git or SVN. |
| 234 if self._reload_seconds: | 306 if self._reload_seconds: |
| 235 logging.info( | 307 logging.info( |
| 236 'Updating expected GM results in %s by syncing Skia repo ...' % | 308 'Updating expected GM results in %s by syncing Skia repo ...' % |
| 237 compare_to_expectations.DEFAULT_EXPECTATIONS_DIR) | 309 compare_to_expectations.DEFAULT_EXPECTATIONS_DIR) |
| 238 _run_command(['gclient', 'sync'], TRUNK_DIRECTORY) | 310 _run_command(['gclient', 'sync'], TRUNK_DIRECTORY) |
| 239 | 311 |
| 240 self._results = compare_to_expectations.Results( | 312 self._results = compare_to_expectations.ExpectationComparisons( |
| 241 actuals_root=self._actuals_dir, | 313 actuals_root=self._actuals_dir, |
| 242 generated_images_root=os.path.join( | 314 generated_images_root=os.path.join( |
| 243 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, | 315 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, |
| 244 GENERATED_IMAGES_SUBDIR), | 316 GENERATED_IMAGES_SUBDIR), |
| 245 diff_base_url=posixpath.join( | 317 diff_base_url=posixpath.join( |
| 246 os.pardir, STATIC_CONTENTS_SUBDIR, GENERATED_IMAGES_SUBDIR)) | 318 os.pardir, STATIC_CONTENTS_SUBDIR, GENERATED_IMAGES_SUBDIR)) |
| 247 | 319 |
| 320 json_dir = os.path.join( | |
| 321 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_JSON_SUBDIR) | |
| 322 if not os.path.isdir(json_dir): | |
| 323 os.makedirs(json_dir) | |
| 324 | |
| 325 for config_pair in self._config_pairs: | |
| 326 config_comparisons = compare_configs.ConfigComparisons( | |
| 327 configs=config_pair, | |
| 328 actuals_root=self._actuals_dir, | |
| 329 generated_images_root=os.path.join( | |
| 330 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, | |
| 331 GENERATED_IMAGES_SUBDIR), | |
| 332 diff_base_url=posixpath.join( | |
| 333 os.pardir, GENERATED_IMAGES_SUBDIR)) | |
| 334 for summary_type in SUMMARY_TYPES: | |
| 335 gm_json.WriteToFile( | |
| 336 config_comparisons.get_packaged_results_of_type( | |
| 337 results_type=summary_type), | |
| 338 os.path.join( | |
| 339 json_dir, '%s-vs-%s_%s.json' % ( | |
| 340 config_pair[0], config_pair[1], summary_type))) | |
| 341 | |
| 248 def _result_loader(self, reload_seconds=0): | 342 def _result_loader(self, reload_seconds=0): |
| 249 """ Call self.update_results(), either once or periodically. | 343 """ Call self.update_results(), either once or periodically. |
| 250 | 344 |
| 251 Params: | 345 Params: |
| 252 reload_seconds: integer; if nonzero, reload results at this interval | 346 reload_seconds: integer; if nonzero, reload results at this interval |
| 253 (in which case, this method will never return!) | 347 (in which case, this method will never return!) |
| 254 """ | 348 """ |
| 255 self.update_results() | 349 self.update_results() |
| 256 logging.info('Initial results loaded. Ready for requests on %s' % self._url) | 350 logging.info('Initial results loaded. Ready for requests on %s' % self._url) |
| 257 if reload_seconds: | 351 if reload_seconds: |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 287 def do_GET(self): | 381 def do_GET(self): |
| 288 """ | 382 """ |
| 289 Handles all GET requests, forwarding them to the appropriate | 383 Handles all GET requests, forwarding them to the appropriate |
| 290 do_GET_* dispatcher. | 384 do_GET_* dispatcher. |
| 291 | 385 |
| 292 If we see any Exceptions, return a 404. This fixes http://skbug.com/2147 | 386 If we see any Exceptions, return a 404. This fixes http://skbug.com/2147 |
| 293 """ | 387 """ |
| 294 try: | 388 try: |
| 295 logging.debug('do_GET: path="%s"' % self.path) | 389 logging.debug('do_GET: path="%s"' % self.path) |
| 296 if self.path == '' or self.path == '/' or self.path == '/index.html' : | 390 if self.path == '' or self.path == '/' or self.path == '/index.html' : |
| 297 self.redirect_to('/%s/index.html' % STATIC_CONTENTS_SUBDIR) | 391 self.redirect_to('/%s/%s/index.html' % ( |
| 392 STATIC_CONTENTS_SUBDIR, GENERATED_HTML_SUBDIR)) | |
| 298 return | 393 return |
| 299 if self.path == '/favicon.ico' : | 394 if self.path == '/favicon.ico' : |
| 300 self.redirect_to('/%s/favicon.ico' % STATIC_CONTENTS_SUBDIR) | 395 self.redirect_to('/%s/favicon.ico' % STATIC_CONTENTS_SUBDIR) |
| 301 return | 396 return |
| 302 | 397 |
| 303 # All requests must be of this form: | 398 # All requests must be of this form: |
| 304 # /dispatcher/remainder | 399 # /dispatcher/remainder |
| 305 # where 'dispatcher' indicates which do_GET_* dispatcher to run | 400 # where 'dispatcher' indicates which do_GET_* dispatcher to run |
| 306 # and 'remainder' is the remaining path sent to that dispatcher. | 401 # and 'remainder' is the remaining path sent to that dispatcher. |
| 307 normpath = posixpath.normpath(self.path) | 402 normpath = posixpath.normpath(self.path) |
| 308 (dispatcher_name, remainder) = PATHSPLIT_RE.match(normpath).groups() | 403 (dispatcher_name, remainder) = PATHSPLIT_RE.match(normpath).groups() |
| 309 dispatchers = { | 404 dispatchers = { |
| 310 'results': self.do_GET_results, | 405 RESULTS_SUBDIR: self.do_GET_results, |
| 311 STATIC_CONTENTS_SUBDIR: self.do_GET_static, | 406 STATIC_CONTENTS_SUBDIR: self.do_GET_static, |
| 312 } | 407 } |
| 313 dispatcher = dispatchers[dispatcher_name] | 408 dispatcher = dispatchers[dispatcher_name] |
| 314 dispatcher(remainder) | 409 dispatcher(remainder) |
| 315 except: | 410 except: |
| 316 self.send_error(404) | 411 self.send_error(404) |
| 317 raise | 412 raise |
| 318 | 413 |
| 319 def do_GET_results(self, results_type): | 414 def do_GET_results(self, results_type): |
| 320 """ Handle a GET request for GM results. | 415 """ Handle a GET request for GM results. |
| 321 | 416 |
| 322 Args: | 417 Args: |
| 323 results_type: string indicating which set of results to return; | 418 results_type: string indicating which set of results to return; |
| 324 must be one of the results_mod.RESULTS_* constants | 419 must be one of the results_mod.RESULTS_* constants |
| 325 """ | 420 """ |
| 326 logging.debug('do_GET_results: sending results of type "%s"' % results_type) | 421 logging.debug('do_GET_results: sending results of type "%s"' % results_type) |
| 327 # Since we must make multiple calls to the Results object, grab a | 422 # Since we must make multiple calls to the ExpectationComparisons object, |
| 328 # reference to it in case it is updated to point at a new Results | 423 # grab a reference to it in case it is updated to point at a new |
| 329 # object within another thread. | 424 # ExpectationComparisons object within another thread. |
| 330 # | 425 # |
| 331 # TODO(epoger): Rather than using a global variable for the handler | 426 # TODO(epoger): Rather than using a global variable for the handler |
| 332 # to refer to the Server object, make Server a subclass of | 427 # to refer to the Server object, make Server a subclass of |
| 333 # HTTPServer, and then it could be available to the handler via | 428 # HTTPServer, and then it could be available to the handler via |
| 334 # the handler's .server instance variable. | 429 # the handler's .server instance variable. |
| 335 results_obj = _SERVER.results | 430 results_obj = _SERVER.results |
| 336 if results_obj: | 431 if results_obj: |
| 337 response_dict = results_obj.get_packaged_results_of_type( | 432 response_dict = results_obj.get_packaged_results_of_type( |
| 338 results_type=results_type, reload_seconds=_SERVER.reload_seconds, | 433 results_type=results_type, reload_seconds=_SERVER.reload_seconds, |
| 339 is_editable=_SERVER.is_editable, is_exported=_SERVER.is_exported) | 434 is_editable=_SERVER.is_editable, is_exported=_SERVER.is_exported) |
| (...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 509 parser.add_argument('--actuals-repo', | 604 parser.add_argument('--actuals-repo', |
| 510 help=('URL of SVN repo to download actual-results.json ' | 605 help=('URL of SVN repo to download actual-results.json ' |
| 511 'files from. Defaults to %(default)s'), | 606 'files from. Defaults to %(default)s'), |
| 512 default=DEFAULT_ACTUALS_REPO_URL) | 607 default=DEFAULT_ACTUALS_REPO_URL) |
| 513 parser.add_argument('--actuals-revision', | 608 parser.add_argument('--actuals-revision', |
| 514 help=('revision of actual-results.json files to process. ' | 609 help=('revision of actual-results.json files to process. ' |
| 515 'Defaults to %(default)s . Beware of setting this ' | 610 'Defaults to %(default)s . Beware of setting this ' |
| 516 'argument in conjunction with --editable; you ' | 611 'argument in conjunction with --editable; you ' |
| 517 'probably only want to edit results at HEAD.'), | 612 'probably only want to edit results at HEAD.'), |
| 518 default=DEFAULT_ACTUALS_REPO_REVISION) | 613 default=DEFAULT_ACTUALS_REPO_REVISION) |
| 614 parser.add_argument('--compare-configs', action='store_true', | |
| 615 help=('In addition to generating differences between ' | |
| 616 'expectations and actuals, also generate ' | |
| 617 'differences between these config pairs: ' | |
| 618 + str(CONFIG_PAIRS_TO_COMPARE))) | |
| 519 parser.add_argument('--editable', action='store_true', | 619 parser.add_argument('--editable', action='store_true', |
| 520 help=('Allow HTTP clients to submit new baselines.')) | 620 help=('Allow HTTP clients to submit new baselines.')) |
| 521 parser.add_argument('--export', action='store_true', | 621 parser.add_argument('--export', action='store_true', |
| 522 help=('Instead of only allowing access from HTTP clients ' | 622 help=('Instead of only allowing access from HTTP clients ' |
| 523 'on localhost, allow HTTP clients on other hosts ' | 623 'on localhost, allow HTTP clients on other hosts ' |
| 524 'to access this server. WARNING: doing so will ' | 624 'to access this server. WARNING: doing so will ' |
| 525 'allow users on other hosts to modify your ' | 625 'allow users on other hosts to modify your ' |
| 526 'GM expectations, if combined with --editable.')) | 626 'GM expectations, if combined with --editable.')) |
| 527 parser.add_argument('--port', type=int, | 627 parser.add_argument('--port', type=int, |
| 528 help=('Which TCP port to listen on for HTTP requests; ' | 628 help=('Which TCP port to listen on for HTTP requests; ' |
| 529 'defaults to %(default)s'), | 629 'defaults to %(default)s'), |
| 530 default=DEFAULT_PORT) | 630 default=DEFAULT_PORT) |
| 531 parser.add_argument('--reload', type=int, | 631 parser.add_argument('--reload', type=int, |
| 532 help=('How often (a period in seconds) to update the ' | 632 help=('How often (a period in seconds) to update the ' |
| 533 'results. If specified, both expected and actual ' | 633 'results. If specified, both expected and actual ' |
| 534 'results will be updated by running "gclient sync" ' | 634 'results will be updated by running "gclient sync" ' |
| 535 'on your Skia checkout as a whole. ' | 635 'on your Skia checkout as a whole. ' |
| 536 'By default, we do not reload at all, and you ' | 636 'By default, we do not reload at all, and you ' |
| 537 'must restart the server to pick up new data.'), | 637 'must restart the server to pick up new data.'), |
| 538 default=0) | 638 default=0) |
| 539 args = parser.parse_args() | 639 args = parser.parse_args() |
| 640 if args.compare_configs: | |
| 641 config_pairs = CONFIG_PAIRS_TO_COMPARE | |
| 642 else: | |
| 643 config_pairs = None | |
| 644 | |
| 540 global _SERVER | 645 global _SERVER |
| 541 _SERVER = Server(actuals_dir=args.actuals_dir, | 646 _SERVER = Server(actuals_dir=args.actuals_dir, |
| 542 actuals_repo_revision=args.actuals_revision, | 647 actuals_repo_revision=args.actuals_revision, |
| 543 actuals_repo_url=args.actuals_repo, | 648 actuals_repo_url=args.actuals_repo, |
| 544 port=args.port, export=args.export, editable=args.editable, | 649 port=args.port, export=args.export, editable=args.editable, |
| 545 reload_seconds=args.reload) | 650 reload_seconds=args.reload, config_pairs=config_pairs) |
| 546 _SERVER.run() | 651 _SERVER.run() |
| 547 | 652 |
| 548 | 653 |
| 549 if __name__ == '__main__': | 654 if __name__ == '__main__': |
| 550 main() | 655 main() |
| OLD | NEW |