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 25 matching lines...) Expand all Loading... | |
| 36 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) | 36 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) |
| 37 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools') | 37 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools') |
| 38 if TOOLS_DIRECTORY not in sys.path: | 38 if TOOLS_DIRECTORY not in sys.path: |
| 39 sys.path.append(TOOLS_DIRECTORY) | 39 sys.path.append(TOOLS_DIRECTORY) |
| 40 import svn | 40 import svn |
| 41 | 41 |
| 42 # Imports from local dir | 42 # Imports from local dir |
| 43 import imagepairset | 43 import imagepairset |
| 44 import results | 44 import results |
| 45 | 45 |
| 46 ACTUALS_SVN_REPO = 'http://skia-autogen.googlecode.com/svn/gm-actual' | |
| 47 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') | 46 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') |
| 48 EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm') | 47 EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm') |
| 49 GENERATED_IMAGES_ROOT = os.path.join(PARENT_DIRECTORY, 'static', | 48 GENERATED_IMAGES_ROOT = os.path.join(PARENT_DIRECTORY, 'static', |
| 50 'generated-images') | 49 'generated-images') |
| 51 | 50 |
| 52 # A simple dictionary of file name extensions to MIME types. The empty string | 51 # A simple dictionary of file name extensions to MIME types. The empty string |
| 53 # entry is used as the default when no extension was given or if the extension | 52 # entry is used as the default when no extension was given or if the extension |
| 54 # has no entry in this dictionary. | 53 # has no entry in this dictionary. |
| 55 MIME_TYPE_MAP = {'': 'application/octet-stream', | 54 MIME_TYPE_MAP = {'': 'application/octet-stream', |
| 56 'html': 'text/html', | 55 'html': 'text/html', |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 68 KEY__HEADER = 'header' | 67 KEY__HEADER = 'header' |
| 69 KEY__HEADER__DATAHASH = 'dataHash' | 68 KEY__HEADER__DATAHASH = 'dataHash' |
| 70 KEY__HEADER__IS_EDITABLE = 'isEditable' | 69 KEY__HEADER__IS_EDITABLE = 'isEditable' |
| 71 KEY__HEADER__IS_EXPORTED = 'isExported' | 70 KEY__HEADER__IS_EXPORTED = 'isExported' |
| 72 KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading' | 71 KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading' |
| 73 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable' | 72 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable' |
| 74 KEY__HEADER__TIME_UPDATED = 'timeUpdated' | 73 KEY__HEADER__TIME_UPDATED = 'timeUpdated' |
| 75 KEY__HEADER__TYPE = 'type' | 74 KEY__HEADER__TYPE = 'type' |
| 76 | 75 |
| 77 DEFAULT_ACTUALS_DIR = '.gm-actuals' | 76 DEFAULT_ACTUALS_DIR = '.gm-actuals' |
| 77 DEFAULT_ACTUALS_REPO_REVISION = 'HEAD' | |
| 78 DEFAULT_ACTUALS_REPO_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual' | |
| 78 DEFAULT_PORT = 8888 | 79 DEFAULT_PORT = 8888 |
| 79 | 80 |
| 80 # How often (in seconds) clients should reload while waiting for initial | 81 # How often (in seconds) clients should reload while waiting for initial |
| 81 # results to load. | 82 # results to load. |
| 82 RELOAD_INTERVAL_UNTIL_READY = 10 | 83 RELOAD_INTERVAL_UNTIL_READY = 10 |
| 83 | 84 |
| 84 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length' | 85 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length' |
| 85 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type' | 86 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type' |
| 86 | 87 |
| 87 _SERVER = None # This gets filled in by main() | 88 _SERVER = None # This gets filled in by main() |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 136 os.makedirs(dir_path) | 137 os.makedirs(dir_path) |
| 137 local_checkout.Checkout(repo_url, '.') | 138 local_checkout.Checkout(repo_url, '.') |
| 138 return local_checkout | 139 return local_checkout |
| 139 | 140 |
| 140 | 141 |
| 141 class Server(object): | 142 class Server(object): |
| 142 """ HTTP server for our HTML rebaseline viewer. """ | 143 """ HTTP server for our HTML rebaseline viewer. """ |
| 143 | 144 |
| 144 def __init__(self, | 145 def __init__(self, |
| 145 actuals_dir=DEFAULT_ACTUALS_DIR, | 146 actuals_dir=DEFAULT_ACTUALS_DIR, |
| 147 actuals_repo_revision=DEFAULT_ACTUALS_REPO_REVISION, | |
| 148 actuals_repo_url=DEFAULT_ACTUALS_REPO_URL, | |
| 146 port=DEFAULT_PORT, export=False, editable=True, | 149 port=DEFAULT_PORT, export=False, editable=True, |
| 147 reload_seconds=0): | 150 reload_seconds=0): |
| 148 """ | 151 """ |
| 149 Args: | 152 Args: |
| 150 actuals_dir: directory under which we will check out the latest actual | 153 actuals_dir: directory under which we will check out the latest actual |
| 151 GM results | 154 GM results |
| 155 actuals_repo_revision: revision of actual-results.json files to process | |
| 156 actuals_repo_url: SVN repo to download actual-results.json files from | |
| 152 port: which TCP port to listen on for HTTP requests | 157 port: which TCP port to listen on for HTTP requests |
| 153 export: whether to allow HTTP clients on other hosts to access this server | 158 export: whether to allow HTTP clients on other hosts to access this server |
| 154 editable: whether HTTP clients are allowed to submit new baselines | 159 editable: whether HTTP clients are allowed to submit new baselines |
| 155 reload_seconds: polling interval with which to check for new results; | 160 reload_seconds: polling interval with which to check for new results; |
| 156 if 0, don't check for new results at all | 161 if 0, don't check for new results at all |
| 157 """ | 162 """ |
| 158 self._actuals_dir = actuals_dir | 163 self._actuals_dir = actuals_dir |
| 164 self._actuals_repo_revision = actuals_repo_revision | |
| 165 self._actuals_repo_url = actuals_repo_url | |
| 159 self._port = port | 166 self._port = port |
| 160 self._export = export | 167 self._export = export |
| 161 self._editable = editable | 168 self._editable = editable |
| 162 self._reload_seconds = reload_seconds | 169 self._reload_seconds = reload_seconds |
| 163 self._actuals_repo = _create_svn_checkout( | 170 self._actuals_repo = _create_svn_checkout( |
| 164 dir_path=actuals_dir, repo_url=ACTUALS_SVN_REPO) | 171 dir_path=actuals_dir, repo_url=actuals_repo_url) |
| 165 | 172 |
| 166 # Reentrant lock that must be held whenever updating EITHER of: | 173 # Reentrant lock that must be held whenever updating EITHER of: |
| 167 # 1. self._results | 174 # 1. self._results |
| 168 # 2. the expected or actual results on local disk | 175 # 2. the expected or actual results on local disk |
| 169 self.results_rlock = threading.RLock() | 176 self.results_rlock = threading.RLock() |
| 170 # self._results will be filled in by calls to update_results() | 177 # self._results will be filled in by calls to update_results() |
| 171 self._results = None | 178 self._results = None |
| 172 | 179 |
| 173 @property | 180 @property |
| 174 def results(self): | 181 def results(self): |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 202 the same time. | 209 the same time. |
| 203 | 210 |
| 204 Args: | 211 Args: |
| 205 invalidate: if True, invalidate self._results immediately upon entry; | 212 invalidate: if True, invalidate self._results immediately upon entry; |
| 206 otherwise, we will let readers see those results until we | 213 otherwise, we will let readers see those results until we |
| 207 replace them | 214 replace them |
| 208 """ | 215 """ |
| 209 with self.results_rlock: | 216 with self.results_rlock: |
| 210 if invalidate: | 217 if invalidate: |
| 211 self._results = None | 218 self._results = None |
| 212 logging.info('Updating actual GM results in %s from SVN repo %s ...' % ( | 219 logging.info( |
| 213 self._actuals_dir, ACTUALS_SVN_REPO)) | 220 'Updating actual GM results in %s to revision %s from repo %s ...' % ( |
| 214 self._actuals_repo.Update('.') | 221 self._actuals_dir, self._actuals_repo_revision, |
| 222 self._actuals_repo_url)) | |
| 223 self._actuals_repo.Update(path='.', revision=self._actuals_repo_revision) | |
| 215 | 224 |
| 216 # We only update the expectations dir if the server was run with a | 225 # We only update the expectations dir if the server was run with a |
| 217 # nonzero --reload argument; otherwise, we expect the user to maintain | 226 # nonzero --reload argument; otherwise, we expect the user to maintain |
| 218 # her own expectations as she sees fit. | 227 # her own expectations as she sees fit. |
| 219 # | 228 # |
| 220 # Because the Skia repo is moving from SVN to git, and git does not | 229 # Because the Skia repo is moving from SVN to git, and git does not |
| 221 # support updating a single directory tree, we have to update the entire | 230 # support updating a single directory tree, we have to update the entire |
| 222 # repo checkout. | 231 # repo checkout. |
| 223 # | 232 # |
| 224 # Because Skia uses depot_tools, we have to update using "gclient sync" | 233 # Because Skia uses depot_tools, we have to update using "gclient sync" |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 324 # the handler's .server instance variable. | 333 # the handler's .server instance variable. |
| 325 results_obj = _SERVER.results | 334 results_obj = _SERVER.results |
| 326 if results_obj: | 335 if results_obj: |
| 327 response_dict = self.package_results(results_obj, type) | 336 response_dict = self.package_results(results_obj, type) |
| 328 else: | 337 else: |
| 329 now = int(time.time()) | 338 now = int(time.time()) |
| 330 response_dict = { | 339 response_dict = { |
| 331 KEY__HEADER: { | 340 KEY__HEADER: { |
| 332 KEY__HEADER__IS_STILL_LOADING: True, | 341 KEY__HEADER__IS_STILL_LOADING: True, |
| 333 KEY__HEADER__TIME_UPDATED: now, | 342 KEY__HEADER__TIME_UPDATED: now, |
| 334 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: | 343 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: ( |
| 335 now + RELOAD_INTERVAL_UNTIL_READY, | 344 now + RELOAD_INTERVAL_UNTIL_READY), |
| 336 }, | 345 }, |
| 337 } | 346 } |
| 338 self.send_json_dict(response_dict) | 347 self.send_json_dict(response_dict) |
| 339 | 348 |
| 340 def package_results(self, results_obj, type): | 349 def package_results(self, results_obj, type): |
| 341 """ Given a nonempty "results" object, package it as a response_dict | 350 """ Given a nonempty "results" object, package it as a response_dict |
| 342 as needed within do_GET_results. | 351 as needed within do_GET_results. |
| 343 | 352 |
| 344 Args: | 353 Args: |
| 345 results_obj: nonempty "results" object | 354 results_obj: nonempty "results" object |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 525 def main(): | 534 def main(): |
| 526 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', | 535 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', |
| 527 datefmt='%m/%d/%Y %H:%M:%S', | 536 datefmt='%m/%d/%Y %H:%M:%S', |
| 528 level=logging.INFO) | 537 level=logging.INFO) |
| 529 parser = argparse.ArgumentParser() | 538 parser = argparse.ArgumentParser() |
| 530 parser.add_argument('--actuals-dir', | 539 parser.add_argument('--actuals-dir', |
| 531 help=('Directory into which we will check out the latest ' | 540 help=('Directory into which we will check out the latest ' |
| 532 'actual GM results. If this directory does not ' | 541 'actual GM results. If this directory does not ' |
| 533 'exist, it will be created. Defaults to %(default)s'), | 542 'exist, it will be created. Defaults to %(default)s'), |
| 534 default=DEFAULT_ACTUALS_DIR) | 543 default=DEFAULT_ACTUALS_DIR) |
| 544 parser.add_argument('--actuals-repo', | |
| 545 help=('URL of SVN repo to download actual-results.json ' | |
| 546 'files from. Defaults to %(default)s'), | |
| 547 default=DEFAULT_ACTUALS_REPO_URL) | |
| 548 parser.add_argument('--actuals-revision', | |
| 549 help=('revision of actual-results.json files to process. ' | |
| 550 'Defaults to %(default)s . Beware of setting this ' | |
|
epoger
2014/03/10 18:08:54
I *think* that will work, but as noted in the warn
| |
| 551 'argument in conjunction with --editable; you ' | |
| 552 'probably only want to edit results at HEAD.'), | |
| 553 default=DEFAULT_ACTUALS_REPO_REVISION) | |
| 535 parser.add_argument('--editable', action='store_true', | 554 parser.add_argument('--editable', action='store_true', |
| 536 help=('Allow HTTP clients to submit new baselines.')) | 555 help=('Allow HTTP clients to submit new baselines.')) |
| 537 parser.add_argument('--export', action='store_true', | 556 parser.add_argument('--export', action='store_true', |
| 538 help=('Instead of only allowing access from HTTP clients ' | 557 help=('Instead of only allowing access from HTTP clients ' |
| 539 'on localhost, allow HTTP clients on other hosts ' | 558 'on localhost, allow HTTP clients on other hosts ' |
| 540 'to access this server. WARNING: doing so will ' | 559 'to access this server. WARNING: doing so will ' |
| 541 'allow users on other hosts to modify your ' | 560 'allow users on other hosts to modify your ' |
| 542 'GM expectations, if combined with --editable.')) | 561 'GM expectations, if combined with --editable.')) |
| 543 parser.add_argument('--port', type=int, | 562 parser.add_argument('--port', type=int, |
| 544 help=('Which TCP port to listen on for HTTP requests; ' | 563 help=('Which TCP port to listen on for HTTP requests; ' |
| 545 'defaults to %(default)s'), | 564 'defaults to %(default)s'), |
| 546 default=DEFAULT_PORT) | 565 default=DEFAULT_PORT) |
| 547 parser.add_argument('--reload', type=int, | 566 parser.add_argument('--reload', type=int, |
| 548 help=('How often (a period in seconds) to update the ' | 567 help=('How often (a period in seconds) to update the ' |
| 549 'results. If specified, both expected and actual ' | 568 'results. If specified, both expected and actual ' |
| 550 'results will be updated by running "gclient sync" ' | 569 'results will be updated by running "gclient sync" ' |
| 551 'on your Skia checkout as a whole. ' | 570 'on your Skia checkout as a whole. ' |
| 552 'By default, we do not reload at all, and you ' | 571 'By default, we do not reload at all, and you ' |
| 553 'must restart the server to pick up new data.'), | 572 'must restart the server to pick up new data.'), |
| 554 default=0) | 573 default=0) |
| 555 args = parser.parse_args() | 574 args = parser.parse_args() |
| 556 global _SERVER | 575 global _SERVER |
| 557 _SERVER = Server(actuals_dir=args.actuals_dir, | 576 _SERVER = Server(actuals_dir=args.actuals_dir, |
| 577 actuals_repo_revision=args.actuals_revision, | |
| 578 actuals_repo_url=args.actuals_repo, | |
| 558 port=args.port, export=args.export, editable=args.editable, | 579 port=args.port, export=args.export, editable=args.editable, |
| 559 reload_seconds=args.reload) | 580 reload_seconds=args.reload) |
| 560 _SERVER.run() | 581 _SERVER.run() |
| 561 | 582 |
| 562 | 583 |
| 563 if __name__ == '__main__': | 584 if __name__ == '__main__': |
| 564 main() | 585 main() |
| OLD | NEW |