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

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

Issue 26891003: rebaseline_server: allow client to pull all results, or just failures (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: fix_comment Created 7 years, 2 months 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
« no previous file with comments | « no previous file | gm/rebaseline_server/server.py » ('j') | gm/rebaseline_server/server.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 14 matching lines...) Expand all
25 # so any dirs that are already in the PYTHONPATH will be preferred. 25 # so any dirs that are already in the PYTHONPATH will be preferred.
26 GM_DIRECTORY = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 26 GM_DIRECTORY = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
27 if GM_DIRECTORY not in sys.path: 27 if GM_DIRECTORY not in sys.path:
28 sys.path.append(GM_DIRECTORY) 28 sys.path.append(GM_DIRECTORY)
29 import gm_json 29 import gm_json
30 30
31 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) 31 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
32 CATEGORIES_TO_SUMMARIZE = [ 32 CATEGORIES_TO_SUMMARIZE = [
33 'builder', 'test', 'config', 'resultType', 33 'builder', 'test', 'config', 'resultType',
34 ] 34 ]
35 RESULTS_ALL = 'all'
36 RESULTS_FAILURES = 'failures'
35 37
36 class Results(object): 38 class Results(object):
37 """ Loads actual and expected results from all builders, supplying combined 39 """ Loads actual and expected results from all builders, supplying combined
38 reports as requested. """ 40 reports as requested.
41
42 Once this object has been constructed, the results are immutable. If you
43 want to update the results based on updated JSON file contents, you will
44 need to create a new Results object."""
39 45
40 def __init__(self, actuals_root, expected_root): 46 def __init__(self, actuals_root, expected_root):
41 """ 47 """
42 Args: 48 Args:
43 actuals_root: root directory containing all actual-results.json files 49 actuals_root: root directory containing all actual-results.json files
44 expected_root: root directory containing all expected-results.json files 50 expected_root: root directory containing all expected-results.json files
45 """ 51 """
46 self._actual_builder_dicts = Results._GetDictsFromRoot(actuals_root) 52 self._actual_builder_dicts = Results._get_dicts_from_root(actuals_root)
47 self._expected_builder_dicts = Results._GetDictsFromRoot(expected_root) 53 self._expected_builder_dicts = Results._get_dicts_from_root(expected_root)
48 self._all_results = Results._Combine( 54 self._combine_actual_and_expected()
49 actual_builder_dicts=self._actual_builder_dicts,
50 expected_builder_dicts=self._expected_builder_dicts)
51 55
52 def GetAll(self): 56 def get_results_of_type(self, type):
53 """Return results of all tests, as a dictionary in this form: 57 """Return results of some/all tests (depending on 'type' parameter).
58
59 Args:
60 type: string describing which types of results to include; must be one
61 of the RESULTS_* constants
62
63 Results are returned as a dictionary in this form:
54 64
55 { 65 {
56 'categories': # dictionary of categories listed in 66 'categories': # dictionary of categories listed in
57 # CATEGORIES_TO_SUMMARIZE, with the number of times 67 # CATEGORIES_TO_SUMMARIZE, with the number of times
58 # each value appears within its category 68 # each value appears within its category
59 { 69 {
60 'resultType': # category name 70 'resultType': # category name
61 { 71 {
62 'failed': 29, # category value and total number found of that value 72 'failed': 29, # category value and total number found of that value
63 'failure-ignored': 948, 73 'failure-ignored': 948,
64 'no-comparison': 4502, 74 'no-comparison': 4502,
65 'succeeded': 38609, 75 'succeeded': 38609,
66 }, 76 },
67 'builder': 77 'builder':
68 { 78 {
69 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug': 1286, 79 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug': 1286,
70 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Release': 1134, 80 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Release': 1134,
71 ... 81 ...
72 }, 82 },
73 ... # other categories from CATEGORIES_TO_SUMMARIZE 83 ... # other categories from CATEGORIES_TO_SUMMARIZE
74 }, # end of 'categories' dictionary 84 }, # end of 'categories' dictionary
75 85
76 'testData': # list of test results, with a dictionary for each 86 'testData': # list of test results, with a dictionary for each
77 [ 87 [
78 { 88 {
79 'index': 0, # index of this result within testData list
80 'builder': 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug', 89 'builder': 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug',
81 'test': 'bigmatrix', 90 'test': 'bigmatrix',
82 'config': '8888', 91 'config': '8888',
83 'resultType': 'failed', 92 'resultType': 'failed',
84 'expectedHashType': 'bitmap-64bitMD5', 93 'expectedHashType': 'bitmap-64bitMD5',
85 'expectedHashDigest': '10894408024079689926', 94 'expectedHashDigest': '10894408024079689926',
86 'actualHashType': 'bitmap-64bitMD5', 95 'actualHashType': 'bitmap-64bitMD5',
87 'actualHashDigest': '2409857384569', 96 'actualHashDigest': '2409857384569',
88 }, 97 },
89 ... 98 ...
90 ], # end of 'testData' list 99 ], # end of 'testData' list
91 } 100 }
92 """ 101 """
93 return self._all_results 102 return self._results[type]
94 103
95 @staticmethod 104 @staticmethod
96 def _GetDictsFromRoot(root, pattern='*.json'): 105 def _get_dicts_from_root(root, pattern='*.json'):
97 """Read all JSON dictionaries within a directory tree. 106 """Read all JSON dictionaries within a directory tree.
98 107
99 Args: 108 Args:
100 root: path to root of directory tree 109 root: path to root of directory tree
101 pattern: which files to read within root (fnmatch-style pattern) 110 pattern: which files to read within root (fnmatch-style pattern)
102 111
103 Returns: 112 Returns:
104 A meta-dictionary containing all the JSON dictionaries found within 113 A meta-dictionary containing all the JSON dictionaries found within
105 the directory tree, keyed by the builder name of each dictionary. 114 the directory tree, keyed by the builder name of each dictionary.
106 """ 115 """
107 meta_dict = {} 116 meta_dict = {}
108 for dirpath, dirnames, filenames in os.walk(root): 117 for dirpath, dirnames, filenames in os.walk(root):
109 for matching_filename in fnmatch.filter(filenames, pattern): 118 for matching_filename in fnmatch.filter(filenames, pattern):
110 builder = os.path.basename(dirpath) 119 builder = os.path.basename(dirpath)
111 if builder.endswith('-Trybot'): 120 if builder.endswith('-Trybot'):
112 continue 121 continue
113 fullpath = os.path.join(dirpath, matching_filename) 122 fullpath = os.path.join(dirpath, matching_filename)
114 meta_dict[builder] = gm_json.LoadFromFile(fullpath) 123 meta_dict[builder] = gm_json.LoadFromFile(fullpath)
115 return meta_dict 124 return meta_dict
116 125
117 @staticmethod 126 def _combine_actual_and_expected(self):
118 def _Combine(actual_builder_dicts, expected_builder_dicts):
119 """Gathers the results of all tests, across all builders (based on the 127 """Gathers the results of all tests, across all builders (based on the
120 contents of actual_builder_dicts and expected_builder_dicts). 128 contents of self._actual_builder_dicts and self._expected_builder_dicts),
121 129 and stores them in self._results.
122 This is a static method, because once we start refreshing results
123 asynchronously, we need to make sure we are not corrupting the object's
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().
135 """ 130 """
136 test_data = [] 131 categories_all = {}
137 category_dict = {} 132 categories_failures = {}
138 Results._EnsureIncludedInCategoryDict(category_dict, 'resultType', [ 133 Results._ensure_included_in_category_dict(categories_all,
134 'resultType', [
139 gm_json.JSONKEY_ACTUALRESULTS_FAILED, 135 gm_json.JSONKEY_ACTUALRESULTS_FAILED,
140 gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED, 136 gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED,
141 gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON, 137 gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON,
142 gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED, 138 gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED,
143 ]) 139 ])
140 Results._ensure_included_in_category_dict(categories_failures,
141 'resultType', [
142 gm_json.JSONKEY_ACTUALRESULTS_FAILED,
143 gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED,
144 gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON,
145 ])
144 146
145 for builder in sorted(actual_builder_dicts.keys()): 147 data_all = []
148 data_failures = []
149 for builder in sorted(self._actual_builder_dicts.keys()):
146 actual_results_for_this_builder = ( 150 actual_results_for_this_builder = (
147 actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS]) 151 self._actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS])
148 for result_type in sorted(actual_results_for_this_builder.keys()): 152 for result_type in sorted(actual_results_for_this_builder.keys()):
149 results_of_this_type = actual_results_for_this_builder[result_type] 153 results_of_this_type = actual_results_for_this_builder[result_type]
150 if not results_of_this_type: 154 if not results_of_this_type:
151 continue 155 continue
152 for image_name in sorted(results_of_this_type.keys()): 156 for image_name in sorted(results_of_this_type.keys()):
153 actual_image = results_of_this_type[image_name] 157 actual_image = results_of_this_type[image_name]
154 try: 158 try:
155 # TODO(epoger): assumes a single allowed digest per test 159 # TODO(epoger): assumes a single allowed digest per test
156 expected_image = ( 160 expected_image = (
157 expected_builder_dicts 161 self._expected_builder_dicts
158 [builder][gm_json.JSONKEY_EXPECTEDRESULTS] 162 [builder][gm_json.JSONKEY_EXPECTEDRESULTS]
159 [image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS] 163 [image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS]
160 [0]) 164 [0])
161 except (KeyError, TypeError): 165 except (KeyError, TypeError):
162 # There are several cases in which we would expect to find 166 # There are several cases in which we would expect to find
163 # no expectations for a given test: 167 # no expectations for a given test:
164 # 168 #
165 # 1. result_type == NOCOMPARISON 169 # 1. result_type == NOCOMPARISON
166 # There are no expectations for this test yet! 170 # There are no expectations for this test yet!
167 # 171 #
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
206 # categories recorded within the gm_actuals AT ALL, and 210 # categories recorded within the gm_actuals AT ALL, and
207 # instead evaluate the result_type ourselves based on what 211 # instead evaluate the result_type ourselves based on what
208 # we see in expectations vs actual checksum? 212 # we see in expectations vs actual checksum?
209 if expected_image == actual_image: 213 if expected_image == actual_image:
210 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED 214 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED
211 else: 215 else:
212 updated_result_type = result_type 216 updated_result_type = result_type
213 217
214 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups() 218 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups()
215 results_for_this_test = { 219 results_for_this_test = {
216 'index': len(test_data),
217 'builder': builder, 220 'builder': builder,
218 'test': test, 221 'test': test,
219 'config': config, 222 'config': config,
220 'resultType': updated_result_type, 223 'resultType': updated_result_type,
221 'actualHashType': actual_image[0], 224 'actualHashType': actual_image[0],
222 'actualHashDigest': str(actual_image[1]), 225 'actualHashDigest': str(actual_image[1]),
223 'expectedHashType': expected_image[0], 226 'expectedHashType': expected_image[0],
224 'expectedHashDigest': str(expected_image[1]), 227 'expectedHashDigest': str(expected_image[1]),
225 } 228 }
226 Results._AddToCategoryDict(category_dict, results_for_this_test) 229 Results._add_to_category_dict(categories_all, results_for_this_test)
227 test_data.append(results_for_this_test) 230 data_all.append(results_for_this_test)
228 return {'categories': category_dict, 'testData': test_data} 231 if updated_result_type != gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED:
232 Results._add_to_category_dict(categories_failures,
233 results_for_this_test)
234 data_failures.append(results_for_this_test)
235
236 self._results = {
237 RESULTS_ALL:
238 {'categories': categories_all, 'testData': data_all},
239 RESULTS_FAILURES:
240 {'categories': categories_failures, 'testData': data_failures},
241 }
229 242
230 @staticmethod 243 @staticmethod
231 def _AddToCategoryDict(category_dict, test_results): 244 def _add_to_category_dict(category_dict, test_results):
232 """Add test_results to the category dictionary we are building. 245 """Add test_results to the category dictionary we are building.
233 (See documentation of self.GetAll() for the format of this dictionary.) 246 (See documentation of self.get_results_of_type() for the format of this
247 dictionary.)
234 248
235 Args: 249 Args:
236 category_dict: category dict-of-dicts to add to; modify this in-place 250 category_dict: category dict-of-dicts to add to; modify this in-place
237 test_results: test data with which to update category_list, in a dict: 251 test_results: test data with which to update category_list, in a dict:
238 { 252 {
239 'category_name': 'category_value', 253 'category_name': 'category_value',
240 'category_name': 'category_value', 254 'category_name': 'category_value',
241 ... 255 ...
242 } 256 }
243 """ 257 """
244 for category in CATEGORIES_TO_SUMMARIZE: 258 for category in CATEGORIES_TO_SUMMARIZE:
245 category_value = test_results.get(category) 259 category_value = test_results.get(category)
246 if not category_value: 260 if not category_value:
247 continue # test_results did not include this category, keep going 261 continue # test_results did not include this category, keep going
248 if not category_dict.get(category): 262 if not category_dict.get(category):
249 category_dict[category] = {} 263 category_dict[category] = {}
250 if not category_dict[category].get(category_value): 264 if not category_dict[category].get(category_value):
251 category_dict[category][category_value] = 0 265 category_dict[category][category_value] = 0
252 category_dict[category][category_value] += 1 266 category_dict[category][category_value] += 1
253 267
254 @staticmethod 268 @staticmethod
255 def _EnsureIncludedInCategoryDict(category_dict, 269 def _ensure_included_in_category_dict(category_dict,
256 category_name, category_values): 270 category_name, category_values):
257 """Ensure that the category name/value pairs are included in category_dict, 271 """Ensure that the category name/value pairs are included in category_dict,
258 even if there aren't any results with that name/value pair. 272 even if there aren't any results with that name/value pair.
259 (See documentation of self.GetAll() for the format of this dictionary.) 273 (See documentation of self.get_results_of_type() for the format of this
274 dictionary.)
260 275
261 Args: 276 Args:
262 category_dict: category dict-of-dicts to modify 277 category_dict: category dict-of-dicts to modify
263 category_name: category name, as a string 278 category_name: category name, as a string
264 category_values: list of values we want to make sure are represented 279 category_values: list of values we want to make sure are represented
265 for this category 280 for this category
266 """ 281 """
267 if not category_dict.get(category_name): 282 if not category_dict.get(category_name):
268 category_dict[category_name] = {} 283 category_dict[category_name] = {}
269 for category_value in category_values: 284 for category_value in category_values:
270 if not category_dict[category_name].get(category_value): 285 if not category_dict[category_name].get(category_value):
271 category_dict[category_name][category_value] = 0 286 category_dict[category_name][category_value] = 0
OLDNEW
« no previous file with comments | « no previous file | gm/rebaseline_server/server.py » ('j') | gm/rebaseline_server/server.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698