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 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 99 reload_seconds: polling interval with which to check for new results; | 99 reload_seconds: polling interval with which to check for new results; |
| 100 if 0, don't check for new results at all | 100 if 0, don't check for new results at all |
| 101 """ | 101 """ |
| 102 self._actuals_dir = actuals_dir | 102 self._actuals_dir = actuals_dir |
| 103 self._expectations_dir = expectations_dir | 103 self._expectations_dir = expectations_dir |
| 104 self._port = port | 104 self._port = port |
| 105 self._export = export | 105 self._export = export |
| 106 self._editable = editable | 106 self._editable = editable |
| 107 self._reload_seconds = reload_seconds | 107 self._reload_seconds = reload_seconds |
| 108 | 108 |
| 109 self._actuals_repo = svn.Svn(actuals_dir) | |
|
epoger
2013/11/14 20:21:56
Now, the self._actuals_repo object is shared betwe
| |
| 110 if not os.path.isdir(actuals_dir): | |
| 111 os.makedirs(actuals_dir) | |
| 112 self._actuals_repo.Checkout(ACTUALS_SVN_REPO, '.') | |
| 113 | |
| 114 # We only update the expectations dir if the server was run with a | |
| 115 # nonzero --reload argument; otherwise, we expect the user to maintain | |
| 116 # her own expectations as she sees fit. | |
| 117 # | |
| 118 # TODO(epoger): Use git instead of svn to check out expectations, since | |
| 119 # the Skia repo is moving to git. | |
| 120 self._expectations_repo = None | |
| 121 if reload_seconds: | |
| 122 self._expectations_repo = svn.Svn(expectations_dir) | |
| 123 if not os.path.isdir(expectations_dir): | |
|
jcgregorio
2013/11/18 16:50:35
Duplicated code from above, break out into a funct
epoger
2013/11/20 19:51:34
Fixed in patchset 2.
| |
| 124 os.makedirs(expectations_dir) | |
| 125 self._expectations_repo.Checkout(EXPECTATIONS_SVN_REPO, '.') | |
| 126 | |
| 109 def is_exported(self): | 127 def is_exported(self): |
| 110 """ Returns true iff HTTP clients on other hosts are allowed to access | 128 """ Returns true iff HTTP clients on other hosts are allowed to access |
| 111 this server. """ | 129 this server. """ |
| 112 return self._export | 130 return self._export |
| 113 | 131 |
| 114 def is_editable(self): | 132 def is_editable(self): |
| 115 """ Returns true iff HTTP clients are allowed to submit new baselines. """ | 133 """ Returns true iff HTTP clients are allowed to submit new baselines. """ |
| 116 return self._editable | 134 return self._editable |
| 117 | 135 |
| 118 def reload_seconds(self): | 136 def reload_seconds(self): |
| 119 """ Returns the result reload period in seconds, or 0 if we don't reload | 137 """ Returns the result reload period in seconds, or 0 if we don't reload |
| 120 results. """ | 138 results. """ |
| 121 return self._reload_seconds | 139 return self._reload_seconds |
| 122 | 140 |
| 123 def update_results(self): | 141 def update_results(self): |
| 124 """ Create or update self.results, based on the expectations in | 142 """ Create or update self.results, based on the expectations in |
| 125 self._expectations_dir and the latest actuals from skia-autogen. | 143 self._expectations_dir and the latest actuals from skia-autogen. |
| 126 """ | 144 """ |
| 127 with self._svn_update_lock: | 145 logging.info('Updating actual GM results in %s from SVN repo %s ...' % ( |
| 128 # self._svn_update_lock prevents us from updating the actual GM results | 146 self._actuals_dir, ACTUALS_SVN_REPO)) |
| 129 # in multiple threads simultaneously | 147 self._actuals_repo.Update('.') |
| 130 logging.info('Updating actual GM results in %s from SVN repo %s ...' % ( | 148 |
| 131 self._actuals_dir, ACTUALS_SVN_REPO)) | 149 if self._expectations_repo: |
| 132 actuals_repo = svn.Svn(self._actuals_dir) | 150 logging.info( |
| 133 if not os.path.isdir(self._actuals_dir): | 151 'Updating expected GM results in %s from SVN repo %s ...' % ( |
| 134 os.makedirs(self._actuals_dir) | 152 self._expectations_dir, EXPECTATIONS_SVN_REPO)) |
| 135 actuals_repo.Checkout(ACTUALS_SVN_REPO, '.') | 153 self._expectations_repo.Update('.') |
| 136 else: | |
| 137 actuals_repo.Update('.') | |
| 138 # We only update the expectations dir if the server was run with a | |
| 139 # nonzero --reload argument; otherwise, we expect the user to maintain | |
| 140 # her own expectations as she sees fit. | |
| 141 # | |
| 142 # self._svn_update_lock prevents us from updating the expected GM results | |
| 143 # in multiple threads simultaneously | |
| 144 # | |
| 145 # TODO(epoger): Use git instead of svn to check out expectations, since | |
| 146 # the Skia repo is moving to git. | |
| 147 if self._reload_seconds: | |
| 148 logging.info( | |
| 149 'Updating expected GM results in %s from SVN repo %s ...' % ( | |
| 150 self._expectations_dir, EXPECTATIONS_SVN_REPO)) | |
| 151 expectations_repo = svn.Svn(self._expectations_dir) | |
| 152 if not os.path.isdir(self._expectations_dir): | |
| 153 os.makedirs(self._expectations_dir) | |
| 154 expectations_repo.Checkout(EXPECTATIONS_SVN_REPO, '.') | |
| 155 else: | |
| 156 expectations_repo.Update('.') | |
| 157 # end of "with self._svn_update_lock:" | |
| 158 | 154 |
| 159 logging.info( | 155 logging.info( |
| 160 ('Parsing results from actuals in %s and expectations in %s, ' | 156 ('Parsing results from actuals in %s and expectations in %s, ' |
| 161 + 'and generating pixel diffs (may take a while) ...') % ( | 157 + 'and generating pixel diffs (may take a while) ...') % ( |
| 162 self._actuals_dir, self._expectations_dir)) | 158 self._actuals_dir, self._expectations_dir)) |
| 163 new_results = results.Results( | 159 self.results = results.Results( |
|
epoger
2013/11/14 20:21:56
I *think* this is fine, because I assume self.resu
jcgregorio
2013/11/18 16:50:35
Yes, assignment won't happen until the RHS is eval
| |
| 164 actuals_root=self._actuals_dir, | 160 actuals_root=self._actuals_dir, |
| 165 expected_root=self._expectations_dir, | 161 expected_root=self._expectations_dir, |
| 166 generated_images_root=GENERATED_IMAGES_ROOT) | 162 generated_images_root=GENERATED_IMAGES_ROOT) |
| 167 | 163 |
| 168 # Make sure we don't update self.results while a client is in the middle | |
| 169 # of reading from it. | |
| 170 with self.results_lock: | |
| 171 self.results = new_results | |
| 172 | |
| 173 def _result_reloader(self): | 164 def _result_reloader(self): |
| 174 """ If --reload argument was specified, reload results at the appropriate | 165 """ If --reload argument was specified, reload results at the appropriate |
| 175 interval. | 166 interval. |
| 176 """ | 167 """ |
| 177 while self._reload_seconds: | 168 while self._reload_seconds: |
| 178 time.sleep(self._reload_seconds) | 169 time.sleep(self._reload_seconds) |
| 179 self.update_results() | 170 self.update_results() |
| 180 | 171 |
| 181 def run(self): | 172 def run(self): |
| 182 self.results_lock = thread.allocate_lock() | |
| 183 self._svn_update_lock = thread.allocate_lock() | |
| 184 self.update_results() | 173 self.update_results() |
| 185 thread.start_new_thread(self._result_reloader, ()) | 174 thread.start_new_thread(self._result_reloader, ()) |
| 186 | 175 |
| 187 if self._export: | 176 if self._export: |
| 188 server_address = ('', self._port) | 177 server_address = ('', self._port) |
| 189 host = get_routable_ip_address() | 178 host = get_routable_ip_address() |
| 190 if self._editable: | 179 if self._editable: |
| 191 logging.warning('Running with combination of "export" and "editable" ' | 180 logging.warning('Running with combination of "export" and "editable" ' |
| 192 'flags. Users on other machines will ' | 181 'flags. Users on other machines will ' |
| 193 'be able to modify your GM expectations!') | 182 'be able to modify your GM expectations!') |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 229 | 218 |
| 230 def do_GET_results(self, type): | 219 def do_GET_results(self, type): |
| 231 """ Handle a GET request for GM results. | 220 """ Handle a GET request for GM results. |
| 232 | 221 |
| 233 Args: | 222 Args: |
| 234 type: string indicating which set of results to return; | 223 type: string indicating which set of results to return; |
| 235 must be one of the results.RESULTS_* constants | 224 must be one of the results.RESULTS_* constants |
| 236 """ | 225 """ |
| 237 logging.debug('do_GET_results: sending results of type "%s"' % type) | 226 logging.debug('do_GET_results: sending results of type "%s"' % type) |
| 238 try: | 227 try: |
| 228 # Since we must make multiple calls to the Results object, grab a | |
| 229 # reference to it in case it is updated to point at a new Results | |
| 230 # object within another thread. | |
| 231 # | |
| 239 # TODO(epoger): Rather than using a global variable for the handler | 232 # TODO(epoger): Rather than using a global variable for the handler |
| 240 # to refer to the Server object, make Server a subclass of | 233 # to refer to the Server object, make Server a subclass of |
| 241 # HTTPServer, and then it could be available to the handler via | 234 # HTTPServer, and then it could be available to the handler via |
| 242 # the handler's .server instance variable. | 235 # the handler's .server instance variable. |
| 236 results_obj = _SERVER.results | |
| 237 response_dict = results_obj.get_results_of_type(type) | |
| 238 time_updated = results_obj.get_timestamp() | |
| 243 | 239 |
| 244 with _SERVER.results_lock: | |
| 245 response_dict = _SERVER.results.get_results_of_type(type) | |
| 246 time_updated = _SERVER.results.get_timestamp() | |
| 247 response_dict['header'] = { | 240 response_dict['header'] = { |
| 248 # Timestamps: | 241 # Timestamps: |
| 249 # 1. when this data was last updated | 242 # 1. when this data was last updated |
| 250 # 2. when the caller should check back for new data (if ever) | 243 # 2. when the caller should check back for new data (if ever) |
| 251 # | 244 # |
| 252 # We only return these timestamps if the --reload argument was passed; | 245 # We only return these timestamps if the --reload argument was passed; |
| 253 # otherwise, we have no idea when the expectations were last updated | 246 # otherwise, we have no idea when the expectations were last updated |
| 254 # (we allow the user to maintain her own expectations as she sees fit). | 247 # (we allow the user to maintain her own expectations as she sees fit). |
| 255 'timeUpdated': time_updated if _SERVER.reload_seconds() else None, | 248 'timeUpdated': time_updated if _SERVER.reload_seconds() else None, |
| 256 'timeNextUpdateAvailable': ( | 249 'timeNextUpdateAvailable': ( |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 346 if content_type != 'application/json;charset=UTF-8': | 339 if content_type != 'application/json;charset=UTF-8': |
| 347 raise Exception('unsupported %s [%s]' % ( | 340 raise Exception('unsupported %s [%s]' % ( |
| 348 _HTTP_HEADER_CONTENT_TYPE, content_type)) | 341 _HTTP_HEADER_CONTENT_TYPE, content_type)) |
| 349 | 342 |
| 350 content_length = int(self.headers[_HTTP_HEADER_CONTENT_LENGTH]) | 343 content_length = int(self.headers[_HTTP_HEADER_CONTENT_LENGTH]) |
| 351 json_data = self.rfile.read(content_length) | 344 json_data = self.rfile.read(content_length) |
| 352 data = json.loads(json_data) | 345 data = json.loads(json_data) |
| 353 logging.debug('do_POST_edits: received new GM expectations data [%s]' % | 346 logging.debug('do_POST_edits: received new GM expectations data [%s]' % |
| 354 data) | 347 data) |
| 355 | 348 |
| 356 with _SERVER.results_lock: | 349 # Since we must make multiple calls to the Results object, grab a |
| 357 oldResultsType = data['oldResultsType'] | 350 # reference to it in case it is updated to point at a new Results |
| 358 oldResults = _SERVER.results.get_results_of_type(oldResultsType) | 351 # object within another thread. |
| 359 oldResultsHash = str(hash(repr(oldResults['testData']))) | 352 results_obj = _SERVER.results |
| 360 if oldResultsHash != data['oldResultsHash']: | 353 oldResultsType = data['oldResultsType'] |
| 361 raise Exception('results of type "%s" changed while the client was ' | 354 oldResults = results_obj.get_results_of_type(oldResultsType) |
| 362 'making modifications. The client should reload the ' | 355 oldResultsHash = str(hash(repr(oldResults['testData']))) |
| 363 'results and submit the modifications again.' % | 356 if oldResultsHash != data['oldResultsHash']: |
| 364 oldResultsType) | 357 raise Exception('results of type "%s" changed while the client was ' |
| 365 _SERVER.results.edit_expectations(data['modifications']) | 358 'making modifications. The client should reload the ' |
| 359 'results and submit the modifications again.' % | |
| 360 oldResultsType) | |
| 361 results_obj.edit_expectations(data['modifications']) | |
| 366 | 362 |
| 367 # Now that the edits have been committed, update results to reflect them. | 363 # Now that the edits have been committed, update results to reflect them. |
| 368 _SERVER.update_results() | 364 _SERVER.update_results() |
| 369 | 365 |
| 370 def redirect_to(self, url): | 366 def redirect_to(self, url): |
| 371 """ Redirect the HTTP client to a different url. | 367 """ Redirect the HTTP client to a different url. |
| 372 | 368 |
| 373 Args: | 369 Args: |
| 374 url: URL to redirect the HTTP client to | 370 url: URL to redirect the HTTP client to |
| 375 """ | 371 """ |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 449 args = parser.parse_args() | 445 args = parser.parse_args() |
| 450 global _SERVER | 446 global _SERVER |
| 451 _SERVER = Server(actuals_dir=args.actuals_dir, | 447 _SERVER = Server(actuals_dir=args.actuals_dir, |
| 452 expectations_dir=args.expectations_dir, | 448 expectations_dir=args.expectations_dir, |
| 453 port=args.port, export=args.export, editable=args.editable, | 449 port=args.port, export=args.export, editable=args.editable, |
| 454 reload_seconds=args.reload) | 450 reload_seconds=args.reload) |
| 455 _SERVER.run() | 451 _SERVER.run() |
| 456 | 452 |
| 457 if __name__ == '__main__': | 453 if __name__ == '__main__': |
| 458 main() | 454 main() |
| OLD | NEW |