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

Side by Side Diff: gm/rebaseline_server/results.py

Issue 59283006: rebaseline_server: add pixel diffs, and sorting by diff metrics (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: improve_self_test Created 7 years, 1 month 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
OLDNEW
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 Repackage expected/actual GM results as needed by our HTML rebaseline viewer. 9 Repackage expected/actual GM results as needed by our HTML rebaseline viewer.
10 """ 10 """
(...skipping 11 matching lines...) Expand all
22 # 22 #
23 # We need to add the 'gm' directory, so that we can import gm_json.py within 23 # We need to add the 'gm' directory, so that we can import gm_json.py within
24 # that directory. That script allows us to parse the actual-results.json file 24 # that directory. That script allows us to parse the actual-results.json file
25 # written out by the GM tool. 25 # written out by the GM tool.
26 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* 26 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end*
27 # so any dirs that are already in the PYTHONPATH will be preferred. 27 # so any dirs that are already in the PYTHONPATH will be preferred.
28 GM_DIRECTORY = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 28 GM_DIRECTORY = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
29 if GM_DIRECTORY not in sys.path: 29 if GM_DIRECTORY not in sys.path:
30 sys.path.append(GM_DIRECTORY) 30 sys.path.append(GM_DIRECTORY)
31 import gm_json 31 import gm_json
32 import imagediffdb
32 33
33 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) 34 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
34 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config) 35 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config)
35 36
36 FIELDS_PASSED_THRU_VERBATIM = [ 37 FIELDS_PASSED_THRU_VERBATIM = [
37 gm_json.JSONKEY_EXPECTEDRESULTS_BUGS, 38 gm_json.JSONKEY_EXPECTEDRESULTS_BUGS,
38 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE, 39 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE,
39 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED, 40 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED,
40 ] 41 ]
41 CATEGORIES_TO_SUMMARIZE = [ 42 CATEGORIES_TO_SUMMARIZE = [
42 'builder', 'test', 'config', 'resultType', 43 'builder', 'test', 'config', 'resultType',
43 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE, 44 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE,
44 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED, 45 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED,
45 ] 46 ]
46 47
47 RESULTS_ALL = 'all' 48 RESULTS_ALL = 'all'
48 RESULTS_FAILURES = 'failures' 49 RESULTS_FAILURES = 'failures'
49 50
50 class Results(object): 51 class Results(object):
51 """ Loads actual and expected results from all builders, supplying combined 52 """ Loads actual and expected results from all builders, supplying combined
52 reports as requested. 53 reports as requested.
53 54
54 Once this object has been constructed, the results (in self._results[]) 55 Once this object has been constructed, the results (in self._results[])
55 are immutable. If you want to update the results based on updated JSON 56 are immutable. If you want to update the results based on updated JSON
56 file contents, you will need to create a new Results object.""" 57 file contents, you will need to create a new Results object."""
57 58
58 def __init__(self, actuals_root, expected_root): 59 def __init__(self, actuals_root, expected_root, generated_images_root):
59 """ 60 """
60 Args: 61 Args:
61 actuals_root: root directory containing all actual-results.json files 62 actuals_root: root directory containing all actual-results.json files
62 expected_root: root directory containing all expected-results.json files 63 expected_root: root directory containing all expected-results.json files
64 generated_images_root: directory within which to create all pixels diffs;
65 if this directory does not yet exist, it will be created
63 """ 66 """
67 self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root)
64 self._actuals_root = actuals_root 68 self._actuals_root = actuals_root
65 self._expected_root = expected_root 69 self._expected_root = expected_root
66 self._load_actual_and_expected() 70 self._load_actual_and_expected()
67 self._timestamp = int(time.time()) 71 self._timestamp = int(time.time())
68 72
69 def get_timestamp(self): 73 def get_timestamp(self):
70 """Return the time at which this object was created, in seconds past epoch 74 """Return the time at which this object was created, in seconds past epoch
71 (UTC). 75 (UTC).
72 """ 76 """
73 return self._timestamp 77 return self._timestamp
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after
242 # Check: did we write out the set of per-builder dictionaries we 246 # Check: did we write out the set of per-builder dictionaries we
243 # expected to? 247 # expected to?
244 expected_builders_written = sorted(meta_dict.keys()) 248 expected_builders_written = sorted(meta_dict.keys())
245 actual_builders_written.sort() 249 actual_builders_written.sort()
246 if expected_builders_written != actual_builders_written: 250 if expected_builders_written != actual_builders_written:
247 raise KeyError( 251 raise KeyError(
248 'expected to write dicts for builders %s, but actually wrote them ' 252 'expected to write dicts for builders %s, but actually wrote them '
249 'for builders %s' % ( 253 'for builders %s' % (
250 expected_builders_written, actual_builders_written)) 254 expected_builders_written, actual_builders_written))
251 255
256 def _generate_pixel_diffs_if_needed(self, test, expected_image, actual_image):
257 """If expected_image and actual_image both exist but are different,
258 add the image pair to self._image_diff_db and generate pixel diffs.
259
260 Args:
261 test: string; name of test
262 expected_image: (hashType, hashDigest) tuple describing the expected image
263 actual_image: (hashType, hashDigest) tuple describing the actual image
264 """
265 if expected_image == actual_image:
266 return
267
268 (expected_hashtype, expected_hashdigest) = expected_image
269 (actual_hashtype, actual_hashdigest) = actual_image
270 if ((not expected_hashtype) or (not expected_hashdigest) or
jcgregorio 2013/11/06 18:47:28 are you checking for None's? if None in [expected
epoger 2013/11/07 21:11:53 Done.
271 (not actual_hashtype) or (not actual_hashdigest)):
272 return
273
274 expected_url = gm_json.CreateGmActualUrl(
275 test_name=test, hash_type=expected_hashtype,
276 hash_digest=expected_hashdigest)
277 actual_url = gm_json.CreateGmActualUrl(
278 test_name=test, hash_type=actual_hashtype,
279 hash_digest=actual_hashdigest)
280 self._image_diff_db.add_image_pair(
281 expected_image_locator=expected_hashdigest,
282 expected_image_url=expected_url,
283 actual_image_locator=actual_hashdigest,
284 actual_image_url=actual_url)
285
252 def _load_actual_and_expected(self): 286 def _load_actual_and_expected(self):
253 """Loads the results of all tests, across all builders (based on the 287 """Loads the results of all tests, across all builders (based on the
254 files within self._actuals_root and self._expected_root), 288 files within self._actuals_root and self._expected_root),
255 and stores them in self._results. 289 and stores them in self._results.
256 """ 290 """
257 actual_builder_dicts = Results._read_dicts_from_root(self._actuals_root) 291 actual_builder_dicts = Results._read_dicts_from_root(self._actuals_root)
258 expected_builder_dicts = Results._read_dicts_from_root(self._expected_root) 292 expected_builder_dicts = Results._read_dicts_from_root(self._expected_root)
259 293
260 categories_all = {} 294 categories_all = {}
261 categories_failures = {} 295 categories_failures = {}
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
336 # category? Maybe we should not rely on the result_type 370 # category? Maybe we should not rely on the result_type
337 # categories recorded within the gm_actuals AT ALL, and 371 # categories recorded within the gm_actuals AT ALL, and
338 # instead evaluate the result_type ourselves based on what 372 # instead evaluate the result_type ourselves based on what
339 # we see in expectations vs actual checksum? 373 # we see in expectations vs actual checksum?
340 if expected_image == actual_image: 374 if expected_image == actual_image:
341 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED 375 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED
342 else: 376 else:
343 updated_result_type = result_type 377 updated_result_type = result_type
344 378
345 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups() 379 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups()
380 self._generate_pixel_diffs_if_needed(
381 test=test, expected_image=expected_image,
jcgregorio 2013/11/06 18:47:28 self._generate_diffs_if_needed(test, expected_imag
epoger 2013/11/07 21:11:53 I've become a fan of named arguments (I like that
jcgregorio 2013/11/08 02:02:29 NP, if you're willing to do the typing :-) On 201
382 actual_image=actual_image)
346 results_for_this_test = { 383 results_for_this_test = {
347 'resultType': updated_result_type, 384 'resultType': updated_result_type,
348 'builder': builder, 385 'builder': builder,
349 'test': test, 386 'test': test,
350 'config': config, 387 'config': config,
351 'actualHashType': actual_image[0], 388 'actualHashType': actual_image[0],
352 'actualHashDigest': str(actual_image[1]), 389 'actualHashDigest': str(actual_image[1]),
353 'expectedHashType': expected_image[0], 390 'expectedHashType': expected_image[0],
354 'expectedHashDigest': str(expected_image[1]), 391 'expectedHashDigest': str(expected_image[1]),
355 392
356 # FIELDS_PASSED_THRU_VERBATIM that may be overwritten below... 393 # FIELDS_PASSED_THRU_VERBATIM that may be overwritten below...
357 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE: False, 394 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE: False,
358 } 395 }
359 if expectations_per_test: 396 if expectations_per_test:
360 for field in FIELDS_PASSED_THRU_VERBATIM: 397 for field in FIELDS_PASSED_THRU_VERBATIM:
361 results_for_this_test[field] = expectations_per_test.get(field) 398 results_for_this_test[field] = expectations_per_test.get(field)
399
400 try:
401 diff_record = self._image_diff_db.get_diff_record(
402 expected_image_locator=expected_image[1],
403 actual_image_locator=actual_image[1])
404 results_for_this_test['percentDifferingPixels'] = (
405 diff_record.get_percent_pixels_differing())
406 results_for_this_test['weightedDiffMeasure'] = (
407 diff_record.get_weighted_diff_measure())
408 except KeyError:
jcgregorio 2013/11/06 18:47:28 log the error?
epoger 2013/11/07 21:11:53 Done.
409 pass
410
362 Results._add_to_category_dict(categories_all, results_for_this_test) 411 Results._add_to_category_dict(categories_all, results_for_this_test)
363 data_all.append(results_for_this_test) 412 data_all.append(results_for_this_test)
364 413
365 # TODO(epoger): In effect, we have a list of resultTypes that we 414 # TODO(epoger): In effect, we have a list of resultTypes that we
366 # include in the different result lists (data_all and data_failures). 415 # include in the different result lists (data_all and data_failures).
367 # This same list should be used by the calls to 416 # This same list should be used by the calls to
368 # Results._ensure_included_in_category_dict() earlier on. 417 # Results._ensure_included_in_category_dict() earlier on.
369 if updated_result_type != gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED: 418 if updated_result_type != gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED:
370 Results._add_to_category_dict(categories_failures, 419 Results._add_to_category_dict(categories_failures,
371 results_for_this_test) 420 results_for_this_test)
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
413 category_dict: category dict-of-dicts to modify 462 category_dict: category dict-of-dicts to modify
414 category_name: category name, as a string 463 category_name: category name, as a string
415 category_values: list of values we want to make sure are represented 464 category_values: list of values we want to make sure are represented
416 for this category 465 for this category
417 """ 466 """
418 if not category_dict.get(category_name): 467 if not category_dict.get(category_name):
419 category_dict[category_name] = {} 468 category_dict[category_name] = {}
420 for category_value in category_values: 469 for category_value in category_values:
421 if not category_dict[category_name].get(category_value): 470 if not category_dict[category_name].get(category_value):
422 category_dict[category_name][category_value] = 0 471 category_dict[category_name][category_value] = 0
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698