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 |