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 |