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 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 |
| OLD | NEW |