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 ''' | |
9 | 8 |
10 ''' | |
11 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. |
12 ''' | 10 """ |
13 | 11 |
14 # System-level imports | 12 # System-level imports |
15 import fnmatch | 13 import fnmatch |
16 import json | 14 import json |
17 import os | 15 import os |
18 import re | 16 import re |
19 import sys | 17 import sys |
20 | 18 |
21 # Imports from within Skia | 19 # Imports from within Skia |
22 # | 20 # |
(...skipping 11 matching lines...) Expand all Loading... |
34 CATEGORIES_TO_SUMMARIZE = [ | 32 CATEGORIES_TO_SUMMARIZE = [ |
35 'builder', 'test', 'config', 'resultType', | 33 'builder', 'test', 'config', 'resultType', |
36 ] | 34 ] |
37 | 35 |
38 class Results(object): | 36 class Results(object): |
39 """ Loads actual and expected results from all builders, supplying combined | 37 """ Loads actual and expected results from all builders, supplying combined |
40 reports as requested. """ | 38 reports as requested. """ |
41 | 39 |
42 def __init__(self, actuals_root, expected_root): | 40 def __init__(self, actuals_root, expected_root): |
43 """ | 41 """ |
44 params: | 42 Args: |
45 actuals_root: root directory containing all actual-results.json files | 43 actuals_root: root directory containing all actual-results.json files |
46 expected_root: root directory containing all expected-results.json files | 44 expected_root: root directory containing all expected-results.json files |
47 """ | 45 """ |
48 self._actual_builder_dicts = Results._GetDictsFromRoot(actuals_root) | 46 self._actual_builder_dicts = Results._GetDictsFromRoot(actuals_root) |
49 self._expected_builder_dicts = Results._GetDictsFromRoot(expected_root) | 47 self._expected_builder_dicts = Results._GetDictsFromRoot(expected_root) |
50 self._all_results = Results._Combine( | 48 self._all_results = Results._Combine( |
51 actual_builder_dicts=self._actual_builder_dicts, | 49 actual_builder_dicts=self._actual_builder_dicts, |
52 expected_builder_dicts=self._expected_builder_dicts) | 50 expected_builder_dicts=self._expected_builder_dicts) |
53 | 51 |
54 def GetAll(self): | 52 def GetAll(self): |
55 """Return results of all tests, as a dictionary in this form: | 53 """Return results of all tests, as a dictionary in this form: |
56 | 54 |
57 { | 55 { |
58 "categories": # dictionary of categories listed in | 56 'categories': # dictionary of categories listed in |
59 # CATEGORIES_TO_SUMMARIZE, with the number of times | 57 # CATEGORIES_TO_SUMMARIZE, with the number of times |
60 # each value appears within its category | 58 # each value appears within its category |
61 { | 59 { |
62 "resultType": # category name | 60 'resultType': # category name |
63 { | 61 { |
64 "failed": 29, # category value and total number found of that value | 62 'failed': 29, # category value and total number found of that value |
65 "failure-ignored": 948, | 63 'failure-ignored': 948, |
66 "no-comparison": 4502, | 64 'no-comparison': 4502, |
67 "succeeded": 38609, | 65 'succeeded': 38609, |
68 }, | 66 }, |
69 "builder": | 67 'builder': |
70 { | 68 { |
71 "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug": 1286, | 69 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug': 1286, |
72 "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Release": 1134, | 70 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Release': 1134, |
73 ... | 71 ... |
74 }, | 72 }, |
75 ... # other categories from CATEGORIES_TO_SUMMARIZE | 73 ... # other categories from CATEGORIES_TO_SUMMARIZE |
76 }, # end of "categories" dictionary | 74 }, # end of 'categories' dictionary |
77 | 75 |
78 "testData": # list of test results, with a dictionary for each | 76 'testData': # list of test results, with a dictionary for each |
79 [ | 77 [ |
80 { | 78 { |
81 "builder": "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug", | 79 'index': 0, # index of this result within testData list |
82 "test": "bigmatrix", | 80 'builder': 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug', |
83 "config": "8888", | 81 'test': 'bigmatrix', |
84 "resultType": "failed", | 82 'config': '8888', |
85 "expectedHashType": "bitmap-64bitMD5", | 83 'resultType': 'failed', |
86 "expectedHashDigest": "10894408024079689926", | 84 'expectedHashType': 'bitmap-64bitMD5', |
87 "actualHashType": "bitmap-64bitMD5", | 85 'expectedHashDigest': '10894408024079689926', |
88 "actualHashDigest": "2409857384569", | 86 'actualHashType': 'bitmap-64bitMD5', |
| 87 'actualHashDigest': '2409857384569', |
89 }, | 88 }, |
90 ... | 89 ... |
91 ], # end of "testData" list | 90 ], # end of 'testData' list |
92 } | 91 } |
93 """ | 92 """ |
94 return self._all_results | 93 return self._all_results |
95 | 94 |
96 @staticmethod | 95 @staticmethod |
97 def _GetDictsFromRoot(root, pattern='*.json'): | 96 def _GetDictsFromRoot(root, pattern='*.json'): |
98 """Read all JSON dictionaries within a directory tree, returning them within | 97 """Read all JSON dictionaries within a directory tree. |
99 a meta-dictionary (keyed by the builder name for each dictionary). | |
100 | 98 |
101 params: | 99 Args: |
102 root: path to root of directory tree | 100 root: path to root of directory tree |
103 pattern: which files to read within root (fnmatch-style pattern) | 101 pattern: which files to read within root (fnmatch-style pattern) |
| 102 |
| 103 Returns: |
| 104 A meta-dictionary containing all the JSON dictionaries found within |
| 105 the directory tree, keyed by the builder name of each dictionary. |
104 """ | 106 """ |
105 meta_dict = {} | 107 meta_dict = {} |
106 for dirpath, dirnames, filenames in os.walk(root): | 108 for dirpath, dirnames, filenames in os.walk(root): |
107 for matching_filename in fnmatch.filter(filenames, pattern): | 109 for matching_filename in fnmatch.filter(filenames, pattern): |
108 builder = os.path.basename(dirpath) | 110 builder = os.path.basename(dirpath) |
109 if builder.endswith('-Trybot'): | 111 if builder.endswith('-Trybot'): |
110 continue | 112 continue |
111 fullpath = os.path.join(dirpath, matching_filename) | 113 fullpath = os.path.join(dirpath, matching_filename) |
112 meta_dict[builder] = gm_json.LoadFromFile(fullpath) | 114 meta_dict[builder] = gm_json.LoadFromFile(fullpath) |
113 return meta_dict | 115 return meta_dict |
114 | 116 |
115 @staticmethod | 117 @staticmethod |
116 def _Combine(actual_builder_dicts, expected_builder_dicts): | 118 def _Combine(actual_builder_dicts, expected_builder_dicts): |
117 """Gathers the results of all tests, across all builders (based on the | 119 """Gathers the results of all tests, across all builders (based on the |
118 contents of actual_builder_dicts and expected_builder_dicts) | 120 contents of actual_builder_dicts and expected_builder_dicts). |
119 and returns it in a list in the same form needed for self.GetAll(). | |
120 | 121 |
121 This is a static method, because once we start refreshing results | 122 This is a static method, because once we start refreshing results |
122 asynchronously, we need to make sure we are not corrupting the object's | 123 asynchronously, we need to make sure we are not corrupting the object's |
123 member variables. | 124 member variables. |
| 125 |
| 126 Args: |
| 127 actual_builder_dicts: a meta-dictionary of all actual JSON results, |
| 128 as returned by _GetDictsFromRoot(). |
| 129 actual_builder_dicts: a meta-dictionary of all expected JSON results, |
| 130 as returned by _GetDictsFromRoot(). |
| 131 |
| 132 Returns: |
| 133 A list of all the results of all tests, in the same form returned by |
| 134 self.GetAll(). |
124 """ | 135 """ |
125 test_data = [] | 136 test_data = [] |
126 category_dict = {} | 137 category_dict = {} |
127 Results._EnsureIncludedInCategoryDict(category_dict, 'resultType', [ | 138 Results._EnsureIncludedInCategoryDict(category_dict, 'resultType', [ |
128 gm_json.JSONKEY_ACTUALRESULTS_FAILED, | 139 gm_json.JSONKEY_ACTUALRESULTS_FAILED, |
129 gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED, | 140 gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED, |
130 gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON, | 141 gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON, |
131 gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED, | 142 gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED, |
132 ]) | 143 ]) |
133 | 144 |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
176 gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON, | 187 gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON, |
177 gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED] : | 188 gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED] : |
178 print 'WARNING: No expectations found for test: %s' % { | 189 print 'WARNING: No expectations found for test: %s' % { |
179 'builder': builder, | 190 'builder': builder, |
180 'image_name': image_name, | 191 'image_name': image_name, |
181 'result_type': result_type, | 192 'result_type': result_type, |
182 } | 193 } |
183 expected_image = [None, None] | 194 expected_image = [None, None] |
184 | 195 |
185 # If this test was recently rebaselined, it will remain in | 196 # If this test was recently rebaselined, it will remain in |
186 # the "failed" set of actuals until all the bots have | 197 # the 'failed' set of actuals until all the bots have |
187 # cycled (although the expectations have indeed been set | 198 # cycled (although the expectations have indeed been set |
188 # from the most recent actuals). Treat these as successes | 199 # from the most recent actuals). Treat these as successes |
189 # instead of failures. | 200 # instead of failures. |
190 # | 201 # |
191 # TODO(epoger): Do we need to do something similar in | 202 # TODO(epoger): Do we need to do something similar in |
192 # other cases, such as when we have recently marked a test | 203 # other cases, such as when we have recently marked a test |
193 # as ignoreFailure but it still shows up in the "failed" | 204 # as ignoreFailure but it still shows up in the 'failed' |
194 # category? Maybe we should not rely on the result_type | 205 # category? Maybe we should not rely on the result_type |
195 # categories recorded within the gm_actuals AT ALL, and | 206 # categories recorded within the gm_actuals AT ALL, and |
196 # instead evaluate the result_type ourselves based on what | 207 # instead evaluate the result_type ourselves based on what |
197 # we see in expectations vs actual checksum? | 208 # we see in expectations vs actual checksum? |
198 if expected_image == actual_image: | 209 if expected_image == actual_image: |
199 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED | 210 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED |
200 else: | 211 else: |
201 updated_result_type = result_type | 212 updated_result_type = result_type |
202 | 213 |
203 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups() | 214 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups() |
204 results_for_this_test = { | 215 results_for_this_test = { |
205 "builder": builder, | 216 'index': len(test_data), |
206 "test": test, | 217 'builder': builder, |
207 "config": config, | 218 'test': test, |
208 "resultType": updated_result_type, | 219 'config': config, |
209 "actualHashType": actual_image[0], | 220 'resultType': updated_result_type, |
210 "actualHashDigest": str(actual_image[1]), | 221 'actualHashType': actual_image[0], |
211 "expectedHashType": expected_image[0], | 222 'actualHashDigest': str(actual_image[1]), |
212 "expectedHashDigest": str(expected_image[1]), | 223 'expectedHashType': expected_image[0], |
| 224 'expectedHashDigest': str(expected_image[1]), |
213 } | 225 } |
214 Results._AddToCategoryDict(category_dict, results_for_this_test) | 226 Results._AddToCategoryDict(category_dict, results_for_this_test) |
215 test_data.append(results_for_this_test) | 227 test_data.append(results_for_this_test) |
216 return {"categories": category_dict, "testData": test_data} | 228 return {'categories': category_dict, 'testData': test_data} |
217 | 229 |
218 @staticmethod | 230 @staticmethod |
219 def _AddToCategoryDict(category_dict, test_results): | 231 def _AddToCategoryDict(category_dict, test_results): |
220 """Add test_results to the category dictionary we are building. | 232 """Add test_results to the category dictionary we are building. |
221 (See documentation of self.GetAll() for the format of this dictionary.) | 233 (See documentation of self.GetAll() for the format of this dictionary.) |
222 | 234 |
223 params: | 235 Args: |
224 category_dict: category dict-of-dicts to add to; modify this in-place | 236 category_dict: category dict-of-dicts to add to; modify this in-place |
225 test_results: test data with which to update category_list, in a dict: | 237 test_results: test data with which to update category_list, in a dict: |
226 { | 238 { |
227 "category_name": "category_value", | 239 'category_name': 'category_value', |
228 "category_name": "category_value", | 240 'category_name': 'category_value', |
229 ... | 241 ... |
230 } | 242 } |
231 """ | 243 """ |
232 for category in CATEGORIES_TO_SUMMARIZE: | 244 for category in CATEGORIES_TO_SUMMARIZE: |
233 category_value = test_results.get(category) | 245 category_value = test_results.get(category) |
234 if not category_value: | 246 if not category_value: |
235 continue # test_results did not include this category, keep going | 247 continue # test_results did not include this category, keep going |
236 if not category_dict.get(category): | 248 if not category_dict.get(category): |
237 category_dict[category] = {} | 249 category_dict[category] = {} |
238 if not category_dict[category].get(category_value): | 250 if not category_dict[category].get(category_value): |
239 category_dict[category][category_value] = 0 | 251 category_dict[category][category_value] = 0 |
240 category_dict[category][category_value] += 1 | 252 category_dict[category][category_value] += 1 |
241 | 253 |
242 @staticmethod | 254 @staticmethod |
243 def _EnsureIncludedInCategoryDict(category_dict, | 255 def _EnsureIncludedInCategoryDict(category_dict, |
244 category_name, category_values): | 256 category_name, category_values): |
245 """Ensure that the category name/value pairs are included in category_dict, | 257 """Ensure that the category name/value pairs are included in category_dict, |
246 even if there aren't any results with that name/value pair. | 258 even if there aren't any results with that name/value pair. |
247 (See documentation of self.GetAll() for the format of this dictionary.) | 259 (See documentation of self.GetAll() for the format of this dictionary.) |
248 | 260 |
249 params: | 261 Args: |
250 category_dict: category dict-of-dicts to modify | 262 category_dict: category dict-of-dicts to modify |
251 category_name: category name, as a string | 263 category_name: category name, as a string |
252 category_values: list of values we want to make sure are represented | 264 category_values: list of values we want to make sure are represented |
253 for this category | 265 for this category |
254 """ | 266 """ |
255 if not category_dict.get(category_name): | 267 if not category_dict.get(category_name): |
256 category_dict[category_name] = {} | 268 category_dict[category_name] = {} |
257 for category_value in category_values: | 269 for category_value in category_values: |
258 if not category_dict[category_name].get(category_value): | 270 if not category_dict[category_name].get(category_value): |
259 category_dict[category_name][category_value] = 0 | 271 category_dict[category_name][category_value] = 0 |
OLD | NEW |