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 |