| 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 |