OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 |
| 3 ''' |
| 4 Copyright 2013 Google Inc. |
| 5 |
| 6 Use of this source code is governed by a BSD-style license that can be |
| 7 found in the LICENSE file. |
| 8 ''' |
| 9 |
| 10 ''' |
| 11 Gathers diffs between 2 JSON expectations files, or between actual and |
| 12 expected results within a single JSON actual-results file, |
| 13 and generates an old-vs-new diff dictionary. |
| 14 ''' |
| 15 |
| 16 # System-level imports |
| 17 import argparse |
| 18 import json |
| 19 import os |
| 20 import sys |
| 21 import urllib2 |
| 22 |
| 23 # Imports from within Skia |
| 24 # |
| 25 # We need to add the 'gm' directory, so that we can import gm_json.py within |
| 26 # that directory. That script allows us to parse the actual-results.json file |
| 27 # written out by the GM tool. |
| 28 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* |
| 29 # so any dirs that are already in the PYTHONPATH will be preferred. |
| 30 # |
| 31 # This assumes that the 'gm' directory has been checked out as a sibling of |
| 32 # the 'tools' directory containing this script, which will be the case if |
| 33 # 'trunk' was checked out as a single unit. |
| 34 GM_DIRECTORY = os.path.realpath( |
| 35 os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm')) |
| 36 if GM_DIRECTORY not in sys.path: |
| 37 sys.path.append(GM_DIRECTORY) |
| 38 import gm_json |
| 39 |
| 40 |
| 41 # Object that generates diffs between two JSON gm result files. |
| 42 class GMDiffer(object): |
| 43 |
| 44 def __init__(self): |
| 45 pass |
| 46 |
| 47 def _GetFileContentsAsString(self, filepath): |
| 48 """Returns the full contents of a file, as a single string. |
| 49 If the filename looks like a URL, download its contents...""" |
| 50 if filepath.startswith('http:') or filepath.startswith('https:'): |
| 51 return urllib2.urlopen(filepath).read() |
| 52 else: |
| 53 return open(filepath, 'r').read() |
| 54 |
| 55 def _GetExpectedResults(self, filepath): |
| 56 """Returns the dictionary of expected results from a JSON file, |
| 57 in this form: |
| 58 |
| 59 { |
| 60 'test1' : 14760033689012826769, |
| 61 'test2' : 9151974350149210736, |
| 62 ... |
| 63 } |
| 64 |
| 65 We make these simplifying assumptions: |
| 66 1. Each test has either 0 or 1 allowed results. |
| 67 2. All expectations are of type JSONKEY_HASHTYPE_BITMAP_64BITMD5. |
| 68 |
| 69 Any tests which violate those assumptions will cause an exception to |
| 70 be raised. |
| 71 |
| 72 Any tests for which we have no expectations will be left out of the |
| 73 returned dictionary. |
| 74 """ |
| 75 result_dict = {} |
| 76 contents = self._GetFileContentsAsString(filepath) |
| 77 json_dict = gm_json.LoadFromString(contents) |
| 78 all_expectations = json_dict[gm_json.JSONKEY_EXPECTEDRESULTS] |
| 79 for test_name in all_expectations.keys(): |
| 80 test_expectations = all_expectations[test_name] |
| 81 allowed_digests = test_expectations[ |
| 82 gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS] |
| 83 if allowed_digests: |
| 84 num_allowed_digests = len(allowed_digests) |
| 85 if num_allowed_digests > 1: |
| 86 raise ValueError( |
| 87 'test %s in file %s has %d allowed digests' % ( |
| 88 test_name, filepath, num_allowed_digests)) |
| 89 digest_pair = allowed_digests[0] |
| 90 if digest_pair[0] != gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5: |
| 91 raise ValueError( |
| 92 'test %s in file %s has unsupported hashtype %s' % ( |
| 93 test_name, filepath, digest_pair[0])) |
| 94 result_dict[test_name] = digest_pair[1] |
| 95 return result_dict |
| 96 |
| 97 def _GetActualResults(self, filepath): |
| 98 """Returns the dictionary of actual results from a JSON file, |
| 99 in this form: |
| 100 |
| 101 { |
| 102 'test1' : 14760033689012826769, |
| 103 'test2' : 9151974350149210736, |
| 104 ... |
| 105 } |
| 106 |
| 107 We make these simplifying assumptions: |
| 108 1. All results are of type JSONKEY_HASHTYPE_BITMAP_64BITMD5. |
| 109 |
| 110 Any tests which violate those assumptions will cause an exception to |
| 111 be raised. |
| 112 |
| 113 Any tests for which we have no actual results will be left out of the |
| 114 returned dictionary. |
| 115 """ |
| 116 result_dict = {} |
| 117 contents = self._GetFileContentsAsString(filepath) |
| 118 json_dict = gm_json.LoadFromString(contents) |
| 119 all_result_types = json_dict[gm_json.JSONKEY_ACTUALRESULTS] |
| 120 for result_type in all_result_types.keys(): |
| 121 results_of_this_type = all_result_types[result_type] |
| 122 if results_of_this_type: |
| 123 for test_name in results_of_this_type.keys(): |
| 124 digest_pair = results_of_this_type[test_name] |
| 125 if digest_pair[0] != gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD
5: |
| 126 raise ValueError( |
| 127 'test %s in file %s has unsupported hashtype %s' % ( |
| 128 test_name, filepath, digest_pair[0])) |
| 129 result_dict[test_name] = digest_pair[1] |
| 130 return result_dict |
| 131 |
| 132 def _DictionaryDiff(self, old_dict, new_dict): |
| 133 """Generate a dictionary showing the diffs between old_dict and new_dict
. |
| 134 Any entries which are identical across them will be left out.""" |
| 135 diff_dict = {} |
| 136 all_keys = set(old_dict.keys() + new_dict.keys()) |
| 137 for key in all_keys: |
| 138 if old_dict.get(key) != new_dict.get(key): |
| 139 new_entry = {} |
| 140 new_entry['old'] = old_dict.get(key) |
| 141 new_entry['new'] = new_dict.get(key) |
| 142 diff_dict[key] = new_entry |
| 143 return diff_dict |
| 144 |
| 145 def GenerateDiffDict(self, oldfile, newfile=None): |
| 146 """Generate a dictionary showing the diffs: |
| 147 old = expectations within oldfile |
| 148 new = expectations within newfile |
| 149 |
| 150 If newfile is not specified, then 'new' is the actual results within |
| 151 oldfile. |
| 152 """ |
| 153 old_results = self._GetExpectedResults(oldfile) |
| 154 if newfile: |
| 155 new_results = self._GetExpectedResults(newfile) |
| 156 else: |
| 157 new_results = self._GetActualResults(oldfile) |
| 158 return self._DictionaryDiff(old_results, new_results) |
| 159 |
| 160 |
| 161 # main... |
| 162 parser = argparse.ArgumentParser() |
| 163 parser.add_argument('old', |
| 164 help='Path to JSON file whose expectations to display on ' + |
| 165 'the "old" side of the diff. This can be a filepath on ' + |
| 166 'local storage, or a URL.') |
| 167 parser.add_argument('new', nargs='?', |
| 168 help='Path to JSON file whose expectations to display on ' + |
| 169 'the "new" side of the diff; if not specified, uses the ' + |
| 170 'ACTUAL results from the "old" JSON file. This can be a ' + |
| 171 'filepath on local storage, or a URL.') |
| 172 args = parser.parse_args() |
| 173 differ = GMDiffer() |
| 174 diffs = differ.GenerateDiffDict(oldfile=args.old, newfile=args.new) |
| 175 json.dump(diffs, sys.stdout, sort_keys=True, indent=2) |
OLD | NEW |