Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(62)

Side by Side Diff: tools/skpdiff/skpdiff_server.py

Issue 22580004: add ui for mutli-rebaselining (Closed) Base URL: https://skia.googlecode.com/svn/trunk
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tools/skpdiff/diff_viewer.js ('k') | tools/skpdiff/viewer.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 from __future__ import print_function 4 from __future__ import print_function
5 import argparse 5 import argparse
6 import BaseHTTPServer 6 import BaseHTTPServer
7 import json 7 import json
8 import os 8 import os
9 import os.path 9 import os.path
10 import re 10 import re
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
92 writer.write(reader.read()) 92 writer.write(reader.read())
93 93
94 94
95 def download_gm_image(image_name, image_path, hash_val): 95 def download_gm_image(image_name, image_path, hash_val):
96 """Download the gm result into the given path. 96 """Download the gm result into the given path.
97 97
98 @param image_name The GM file name, for example imageblur_gpu.png. 98 @param image_name The GM file name, for example imageblur_gpu.png.
99 @param image_path Path to place the image. 99 @param image_path Path to place the image.
100 @param hash_val The hash value of the image. 100 @param hash_val The hash value of the image.
101 """ 101 """
102 if hash_val is None:
103 return
102 104
103 # Separate the test name from a image name 105 # Separate the test name from a image name
104 image_match = IMAGE_FILENAME_RE.match(image_name) 106 image_match = IMAGE_FILENAME_RE.match(image_name)
105 test_name = image_match.group(1) 107 test_name = image_match.group(1)
106 108
107 # Calculate the URL of the requested image 109 # Calculate the URL of the requested image
108 image_url = gm_json.CreateGmActualUrl( 110 image_url = gm_json.CreateGmActualUrl(
109 test_name, gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, hash_val) 111 test_name, gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, hash_val)
110 112
111 # Download the image as requested 113 # Download the image as requested
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
167 git_version_content, _ = git_show_proc.communicate() 169 git_version_content, _ = git_show_proc.communicate()
168 return git_version_content 170 return git_version_content
169 171
170 172
171 class GMInstance: 173 class GMInstance:
172 """Information about a GM test result on a specific device: 174 """Information about a GM test result on a specific device:
173 - device_name = the name of the device that rendered it 175 - device_name = the name of the device that rendered it
174 - image_name = the GM test name and config 176 - image_name = the GM test name and config
175 - expected_hash = the current expected hash value 177 - expected_hash = the current expected hash value
176 - actual_hash = the actual hash value 178 - actual_hash = the actual hash value
179 - is_rebaselined = True if actual_hash is what is currently in the expected
180 results file, False otherwise.
177 """ 181 """
178 def __init__(self, 182 def __init__(self,
179 device_name, image_name, 183 device_name, image_name,
180 expected_hash, actual_hash): 184 expected_hash, actual_hash,
185 is_rebaselined):
181 self.device_name = device_name 186 self.device_name = device_name
182 self.image_name = image_name 187 self.image_name = image_name
183 self.expected_hash = expected_hash 188 self.expected_hash = expected_hash
184 self.actual_hash = actual_hash 189 self.actual_hash = actual_hash
190 self.is_rebaselined = is_rebaselined
185 191
186 192
187 class ExpectationsManager: 193 class ExpectationsManager:
188 def __init__(self, expectations_dir, expected_name, updated_name, 194 def __init__(self, expectations_dir, expected_name, updated_name,
189 skpdiff_path): 195 skpdiff_path):
190 """ 196 """
191 @param expectations_dir The directory to traverse for results files. 197 @param expectations_dir The directory to traverse for results files.
192 This should resemble expectations/gm in the Skia trunk. 198 This should resemble expectations/gm in the Skia trunk.
193 @param expected_name The name of the expected result files. These 199 @param expected_name The name of the expected result files. These
194 are in the format of expected-results.json. 200 are in the format of expected-results.json.
(...skipping 26 matching lines...) Expand all
221 actual_image_dir = os.path.join(image_output_dir, 'actual') 227 actual_image_dir = os.path.join(image_output_dir, 'actual')
222 os.mkdir(expected_image_dir) 228 os.mkdir(expected_image_dir)
223 os.mkdir(actual_image_dir) 229 os.mkdir(actual_image_dir)
224 230
225 # Download expected and actual images that differed into the temporary 231 # Download expected and actual images that differed into the temporary
226 # file tree. 232 # file tree.
227 self._download_expectation_images(expected_image_dir, actual_image_dir) 233 self._download_expectation_images(expected_image_dir, actual_image_dir)
228 234
229 # Invoke skpdiff with our downloaded images and place its results in the 235 # Invoke skpdiff with our downloaded images and place its results in the
230 # temporary directory. 236 # temporary directory.
231 self.skpdiff_output_path = os.path.join(image_output_dir, 237 self._skpdiff_output_path = os.path.join(image_output_dir,
232 'skpdiff_output.json') 238 'skpdiff_output.json')
233 skpdiff_cmd = SKPDIFF_INVOKE_FORMAT.format(self._skpdiff_path, 239 skpdiff_cmd = SKPDIFF_INVOKE_FORMAT.format(self._skpdiff_path,
234 self.skpdiff_output_path, 240 self._skpdiff_output_path,
235 expected_image_dir, 241 expected_image_dir,
236 actual_image_dir) 242 actual_image_dir)
237 os.system(skpdiff_cmd) 243 os.system(skpdiff_cmd)
244 self._load_skpdiff_output()
238 245
239 246
240 def _get_expectations(self): 247 def _get_expectations(self):
241 """Fills self._expectations with GMInstance objects for each test whose 248 """Fills self._expectations with GMInstance objects for each test whose
242 expectation is different between the following two files: 249 expectation is different between the following two files:
243 - the local filesystem's updated results file 250 - the local filesystem's updated results file
244 - git's head version of the expected results file 251 - git's head version of the expected results file
245 """ 252 """
246 differ = jsondiff.GMDiffer() 253 differ = jsondiff.GMDiffer()
247 self._expectations = [] 254 self._expectations = []
(...skipping 12 matching lines...) Expand all
260 if not os.path.isfile(updated_file_path): 267 if not os.path.isfile(updated_file_path):
261 continue 268 continue
262 269
263 # Always get the expected results from git because we may have 270 # Always get the expected results from git because we may have
264 # changed them in a previous instance of the server. 271 # changed them in a previous instance of the server.
265 expected_contents = get_head_version(expected_file_path) 272 expected_contents = get_head_version(expected_file_path)
266 updated_contents = None 273 updated_contents = None
267 with open(updated_file_path, 'rb') as updated_file: 274 with open(updated_file_path, 'rb') as updated_file:
268 updated_contents = updated_file.read() 275 updated_contents = updated_file.read()
269 276
277 # Read the expected results on disk to determine what we've
278 # already rebaselined.
279 commited_contents = None
280 with open(expected_file_path, 'rb') as expected_file:
281 commited_contents = expected_file.read()
282
270 # Find all expectations that did not match. 283 # Find all expectations that did not match.
271 expected_diff = differ.GenerateDiffDictFromStrings( 284 expected_diff = differ.GenerateDiffDictFromStrings(
272 expected_contents, 285 expected_contents,
273 updated_contents) 286 updated_contents)
274 287
288 # Generate a set of images that have already been rebaselined
289 # onto disk.
290 rebaselined_diff = differ.GenerateDiffDictFromStrings(
291 expected_contents,
292 commited_contents)
293
294 rebaselined_set = set(rebaselined_diff.keys())
295
275 # The name of the device corresponds to the name of the folder 296 # The name of the device corresponds to the name of the folder
276 # we are in. 297 # we are in.
277 device_name = os.path.basename(root) 298 device_name = os.path.basename(root)
278 299
279 # Store old and new versions of the expectation for each GM 300 # Store old and new versions of the expectation for each GM
280 for image_name, hashes in expected_diff.iteritems(): 301 for image_name, hashes in expected_diff.iteritems():
281 self._expectations.append( 302 self._expectations.append(
282 GMInstance(device_name, image_name, 303 GMInstance(device_name, image_name,
283 hashes['old'], hashes['new'])) 304 hashes['old'], hashes['new'],
305 image_name in rebaselined_set))
306
307 def _load_skpdiff_output(self):
308 """Loads the results of skpdiff and annotates them with whether they
309 have already been rebaselined or not. The resulting data is store in
310 self.skpdiff_records."""
311 self.skpdiff_records = None
312 with open(self._skpdiff_output_path, 'rb') as skpdiff_output_file:
313 self.skpdiff_records = json.load(skpdiff_output_file)['records']
314 for record in self.skpdiff_records:
315 record['isRebaselined'] = self.image_map[record['baselinePath']] [1].is_rebaselined
284 316
285 317
286 def _download_expectation_images(self, expected_image_dir, actual_image_dir) : 318 def _download_expectation_images(self, expected_image_dir, actual_image_dir) :
287 """Download the expected and actual images for the _expectations array. 319 """Download the expected and actual images for the _expectations array.
288 320
289 @param expected_image_dir The directory to download expected images 321 @param expected_image_dir The directory to download expected images
290 into. 322 into.
291 @param actual_image_dir The directory to download actual images into. 323 @param actual_image_dir The directory to download actual images into.
292 """ 324 """
293 image_map = {} 325 image_map = {}
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
339 json_path = os.path.join(self._expectations_dir, device_name, 371 json_path = os.path.join(self._expectations_dir, device_name,
340 self._expected_name) 372 self._expected_name)
341 expectations = gm_json.LoadFromFile(json_path) 373 expectations = gm_json.LoadFromFile(json_path)
342 374
343 # Set the specified hash. 375 # Set the specified hash.
344 set_expected_hash_in_json(expectations, image_name, hash_value) 376 set_expected_hash_in_json(expectations, image_name, hash_value)
345 377
346 # Write it out to disk using gm_json to keep the formatting consistent. 378 # Write it out to disk using gm_json to keep the formatting consistent.
347 gm_json.WriteToFile(expectations, json_path) 379 gm_json.WriteToFile(expectations, json_path)
348 380
349 def use_hash_of(self, image_path): 381 def commit_rebaselines(self, rebaselines):
350 """Determine the hash of the image at the path using the records, and 382 """Resets all expectations to their defaults and uses the hashes for the
351 set it as the expected hash for its device and image config. 383 images in the given list.
352 384
353 @param image_path The path of the image as it was stored in the output 385 @param rebaselines A list of image paths to use the hash of.
354 of skpdiff_path
355 """ 386 """
387 # Reset all expectations to their old hashes
epoger 2013/08/07 16:51:55 I don't understand why you have to reset all the e
388 for expectation in self._expectations:
389 expectation.is_rebaselined = False
390 self._set_expected_hash(expectation.device_name,
391 expectation.image_name,
392 expectation.expected_hash)
356 393
357 # Get the metadata about the image at the path. 394 # Take all the images to rebaseline
358 is_actual, expectation = self.image_map[image_path] 395 for image_path in rebaselines:
396 # Get the metadata about the image at the path.
397 is_actual, expectation = self.image_map[image_path]
359 398
360 expectation_hash = expectation.actual_hash if is_actual else\ 399 expectation.is_rebaselined = is_actual
361 expectation.expected_hash 400 expectation_hash = expectation.actual_hash if is_actual else\
401 expectation.expected_hash
362 402
363 # Write out that image's hash directly to the expected results file. 403 # Write out that image's hash directly to the expected results file.
364 self._set_expected_hash(expectation.device_name, expectation.image_name, 404 self._set_expected_hash(expectation.device_name,
365 expectation_hash) 405 expectation.image_name,
406 expectation_hash)
407
408 self._load_skpdiff_output()
366 409
367 410
368 class SkPDiffHandler(BaseHTTPServer.BaseHTTPRequestHandler): 411 class SkPDiffHandler(BaseHTTPServer.BaseHTTPRequestHandler):
369 def send_file(self, file_path): 412 def send_file(self, file_path):
370 # Grab the extension if there is one 413 # Grab the extension if there is one
371 extension = os.path.splitext(file_path)[1] 414 extension = os.path.splitext(file_path)[1]
372 if len(extension) >= 1: 415 if len(extension) >= 1:
373 extension = extension[1:] 416 extension = extension[1:]
374 417
375 # Determine the MIME type of the file from its extension 418 # Determine the MIME type of the file from its extension
(...skipping 27 matching lines...) Expand all
403 446
404 # The [1:] chops off the leading '/' 447 # The [1:] chops off the leading '/'
405 file_path = self.path[1:] 448 file_path = self.path[1:]
406 449
407 # Handle skpdiff_output.json manually because it is was processed by the 450 # Handle skpdiff_output.json manually because it is was processed by the
408 # server when it was started and does not exist as a file. 451 # server when it was started and does not exist as a file.
409 if file_path == 'skpdiff_output.json': 452 if file_path == 'skpdiff_output.json':
410 self.send_response(200) 453 self.send_response(200)
411 self.send_header('Content-type', MIME_TYPE_MAP['json']) 454 self.send_header('Content-type', MIME_TYPE_MAP['json'])
412 self.end_headers() 455 self.end_headers()
413 self.wfile.write(self.server.skpdiff_output_json) 456
457 # Add JSONP padding to the JSON because the web page expects it. It
458 # expects it because it was designed to run with or without a web
459 # server. Without a web server, the only way to load JSON is with
460 # JSONP.
461 skpdiff_records = self.server.expectations_manager.skpdiff_records
462 self.wfile.write('var SkPDiffRecords = ')
463 json.dump({'records': skpdiff_records}, self.wfile)
464 self.wfile.write(';')
414 return 465 return
415 466
416 # Attempt to send static asset files first. 467 # Attempt to send static asset files first.
417 if self.serve_if_in_dir(SCRIPT_DIR, file_path): 468 if self.serve_if_in_dir(SCRIPT_DIR, file_path):
418 return 469 return
419 470
420 # WARNING: Serving any file the user wants is incredibly insecure. Its 471 # WARNING: Serving any file the user wants is incredibly insecure. Its
421 # redeeming quality is that we only serve gm files on a white list. 472 # redeeming quality is that we only serve gm files on a white list.
422 if self.path in self.server.image_set: 473 if self.path in self.server.image_set:
423 self.send_file(self.path) 474 self.send_file(self.path)
424 return 475 return
425 476
426 # If no file to send was found, just give the standard 404 477 # If no file to send was found, just give the standard 404
427 self.send_error(404) 478 self.send_error(404)
428 479
429 def do_POST(self): 480 def do_POST(self):
430 if self.path == '/set_hash': 481 if self.path == '/commit_rebaselines':
431 content_length = int(self.headers['Content-length']) 482 content_length = int(self.headers['Content-length'])
432 request_data = json.loads(self.rfile.read(content_length)) 483 request_data = json.loads(self.rfile.read(content_length))
433 self.server.expectations_manager.use_hash_of(request_data['path']) 484 rebaselines = request_data['rebaselines']
485 self.server.expectations_manager.commit_rebaselines(rebaselines)
434 self.send_response(200) 486 self.send_response(200)
435 self.send_header('Content-type', 'application/json') 487 self.send_header('Content-type', 'application/json')
436 self.end_headers() 488 self.end_headers()
437 self.wfile.write('{"success":true}') 489 self.wfile.write('{"success":true}')
438 return 490 return
439 491
440 # If the we have no handler for this path, give em' the 404 492 # If the we have no handler for this path, give em' the 404
441 self.send_error(404) 493 self.send_error(404)
442 494
443 495
444 def run_server(expectations_manager, port=8080): 496 def run_server(expectations_manager, port=8080):
445 # Preload the skpdiff results file. This is so we can perform some
446 # processing on it.
447 skpdiff_output_json = ''
448 with open(expectations_manager.skpdiff_output_path, 'rb') as records_file:
449 skpdiff_output_json = records_file.read()
450
451 # It's important to parse the results file so that we can make a set of 497 # It's important to parse the results file so that we can make a set of
452 # images that the web page might request. 498 # images that the web page might request.
453 skpdiff_records = json.loads(skpdiff_output_json)['records'] 499 skpdiff_records = expectations_manager.skpdiff_records
454 image_set = get_image_set_from_skpdiff(skpdiff_records) 500 image_set = get_image_set_from_skpdiff(skpdiff_records)
455 501
456 # Add JSONP padding to the JSON because the web page expects it. It expects
457 # it because it was designed to run with or without a web server. Without a
458 # web server, the only way to load JSON is with JSONP.
459 skpdiff_output_json = ('var SkPDiffRecords = ' +
460 json.dumps({'records': skpdiff_records}) + ';')
461
462 # Do not bind to interfaces other than localhost because the server will 502 # Do not bind to interfaces other than localhost because the server will
463 # attempt to serve files relative to the root directory as a last resort 503 # attempt to serve files relative to the root directory as a last resort
464 # before 404ing. This means all of your files can be accessed from this 504 # before 404ing. This means all of your files can be accessed from this
465 # server, so DO NOT let this server listen to anything but localhost. 505 # server, so DO NOT let this server listen to anything but localhost.
466 server_address = ('127.0.0.1', port) 506 server_address = ('127.0.0.1', port)
467 http_server = BaseHTTPServer.HTTPServer(server_address, SkPDiffHandler) 507 http_server = BaseHTTPServer.HTTPServer(server_address, SkPDiffHandler)
468 http_server.image_set = image_set 508 http_server.image_set = image_set
469 http_server.skpdiff_output_json = skpdiff_output_json
470 http_server.expectations_manager = expectations_manager 509 http_server.expectations_manager = expectations_manager
471 print('Navigate thine browser to: http://{}:{}/'.format(*server_address)) 510 print('Navigate thine browser to: http://{}:{}/'.format(*server_address))
472 http_server.serve_forever() 511 http_server.serve_forever()
473 512
474 513
475 def main(): 514 def main():
476 parser = argparse.ArgumentParser() 515 parser = argparse.ArgumentParser()
477 parser.add_argument('--port', '-p', metavar='PORT', 516 parser.add_argument('--port', '-p', metavar='PORT',
478 type=int, 517 type=int,
479 default=8080, 518 default=8080,
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
523 562
524 expectations_manager = ExpectationsManager(args['expectations_dir'], 563 expectations_manager = ExpectationsManager(args['expectations_dir'],
525 args['expected'], 564 args['expected'],
526 args['updated'], 565 args['updated'],
527 skpdiff_path) 566 skpdiff_path)
528 567
529 run_server(expectations_manager, port=args['port']) 568 run_server(expectations_manager, port=args['port'])
530 569
531 if __name__ == '__main__': 570 if __name__ == '__main__':
532 main() 571 main()
OLDNEW
« no previous file with comments | « tools/skpdiff/diff_viewer.js ('k') | tools/skpdiff/viewer.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698