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 """ |
11 | 11 |
12 # System-level imports | 12 # System-level imports |
13 import argparse | 13 import argparse |
14 import BaseHTTPServer | 14 import BaseHTTPServer |
15 import json | 15 import json |
16 import logging | 16 import logging |
17 import os | 17 import os |
18 import posixpath | 18 import posixpath |
19 import re | 19 import re |
20 import shutil | 20 import shutil |
21 import socket | 21 import socket |
22 import subprocess | 22 import subprocess |
| 23 import sys |
23 import thread | 24 import thread |
24 import threading | 25 import threading |
25 import time | 26 import time |
26 import urlparse | 27 import urlparse |
27 | 28 |
28 # Imports from within Skia | 29 # Imports from within Skia |
29 import fix_pythonpath # must do this first | 30 # |
30 from pyutils import gs_utils | 31 # We need to add the 'tools' directory for svn.py, and the 'gm' directory for |
| 32 # gm_json.py . |
| 33 # that directory. |
| 34 # Make sure that the 'tools' dir is in the PYTHONPATH, but add it at the *end* |
| 35 # so any dirs that are already in the PYTHONPATH will be preferred. |
| 36 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) |
| 37 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY) |
| 38 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY) |
| 39 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools') |
| 40 if TOOLS_DIRECTORY not in sys.path: |
| 41 sys.path.append(TOOLS_DIRECTORY) |
| 42 import svn |
| 43 if GM_DIRECTORY not in sys.path: |
| 44 sys.path.append(GM_DIRECTORY) |
31 import gm_json | 45 import gm_json |
32 | 46 |
33 # Imports from local dir | 47 # Imports from local dir |
34 # | 48 # |
35 # 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 |
36 # Server.results() property. See discussion at | 50 # Server.results() property. See discussion at |
37 # 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 |
38 import compare_configs | 52 import compare_configs |
39 import compare_to_expectations | 53 import compare_to_expectations |
40 import download_actuals | |
41 import imagepairset | 54 import imagepairset |
42 import results as results_mod | 55 import results as results_mod |
43 | 56 |
44 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') | 57 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') |
45 | 58 |
46 # 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 |
47 # 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 |
48 # has no entry in this dictionary. | 61 # has no entry in this dictionary. |
49 MIME_TYPE_MAP = {'': 'application/octet-stream', | 62 MIME_TYPE_MAP = {'': 'application/octet-stream', |
50 'html': 'text/html', | 63 'html': 'text/html', |
51 'css': 'text/css', | 64 'css': 'text/css', |
52 'png': 'image/png', | 65 'png': 'image/png', |
53 'js': 'application/javascript', | 66 'js': 'application/javascript', |
54 'json': 'application/json' | 67 'json': 'application/json' |
55 } | 68 } |
56 | 69 |
57 # Keys that server.py uses to create the toplevel content header. | 70 # Keys that server.py uses to create the toplevel content header. |
58 # NOTE: Keep these in sync with static/constants.js | 71 # NOTE: Keep these in sync with static/constants.js |
59 KEY__EDITS__MODIFICATIONS = 'modifications' | 72 KEY__EDITS__MODIFICATIONS = 'modifications' |
60 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' | 73 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' |
61 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' | 74 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' |
62 | 75 |
63 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR | 76 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR |
64 DEFAULT_GM_SUMMARIES_BUCKET = download_actuals.GM_SUMMARIES_BUCKET | 77 DEFAULT_ACTUALS_REPO_REVISION = 'HEAD' |
65 DEFAULT_JSON_FILENAME = download_actuals.DEFAULT_JSON_FILENAME | 78 DEFAULT_ACTUALS_REPO_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual' |
66 DEFAULT_PORT = 8888 | 79 DEFAULT_PORT = 8888 |
67 | 80 |
68 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) | |
69 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) | |
70 # Directory, relative to PARENT_DIRECTORY, within which the server will serve | 81 # Directory, relative to PARENT_DIRECTORY, within which the server will serve |
71 # out live results (not static files). | 82 # out live results (not static files). |
72 RESULTS_SUBDIR = 'results' | 83 RESULTS_SUBDIR = 'results' |
73 # Directory, relative to PARENT_DIRECTORY, within which the server will serve | 84 # Directory, relative to PARENT_DIRECTORY, within which the server will serve |
74 # out static files. | 85 # out static files. |
75 STATIC_CONTENTS_SUBDIR = 'static' | 86 STATIC_CONTENTS_SUBDIR = 'static' |
76 # All of the GENERATED_*_SUBDIRS are relative to STATIC_CONTENTS_SUBDIR | 87 # All of the GENERATED_*_SUBDIRS are relative to STATIC_CONTENTS_SUBDIR |
77 GENERATED_HTML_SUBDIR = 'generated-html' | 88 GENERATED_HTML_SUBDIR = 'generated-html' |
78 GENERATED_IMAGES_SUBDIR = 'generated-images' | 89 GENERATED_IMAGES_SUBDIR = 'generated-images' |
79 GENERATED_JSON_SUBDIR = 'generated-json' | 90 GENERATED_JSON_SUBDIR = 'generated-json' |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
121 """Returns routable IP address of this host (the IP address of its network | 132 """Returns routable IP address of this host (the IP address of its network |
122 interface that would be used for most traffic, not its localhost | 133 interface that would be used for most traffic, not its localhost |
123 interface). See http://stackoverflow.com/a/166589 """ | 134 interface). See http://stackoverflow.com/a/166589 """ |
124 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | 135 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
125 sock.connect(('8.8.8.8', 80)) | 136 sock.connect(('8.8.8.8', 80)) |
126 host = sock.getsockname()[0] | 137 host = sock.getsockname()[0] |
127 sock.close() | 138 sock.close() |
128 return host | 139 return host |
129 | 140 |
130 | 141 |
| 142 def _create_svn_checkout(dir_path, repo_url): |
| 143 """Creates local checkout of an SVN repository at the specified directory |
| 144 path, returning an svn.Svn object referring to the local checkout. |
| 145 |
| 146 Args: |
| 147 dir_path: path to the local checkout; if this directory does not yet exist, |
| 148 it will be created and the repo will be checked out into it |
| 149 repo_url: URL of SVN repo to check out into dir_path (unless the local |
| 150 checkout already exists) |
| 151 Returns: an svn.Svn object referring to the local checkout. |
| 152 """ |
| 153 local_checkout = svn.Svn(dir_path) |
| 154 if not os.path.isdir(dir_path): |
| 155 os.makedirs(dir_path) |
| 156 local_checkout.Checkout(repo_url, '.') |
| 157 return local_checkout |
| 158 |
| 159 |
131 def _create_index(file_path, config_pairs): | 160 def _create_index(file_path, config_pairs): |
132 """Creates an index file linking to all results available from this server. | 161 """Creates an index file linking to all results available from this server. |
133 | 162 |
134 Prior to https://codereview.chromium.org/215503002 , we had a static | 163 Prior to https://codereview.chromium.org/215503002 , we had a static |
135 index.html within our repo. But now that the results may or may not include | 164 index.html within our repo. But now that the results may or may not include |
136 config comparisons, index.html needs to be generated differently depending | 165 config comparisons, index.html needs to be generated differently depending |
137 on which results are included. | 166 on which results are included. |
138 | 167 |
139 TODO(epoger): Instead of including raw HTML within the Python code, | 168 TODO(epoger): Instead of including raw HTML within the Python code, |
140 consider restoring the index.html file as a template and using django (or | 169 consider restoring the index.html file as a template and using django (or |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
177 file_handle.write('</li>') | 206 file_handle.write('</li>') |
178 file_handle.write('</ul>') | 207 file_handle.write('</ul>') |
179 file_handle.write('</ul></body></html>') | 208 file_handle.write('</ul></body></html>') |
180 | 209 |
181 | 210 |
182 class Server(object): | 211 class Server(object): |
183 """ HTTP server for our HTML rebaseline viewer. """ | 212 """ HTTP server for our HTML rebaseline viewer. """ |
184 | 213 |
185 def __init__(self, | 214 def __init__(self, |
186 actuals_dir=DEFAULT_ACTUALS_DIR, | 215 actuals_dir=DEFAULT_ACTUALS_DIR, |
187 json_filename=DEFAULT_JSON_FILENAME, | 216 actuals_repo_revision=DEFAULT_ACTUALS_REPO_REVISION, |
188 gm_summaries_bucket=DEFAULT_GM_SUMMARIES_BUCKET, | 217 actuals_repo_url=DEFAULT_ACTUALS_REPO_URL, |
189 port=DEFAULT_PORT, export=False, editable=True, | 218 port=DEFAULT_PORT, export=False, editable=True, |
190 reload_seconds=0, config_pairs=None, builder_regex_list=None): | 219 reload_seconds=0, config_pairs=None, builder_regex_list=None): |
191 """ | 220 """ |
192 Args: | 221 Args: |
193 actuals_dir: directory under which we will check out the latest actual | 222 actuals_dir: directory under which we will check out the latest actual |
194 GM results | 223 GM results |
195 json_filename: basename of the JSON summary file to load for each builder | 224 actuals_repo_revision: revision of actual-results.json files to process |
196 gm_summaries_bucket: Google Storage bucket to download json_filename | 225 actuals_repo_url: SVN repo to download actual-results.json files from; |
197 files from; if None or '', don't fetch new actual-results files | 226 if None or '', don't fetch new actual-results files at all, |
198 at all, just compare to whatever files are already in actuals_dir | 227 just compare to whatever files are already in actuals_dir |
199 port: which TCP port to listen on for HTTP requests | 228 port: which TCP port to listen on for HTTP requests |
200 export: whether to allow HTTP clients on other hosts to access this server | 229 export: whether to allow HTTP clients on other hosts to access this server |
201 editable: whether HTTP clients are allowed to submit new baselines | 230 editable: whether HTTP clients are allowed to submit new baselines |
202 reload_seconds: polling interval with which to check for new results; | 231 reload_seconds: polling interval with which to check for new results; |
203 if 0, don't check for new results at all | 232 if 0, don't check for new results at all |
204 config_pairs: List of (string, string) tuples; for each tuple, compare | 233 config_pairs: List of (string, string) tuples; for each tuple, compare |
205 actual results of these two configs. If None or empty, | 234 actual results of these two configs. If None or empty, |
206 don't compare configs at all. | 235 don't compare configs at all. |
207 builder_regex_list: List of regular expressions specifying which builders | 236 builder_regex_list: List of regular expressions specifying which builders |
208 we will process. If None, process all builders. | 237 we will process. If None, process all builders. |
209 """ | 238 """ |
210 self._actuals_dir = actuals_dir | 239 self._actuals_dir = actuals_dir |
211 self._json_filename = json_filename | 240 self._actuals_repo_revision = actuals_repo_revision |
212 self._gm_summaries_bucket = gm_summaries_bucket | 241 self._actuals_repo_url = actuals_repo_url |
213 self._port = port | 242 self._port = port |
214 self._export = export | 243 self._export = export |
215 self._editable = editable | 244 self._editable = editable |
216 self._reload_seconds = reload_seconds | 245 self._reload_seconds = reload_seconds |
217 self._config_pairs = config_pairs or [] | 246 self._config_pairs = config_pairs or [] |
218 self._builder_regex_list = builder_regex_list | 247 self._builder_regex_list = builder_regex_list |
219 _create_index( | 248 _create_index( |
220 file_path=os.path.join( | 249 file_path=os.path.join( |
221 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_HTML_SUBDIR, | 250 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_HTML_SUBDIR, |
222 "index.html"), | 251 "index.html"), |
223 config_pairs=config_pairs) | 252 config_pairs=config_pairs) |
| 253 # TODO(epoger): Create shareable functions within download_actuals.py that |
| 254 # we can use both there and here to download the actual image results. |
| 255 if actuals_repo_url: |
| 256 self._actuals_repo = _create_svn_checkout( |
| 257 dir_path=actuals_dir, repo_url=actuals_repo_url) |
224 | 258 |
225 # Reentrant lock that must be held whenever updating EITHER of: | 259 # Reentrant lock that must be held whenever updating EITHER of: |
226 # 1. self._results | 260 # 1. self._results |
227 # 2. the expected or actual results on local disk | 261 # 2. the expected or actual results on local disk |
228 self.results_rlock = threading.RLock() | 262 self.results_rlock = threading.RLock() |
229 # self._results will be filled in by calls to update_results() | 263 # self._results will be filled in by calls to update_results() |
230 self._results = None | 264 self._results = None |
231 | 265 |
232 @property | 266 @property |
233 def results(self): | 267 def results(self): |
(...skipping 27 matching lines...) Expand all Loading... |
261 the same time. | 295 the same time. |
262 | 296 |
263 Args: | 297 Args: |
264 invalidate: if True, invalidate self._results immediately upon entry; | 298 invalidate: if True, invalidate self._results immediately upon entry; |
265 otherwise, we will let readers see those results until we | 299 otherwise, we will let readers see those results until we |
266 replace them | 300 replace them |
267 """ | 301 """ |
268 with self.results_rlock: | 302 with self.results_rlock: |
269 if invalidate: | 303 if invalidate: |
270 self._results = None | 304 self._results = None |
271 if self._gm_summaries_bucket: | 305 if self._actuals_repo_url: |
272 logging.info( | 306 logging.info( |
273 'Updating GM result summaries in %s from gm_summaries_bucket %s ...' | 307 'Updating actual GM results in %s to revision %s from repo %s ...' |
274 % (self._actuals_dir, self._gm_summaries_bucket)) | 308 % ( |
275 | 309 self._actuals_dir, self._actuals_repo_revision, |
276 # Clean out actuals_dir first, in case some builders have gone away | 310 self._actuals_repo_url)) |
277 # since we last ran. | 311 self._actuals_repo.Update( |
278 if os.path.isdir(self._actuals_dir): | 312 path='.', revision=self._actuals_repo_revision) |
279 shutil.rmtree(self._actuals_dir) | |
280 | |
281 # Get the list of builders we care about. | |
282 all_builders = download_actuals.get_builders_list( | |
283 summaries_bucket=self._gm_summaries_bucket) | |
284 if self._builder_regex_list: | |
285 matching_builders = [] | |
286 for builder in all_builders: | |
287 for regex in self._builder_regex_list: | |
288 if re.match(regex, builder): | |
289 matching_builders.append(builder) | |
290 break # go on to the next builder, no need to try more regexes | |
291 else: | |
292 matching_builders = all_builders | |
293 | |
294 # Download the JSON file for each builder we care about. | |
295 # | |
296 # TODO(epoger): When this is a large number of builders, we would be | |
297 # better off downloading them in parallel! | |
298 for builder in matching_builders: | |
299 gs_utils.download_file( | |
300 source_bucket=self._gm_summaries_bucket, | |
301 source_path=posixpath.join(builder, self._json_filename), | |
302 dest_path=os.path.join(self._actuals_dir, builder, | |
303 self._json_filename), | |
304 create_subdirs_if_needed=True) | |
305 | 313 |
306 # We only update the expectations dir if the server was run with a | 314 # We only update the expectations dir if the server was run with a |
307 # nonzero --reload argument; otherwise, we expect the user to maintain | 315 # nonzero --reload argument; otherwise, we expect the user to maintain |
308 # her own expectations as she sees fit. | 316 # her own expectations as she sees fit. |
309 # | 317 # |
310 # Because the Skia repo is hosted using git, and git does not | 318 # Because the Skia repo is moving from SVN to git, and git does not |
311 # support updating a single directory tree, we have to update the entire | 319 # support updating a single directory tree, we have to update the entire |
312 # repo checkout. | 320 # repo checkout. |
313 # | 321 # |
314 # Because Skia uses depot_tools, we have to update using "gclient sync" | 322 # Because Skia uses depot_tools, we have to update using "gclient sync" |
315 # instead of raw git commands. | 323 # instead of raw git (or SVN) update. Happily, this will work whether |
316 # | 324 # the checkout was created using git or SVN. |
317 # TODO(epoger): Fetch latest expectations in some other way. | |
318 # Eric points out that our official documentation recommends an | |
319 # unmanaged Skia checkout, so "gclient sync" will not bring down updated | |
320 # expectations from origin/master-- you'd have to do a "git pull" of | |
321 # some sort instead. | |
322 # However, the live rebaseline_server at | |
323 # http://skia-tree-status.appspot.com/redirect/rebaseline-server (which | |
324 # is probably the only user of the --reload flag!) uses a managed | |
325 # checkout, so "gclient sync" works in that case. | |
326 # Probably the best idea is to avoid all of this nonsense by fetching | |
327 # updated expectations into a temp directory, and leaving the rest of | |
328 # the checkout alone. This could be done using "git show", or by | |
329 # downloading individual expectation JSON files from | |
330 # skia.googlesource.com . | |
331 if self._reload_seconds: | 325 if self._reload_seconds: |
332 logging.info( | 326 logging.info( |
333 'Updating expected GM results in %s by syncing Skia repo ...' % | 327 'Updating expected GM results in %s by syncing Skia repo ...' % |
334 compare_to_expectations.DEFAULT_EXPECTATIONS_DIR) | 328 compare_to_expectations.DEFAULT_EXPECTATIONS_DIR) |
335 _run_command(['gclient', 'sync'], TRUNK_DIRECTORY) | 329 _run_command(['gclient', 'sync'], TRUNK_DIRECTORY) |
336 | 330 |
337 self._results = compare_to_expectations.ExpectationComparisons( | 331 self._results = compare_to_expectations.ExpectationComparisons( |
338 actuals_root=self._actuals_dir, | 332 actuals_root=self._actuals_dir, |
339 generated_images_root=os.path.join( | 333 generated_images_root=os.path.join( |
340 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, | 334 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, |
(...skipping 281 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
622 def main(): | 616 def main(): |
623 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', | 617 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', |
624 datefmt='%m/%d/%Y %H:%M:%S', | 618 datefmt='%m/%d/%Y %H:%M:%S', |
625 level=logging.INFO) | 619 level=logging.INFO) |
626 parser = argparse.ArgumentParser() | 620 parser = argparse.ArgumentParser() |
627 parser.add_argument('--actuals-dir', | 621 parser.add_argument('--actuals-dir', |
628 help=('Directory into which we will check out the latest ' | 622 help=('Directory into which we will check out the latest ' |
629 'actual GM results. If this directory does not ' | 623 'actual GM results. If this directory does not ' |
630 'exist, it will be created. Defaults to %(default)s'), | 624 'exist, it will be created. Defaults to %(default)s'), |
631 default=DEFAULT_ACTUALS_DIR) | 625 default=DEFAULT_ACTUALS_DIR) |
632 # TODO(epoger): Before https://codereview.chromium.org/310093003 , | 626 parser.add_argument('--actuals-repo', |
633 # when this tool downloaded the JSON summaries from skia-autogen, | 627 help=('URL of SVN repo to download actual-results.json ' |
634 # it had an --actuals-revision the caller could specify to download | 628 'files from. Defaults to %(default)s ; if set to ' |
635 # actual results as of a specific point in time. We should add similar | 629 'empty string, just compare to actual-results ' |
636 # functionality when retrieving the summaries from Google Storage. | 630 'already found in ACTUALS_DIR.'), |
| 631 default=DEFAULT_ACTUALS_REPO_URL) |
| 632 parser.add_argument('--actuals-revision', |
| 633 help=('revision of actual-results.json files to process. ' |
| 634 'Defaults to %(default)s . Beware of setting this ' |
| 635 'argument in conjunction with --editable; you ' |
| 636 'probably only want to edit results at HEAD.'), |
| 637 default=DEFAULT_ACTUALS_REPO_REVISION) |
637 parser.add_argument('--builders', metavar='BUILDER_REGEX', nargs='+', | 638 parser.add_argument('--builders', metavar='BUILDER_REGEX', nargs='+', |
638 help=('Only process builders matching these regular ' | 639 help=('Only process builders matching these regular ' |
639 'expressions. If unspecified, process all ' | 640 'expressions. If unspecified, process all ' |
640 'builders.')) | 641 'builders.')) |
641 parser.add_argument('--compare-configs', action='store_true', | 642 parser.add_argument('--compare-configs', action='store_true', |
642 help=('In addition to generating differences between ' | 643 help=('In addition to generating differences between ' |
643 'expectations and actuals, also generate ' | 644 'expectations and actuals, also generate ' |
644 'differences between these config pairs: ' | 645 'differences between these config pairs: ' |
645 + str(CONFIG_PAIRS_TO_COMPARE))) | 646 + str(CONFIG_PAIRS_TO_COMPARE))) |
646 parser.add_argument('--editable', action='store_true', | 647 parser.add_argument('--editable', action='store_true', |
647 help=('Allow HTTP clients to submit new baselines.')) | 648 help=('Allow HTTP clients to submit new baselines.')) |
648 parser.add_argument('--export', action='store_true', | 649 parser.add_argument('--export', action='store_true', |
649 help=('Instead of only allowing access from HTTP clients ' | 650 help=('Instead of only allowing access from HTTP clients ' |
650 'on localhost, allow HTTP clients on other hosts ' | 651 'on localhost, allow HTTP clients on other hosts ' |
651 'to access this server. WARNING: doing so will ' | 652 'to access this server. WARNING: doing so will ' |
652 'allow users on other hosts to modify your ' | 653 'allow users on other hosts to modify your ' |
653 'GM expectations, if combined with --editable.')) | 654 'GM expectations, if combined with --editable.')) |
654 parser.add_argument('--gm-summaries-bucket', | |
655 help=('Google Cloud Storage bucket to download ' | |
656 'JSON_FILENAME files from. ' | |
657 'Defaults to %(default)s ; if set to ' | |
658 'empty string, just compare to actual-results ' | |
659 'already found in ACTUALS_DIR.'), | |
660 default=DEFAULT_GM_SUMMARIES_BUCKET) | |
661 parser.add_argument('--json-filename', | |
662 help=('JSON summary filename to read for each builder; ' | |
663 'defaults to %(default)s.'), | |
664 default=DEFAULT_JSON_FILENAME) | |
665 parser.add_argument('--port', type=int, | 655 parser.add_argument('--port', type=int, |
666 help=('Which TCP port to listen on for HTTP requests; ' | 656 help=('Which TCP port to listen on for HTTP requests; ' |
667 'defaults to %(default)s'), | 657 'defaults to %(default)s'), |
668 default=DEFAULT_PORT) | 658 default=DEFAULT_PORT) |
669 parser.add_argument('--reload', type=int, | 659 parser.add_argument('--reload', type=int, |
670 help=('How often (a period in seconds) to update the ' | 660 help=('How often (a period in seconds) to update the ' |
671 'results. If specified, both expected and actual ' | 661 'results. If specified, both expected and actual ' |
672 'results will be updated by running "gclient sync" ' | 662 'results will be updated by running "gclient sync" ' |
673 'on your Skia checkout as a whole. ' | 663 'on your Skia checkout as a whole. ' |
674 'By default, we do not reload at all, and you ' | 664 'By default, we do not reload at all, and you ' |
675 'must restart the server to pick up new data.'), | 665 'must restart the server to pick up new data.'), |
676 default=0) | 666 default=0) |
677 args = parser.parse_args() | 667 args = parser.parse_args() |
678 if args.compare_configs: | 668 if args.compare_configs: |
679 config_pairs = CONFIG_PAIRS_TO_COMPARE | 669 config_pairs = CONFIG_PAIRS_TO_COMPARE |
680 else: | 670 else: |
681 config_pairs = None | 671 config_pairs = None |
682 | 672 |
683 global _SERVER | 673 global _SERVER |
684 _SERVER = Server(actuals_dir=args.actuals_dir, | 674 _SERVER = Server(actuals_dir=args.actuals_dir, |
685 json_filename=args.json_filename, | 675 actuals_repo_revision=args.actuals_revision, |
686 gm_summaries_bucket=args.gm_summaries_bucket, | 676 actuals_repo_url=args.actuals_repo, |
687 port=args.port, export=args.export, editable=args.editable, | 677 port=args.port, export=args.export, editable=args.editable, |
688 reload_seconds=args.reload, config_pairs=config_pairs, | 678 reload_seconds=args.reload, config_pairs=config_pairs, |
689 builder_regex_list=args.builders) | 679 builder_regex_list=args.builders) |
690 _SERVER.run() | 680 _SERVER.run() |
691 | 681 |
692 | 682 |
693 if __name__ == '__main__': | 683 if __name__ == '__main__': |
694 main() | 684 main() |
OLD | NEW |