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 23 matching lines...) Expand all Loading... |
34 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) | 34 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) |
35 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools') | 35 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools') |
36 if TOOLS_DIRECTORY not in sys.path: | 36 if TOOLS_DIRECTORY not in sys.path: |
37 sys.path.append(TOOLS_DIRECTORY) | 37 sys.path.append(TOOLS_DIRECTORY) |
38 import svn | 38 import svn |
39 | 39 |
40 # Imports from local dir | 40 # Imports from local dir |
41 import results | 41 import results |
42 | 42 |
43 ACTUALS_SVN_REPO = 'http://skia-autogen.googlecode.com/svn/gm-actual' | 43 ACTUALS_SVN_REPO = 'http://skia-autogen.googlecode.com/svn/gm-actual' |
44 EXPECTATIONS_SVN_REPO = 'http://skia.googlecode.com/svn/trunk/expectations/gm' | |
45 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') | 44 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') |
46 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(os.path.dirname( | 45 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(os.path.dirname( |
47 os.path.realpath(__file__)))) | 46 os.path.realpath(__file__)))) |
| 47 EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm') |
48 GENERATED_IMAGES_ROOT = os.path.join(PARENT_DIRECTORY, 'static', | 48 GENERATED_IMAGES_ROOT = os.path.join(PARENT_DIRECTORY, 'static', |
49 'generated-images') | 49 'generated-images') |
50 | 50 |
51 # 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 |
52 # 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 |
53 # has no entry in this dictionary. | 53 # has no entry in this dictionary. |
54 MIME_TYPE_MAP = {'': 'application/octet-stream', | 54 MIME_TYPE_MAP = {'': 'application/octet-stream', |
55 'html': 'text/html', | 55 'html': 'text/html', |
56 'css': 'text/css', | 56 'css': 'text/css', |
57 'png': 'image/png', | 57 'png': 'image/png', |
58 'js': 'application/javascript', | 58 'js': 'application/javascript', |
59 'json': 'application/json' | 59 'json': 'application/json' |
60 } | 60 } |
61 | 61 |
62 DEFAULT_ACTUALS_DIR = '.gm-actuals' | 62 DEFAULT_ACTUALS_DIR = '.gm-actuals' |
63 DEFAULT_EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm') | |
64 DEFAULT_PORT = 8888 | 63 DEFAULT_PORT = 8888 |
65 | 64 |
66 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length' | 65 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length' |
67 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type' | 66 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type' |
68 | 67 |
69 _SERVER = None # This gets filled in by main() | 68 _SERVER = None # This gets filled in by main() |
70 | 69 |
71 def _get_routable_ip_address(): | 70 def _get_routable_ip_address(): |
72 """Returns routable IP address of this host (the IP address of its network | 71 """Returns routable IP address of this host (the IP address of its network |
73 interface that would be used for most traffic, not its localhost | 72 interface that would be used for most traffic, not its localhost |
(...skipping 20 matching lines...) Expand all Loading... |
94 os.makedirs(dir_path) | 93 os.makedirs(dir_path) |
95 local_checkout.Checkout(repo_url, '.') | 94 local_checkout.Checkout(repo_url, '.') |
96 return local_checkout | 95 return local_checkout |
97 | 96 |
98 | 97 |
99 class Server(object): | 98 class Server(object): |
100 """ HTTP server for our HTML rebaseline viewer. """ | 99 """ HTTP server for our HTML rebaseline viewer. """ |
101 | 100 |
102 def __init__(self, | 101 def __init__(self, |
103 actuals_dir=DEFAULT_ACTUALS_DIR, | 102 actuals_dir=DEFAULT_ACTUALS_DIR, |
104 expectations_dir=DEFAULT_EXPECTATIONS_DIR, | |
105 port=DEFAULT_PORT, export=False, editable=True, | 103 port=DEFAULT_PORT, export=False, editable=True, |
106 reload_seconds=0): | 104 reload_seconds=0): |
107 """ | 105 """ |
108 Args: | 106 Args: |
109 actuals_dir: directory under which we will check out the latest actual | 107 actuals_dir: directory under which we will check out the latest actual |
110 GM results | 108 GM results |
111 expectations_dir: DEPRECATED: directory under which to find | |
112 GM expectations (they must already be in that directory) | |
113 port: which TCP port to listen on for HTTP requests | 109 port: which TCP port to listen on for HTTP requests |
114 export: whether to allow HTTP clients on other hosts to access this server | 110 export: whether to allow HTTP clients on other hosts to access this server |
115 editable: whether HTTP clients are allowed to submit new baselines | 111 editable: whether HTTP clients are allowed to submit new baselines |
116 reload_seconds: polling interval with which to check for new results; | 112 reload_seconds: polling interval with which to check for new results; |
117 if 0, don't check for new results at all | 113 if 0, don't check for new results at all |
118 """ | 114 """ |
119 self._actuals_dir = actuals_dir | 115 self._actuals_dir = actuals_dir |
120 self._expectations_dir = expectations_dir | |
121 self._port = port | 116 self._port = port |
122 self._export = export | 117 self._export = export |
123 self._editable = editable | 118 self._editable = editable |
124 self._reload_seconds = reload_seconds | 119 self._reload_seconds = reload_seconds |
125 self._actuals_repo = _create_svn_checkout( | 120 self._actuals_repo = _create_svn_checkout( |
126 dir_path=actuals_dir, repo_url=ACTUALS_SVN_REPO) | 121 dir_path=actuals_dir, repo_url=ACTUALS_SVN_REPO) |
127 | 122 |
128 # We only update the expectations dir if the server was run with a | 123 # We only update the expectations dir if the server was run with a |
129 # nonzero --reload argument; otherwise, we expect the user to maintain | 124 # nonzero --reload argument; otherwise, we expect the user to maintain |
130 # her own expectations as she sees fit. | 125 # her own expectations as she sees fit. |
131 # | 126 # |
132 # TODO(epoger): Use git instead of svn to check out expectations, since | 127 # TODO(epoger): Use git instead of svn to update the expectations dir, since |
133 # the Skia repo is moving to git. | 128 # the Skia repo is moving to git. |
| 129 # When we make that change, we will have to update the entire workspace, |
| 130 # not just the expectations dir, because git only operates on the repo |
| 131 # as a whole. |
| 132 # And since Skia uses depot_tools to manage its dependencies, we will have |
| 133 # to run "gclient sync" rather than a raw "git pull". |
134 if reload_seconds: | 134 if reload_seconds: |
135 self._expectations_repo = _create_svn_checkout( | 135 self._expectations_repo = svn.Svn(EXPECTATIONS_DIR) |
136 dir_path=expectations_dir, repo_url=EXPECTATIONS_SVN_REPO) | |
137 else: | 136 else: |
138 self._expectations_repo = None | 137 self._expectations_repo = None |
139 | 138 |
140 def is_exported(self): | 139 def is_exported(self): |
141 """ Returns true iff HTTP clients on other hosts are allowed to access | 140 """ Returns true iff HTTP clients on other hosts are allowed to access |
142 this server. """ | 141 this server. """ |
143 return self._export | 142 return self._export |
144 | 143 |
145 def is_editable(self): | 144 def is_editable(self): |
146 """ Returns true iff HTTP clients are allowed to submit new baselines. """ | 145 """ Returns true iff HTTP clients are allowed to submit new baselines. """ |
147 return self._editable | 146 return self._editable |
148 | 147 |
149 def reload_seconds(self): | 148 def reload_seconds(self): |
150 """ Returns the result reload period in seconds, or 0 if we don't reload | 149 """ Returns the result reload period in seconds, or 0 if we don't reload |
151 results. """ | 150 results. """ |
152 return self._reload_seconds | 151 return self._reload_seconds |
153 | 152 |
154 def update_results(self): | 153 def update_results(self): |
155 """ Create or update self.results, based on the expectations in | 154 """ Create or update self.results, based on the expectations in |
156 self._expectations_dir and the latest actuals from skia-autogen. | 155 EXPECTATIONS_DIR and the latest actuals from skia-autogen. |
157 """ | 156 """ |
158 logging.info('Updating actual GM results in %s from SVN repo %s ...' % ( | 157 logging.info('Updating actual GM results in %s from SVN repo %s ...' % ( |
159 self._actuals_dir, ACTUALS_SVN_REPO)) | 158 self._actuals_dir, ACTUALS_SVN_REPO)) |
160 self._actuals_repo.Update('.') | 159 self._actuals_repo.Update('.') |
161 | 160 |
162 if self._expectations_repo: | 161 if self._expectations_repo: |
163 logging.info( | 162 logging.info( |
164 'Updating expected GM results in %s from SVN repo %s ...' % ( | 163 'Updating expected GM results in %s ...' % EXPECTATIONS_DIR) |
165 self._expectations_dir, EXPECTATIONS_SVN_REPO)) | |
166 self._expectations_repo.Update('.') | 164 self._expectations_repo.Update('.') |
167 | 165 |
168 logging.info( | 166 logging.info( |
169 ('Parsing results from actuals in %s and expectations in %s, ' | 167 ('Parsing results from actuals in %s and expectations in %s, ' |
170 + 'and generating pixel diffs (may take a while) ...') % ( | 168 + 'and generating pixel diffs (may take a while) ...') % ( |
171 self._actuals_dir, self._expectations_dir)) | 169 self._actuals_dir, EXPECTATIONS_DIR)) |
172 self.results = results.Results( | 170 self.results = results.Results( |
173 actuals_root=self._actuals_dir, | 171 actuals_root=self._actuals_dir, |
174 expected_root=self._expectations_dir, | 172 expected_root=EXPECTATIONS_DIR, |
175 generated_images_root=GENERATED_IMAGES_ROOT) | 173 generated_images_root=GENERATED_IMAGES_ROOT) |
176 | 174 |
177 def _result_reloader(self): | 175 def _result_reloader(self): |
178 """ If --reload argument was specified, reload results at the appropriate | 176 """ If --reload argument was specified, reload results at the appropriate |
179 interval. | 177 interval. |
180 """ | 178 """ |
181 while self._reload_seconds: | 179 while self._reload_seconds: |
182 time.sleep(self._reload_seconds) | 180 time.sleep(self._reload_seconds) |
183 self.update_results() | 181 self.update_results() |
184 | 182 |
(...skipping 242 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
427 def main(): | 425 def main(): |
428 logging.basicConfig(level=logging.INFO) | 426 logging.basicConfig(level=logging.INFO) |
429 parser = argparse.ArgumentParser() | 427 parser = argparse.ArgumentParser() |
430 parser.add_argument('--actuals-dir', | 428 parser.add_argument('--actuals-dir', |
431 help=('Directory into which we will check out the latest ' | 429 help=('Directory into which we will check out the latest ' |
432 'actual GM results. If this directory does not ' | 430 'actual GM results. If this directory does not ' |
433 'exist, it will be created. Defaults to %(default)s'), | 431 'exist, it will be created. Defaults to %(default)s'), |
434 default=DEFAULT_ACTUALS_DIR) | 432 default=DEFAULT_ACTUALS_DIR) |
435 parser.add_argument('--editable', action='store_true', | 433 parser.add_argument('--editable', action='store_true', |
436 help=('Allow HTTP clients to submit new baselines.')) | 434 help=('Allow HTTP clients to submit new baselines.')) |
437 # Deprecated the --expectations-dir option, because once our GM expectations | |
438 # are maintained within git we will no longer be able to check out and update | |
439 # them in isolation (in SVN you can update a single directory subtree within | |
440 # a checkout, but you cannot do that with git). | |
441 # | |
442 # In a git world, we will force the user to refer to expectations | |
443 # within the same checkout as this tool (at the relative path | |
444 # ../../expectations/gm ). If they specify the --reload option, we will | |
445 # periodically run "git pull" on the entire Skia checkout, which will update | |
446 # the GM expectations along with everything else (such as this script). | |
447 # | |
448 # We can still allow --actuals-dir to be specified, though, because the | |
449 # actual results will continue to be maintained in the skia-autogen | |
450 # SVN repository. | |
451 parser.add_argument('--deprecated-expectations-dir', | |
452 help=('DEPRECATED due to our transition from SVN to git ' | |
453 '(formerly known as --expectations-dir). ' | |
454 'If you still need this option, contact ' | |
455 'epoger@google.com as soon as possible. WAS: ' | |
456 'Directory under which to find GM expectations; ' | |
457 'defaults to %(default)s'), | |
458 default=DEFAULT_EXPECTATIONS_DIR) | |
459 parser.add_argument('--export', action='store_true', | 435 parser.add_argument('--export', action='store_true', |
460 help=('Instead of only allowing access from HTTP clients ' | 436 help=('Instead of only allowing access from HTTP clients ' |
461 'on localhost, allow HTTP clients on other hosts ' | 437 'on localhost, allow HTTP clients on other hosts ' |
462 'to access this server. WARNING: doing so will ' | 438 'to access this server. WARNING: doing so will ' |
463 'allow users on other hosts to modify your ' | 439 'allow users on other hosts to modify your ' |
464 'GM expectations, if combined with --editable.')) | 440 'GM expectations, if combined with --editable.')) |
465 parser.add_argument('--port', type=int, | 441 parser.add_argument('--port', type=int, |
466 help=('Which TCP port to listen on for HTTP requests; ' | 442 help=('Which TCP port to listen on for HTTP requests; ' |
467 'defaults to %(default)s'), | 443 'defaults to %(default)s'), |
468 default=DEFAULT_PORT) | 444 default=DEFAULT_PORT) |
469 parser.add_argument('--reload', type=int, | 445 parser.add_argument('--reload', type=int, |
470 help=('How often (a period in seconds) to update the ' | 446 help=('How often (a period in seconds) to update the ' |
471 'results. If specified, both ' | 447 'results. If specified, both expected and actual ' |
472 'DEPRECATED_EXPECTATIONS_DIR and ' | 448 'results will be updated. ' |
473 'ACTUAL_DIR will be updated. ' | |
474 'By default, we do not reload at all, and you ' | 449 'By default, we do not reload at all, and you ' |
475 'must restart the server to pick up new data.'), | 450 'must restart the server to pick up new data.'), |
476 default=0) | 451 default=0) |
477 args = parser.parse_args() | 452 args = parser.parse_args() |
478 global _SERVER | 453 global _SERVER |
479 _SERVER = Server(actuals_dir=args.actuals_dir, | 454 _SERVER = Server(actuals_dir=args.actuals_dir, |
480 expectations_dir=args.deprecated_expectations_dir, | |
481 port=args.port, export=args.export, editable=args.editable, | 455 port=args.port, export=args.export, editable=args.editable, |
482 reload_seconds=args.reload) | 456 reload_seconds=args.reload) |
483 _SERVER.run() | 457 _SERVER.run() |
484 | 458 |
485 if __name__ == '__main__': | 459 if __name__ == '__main__': |
486 main() | 460 main() |
OLD | NEW |