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

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

Issue 25045003: HTTP GM results viewer: server now returns category summaries along with testData (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: add_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
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 9
10 ''' 10 '''
(...skipping 13 matching lines...) Expand all
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 32
33 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) 33 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
34 CATEGORIES_TO_SUMMARIZE = [
35 'builder', 'test', 'config', 'resultType',
36 ]
34 37
35 class Results(object): 38 class Results(object):
36 """ Loads actual and expected results from all builders, supplying combined 39 """ Loads actual and expected results from all builders, supplying combined
37 reports as requested. """ 40 reports as requested. """
38 41
39 def __init__(self, actuals_root, expected_root): 42 def __init__(self, actuals_root, expected_root):
40 """ 43 """
41 params: 44 params:
42 actuals_root: root directory containing all actual-results.json files 45 actuals_root: root directory containing all actual-results.json files
43 expected_root: root directory containing all expected-results.json files 46 expected_root: root directory containing all expected-results.json files
44 """ 47 """
45 self._actual_builder_dicts = Results._GetDictsFromRoot(actuals_root) 48 self._actual_builder_dicts = Results._GetDictsFromRoot(actuals_root)
46 self._expected_builder_dicts = Results._GetDictsFromRoot(expected_root) 49 self._expected_builder_dicts = Results._GetDictsFromRoot(expected_root)
47 self._all_results = self._Combine() 50 self._all_results = Results._Combine(
51 actual_builder_dicts=self._actual_builder_dicts,
52 expected_builder_dicts=self._expected_builder_dicts)
48 53
49 def GetAll(self): 54 def GetAll(self):
50 """Return results of all tests, as a list in this form: 55 """Return results of all tests, as a dictionary in this form:
51 56
52 [ 57 {
58 "categories": # dictionary of category totals, keyed by category name
epoger 2013/09/27 18:12:39 This "categories" dict is the new part. By includ
borenet 2013/09/27 19:22:01 I'm confused - "resultType" and "builder" are the
epoger 2013/09/27 21:38:13 I updated the docstring to (hopefully) make it cle
borenet 2013/09/30 13:06:02 Thanks Elliot. This is much clearer.
59 # and value
53 { 60 {
54 "builder": "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug", 61 "resultType": # category name, one of CATEGORIES_TO_SUMMARIZE
55 "test": "bigmatrix", 62 {
56 "config": "8888", 63 "failed": 3, # category value and total number found of that value
57 "resultType": "failed", 64 "failure-ignored": 19,
58 "expectedHashType": "bitmap-64bitMD5", 65 "no-comparison": 120,
59 "expectedHashDigest": "10894408024079689926", 66 "succeeded": 1932,
60 "actualHashType": "bitmap-64bitMD5", 67 },
61 "actualHashDigest": "2409857384569", 68 "builder":
62 }, 69 {
63 ... 70 "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug": 180,
64 ] 71 "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Release": 180,
borenet 2013/09/27 19:22:01 What does 180 mean? Is that the total number of r
epoger 2013/09/27 21:38:13 That's what the number at that position represents
72 ...
73 },
74 }, # end of "categories" dictionary
75
76 "testData": # list of test results, with a dictionary for each
77 [
78 {
79 "builder": "Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug",
80 "test": "bigmatrix",
81 "config": "8888",
82 "resultType": "failed",
83 "expectedHashType": "bitmap-64bitMD5",
84 "expectedHashDigest": "10894408024079689926",
85 "actualHashType": "bitmap-64bitMD5",
86 "actualHashDigest": "2409857384569",
87 },
88 ...
89 ], # end of "testData" list
90 }
65 """ 91 """
66 return self._all_results 92 return self._all_results
67 93
68 @staticmethod 94 @staticmethod
69 def _GetDictsFromRoot(root, pattern='*.json'): 95 def _GetDictsFromRoot(root, pattern='*.json'):
70 """Read all JSON dictionaries within a directory tree, returning them within 96 """Read all JSON dictionaries within a directory tree, returning them within
71 a meta-dictionary (keyed by the builder name for each dictionary). 97 a meta-dictionary (keyed by the builder name for each dictionary).
72 98
73 params: 99 params:
74 root: path to root of directory tree 100 root: path to root of directory tree
75 pattern: which files to read within root (fnmatch-style pattern) 101 pattern: which files to read within root (fnmatch-style pattern)
76 """ 102 """
77 meta_dict = {} 103 meta_dict = {}
78 for dirpath, dirnames, filenames in os.walk(root): 104 for dirpath, dirnames, filenames in os.walk(root):
79 for matching_filename in fnmatch.filter(filenames, pattern): 105 for matching_filename in fnmatch.filter(filenames, pattern):
80 builder = os.path.basename(dirpath) 106 builder = os.path.basename(dirpath)
81 if builder.endswith('-Trybot'): 107 if builder.endswith('-Trybot'):
82 continue 108 continue
83 fullpath = os.path.join(dirpath, matching_filename) 109 fullpath = os.path.join(dirpath, matching_filename)
84 meta_dict[builder] = gm_json.LoadFromFile(fullpath) 110 meta_dict[builder] = gm_json.LoadFromFile(fullpath)
85 return meta_dict 111 return meta_dict
86 112
87 def _Combine(self): 113 @staticmethod
88 """Returns a list of all tests, across all builders, based on the 114 def _Combine(actual_builder_dicts, expected_builder_dicts):
89 contents of self._actual_builder_dicts and self._expected_builder_dicts . 115 """Gathers the results of all tests, across all builders (based on the
90 Returns the list in the same form needed for GetAllResults(). 116 contents of actual_builder_dicts and expected_builder_dicts)
117 and returns it in a list in the same form needed for self.GetAll().
118
119 This is a static method, because once we start refreshing results
120 asynchronously, we need to make sure we are not corrupting the object's
121 member variables.
91 """ 122 """
92 all_tests = [] 123 test_data = []
93 for builder in sorted(self._actual_builder_dicts.keys()): 124 category_dict = {}
125 for builder in sorted(actual_builder_dicts.keys()):
94 actual_results_for_this_builder = ( 126 actual_results_for_this_builder = (
95 self._actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS]) 127 actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS])
96 for result_type in sorted(actual_results_for_this_builder.keys()): 128 for result_type in sorted(actual_results_for_this_builder.keys()):
97 results_of_this_type = actual_results_for_this_builder[result_type] 129 results_of_this_type = actual_results_for_this_builder[result_type]
98 if not results_of_this_type: 130 if not results_of_this_type:
99 continue 131 continue
100 for image_name in sorted(results_of_this_type.keys()): 132 for image_name in sorted(results_of_this_type.keys()):
101 actual_image = results_of_this_type[image_name] 133 actual_image = results_of_this_type[image_name]
102 try: 134 try:
103 # TODO(epoger): assumes a single allowed digest per test 135 # TODO(epoger): assumes a single allowed digest per test
104 expected_image = ( 136 expected_image = (
105 self._expected_builder_dicts 137 expected_builder_dicts
106 [builder][gm_json.JSONKEY_EXPECTEDRESULTS] 138 [builder][gm_json.JSONKEY_EXPECTEDRESULTS]
107 [image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS] 139 [image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS]
108 [0]) 140 [0])
109 except (KeyError, TypeError): 141 except (KeyError, TypeError):
110 # There are several cases in which we would expect to find 142 # There are several cases in which we would expect to find
111 # no expectations for a given test: 143 # no expectations for a given test:
112 # 144 #
113 # 1. result_type == NOCOMPARISON 145 # 1. result_type == NOCOMPARISON
114 # There are no expectations for this test yet! 146 # There are no expectations for this test yet!
115 # 147 #
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
152 # as ignoreFailure but it still shows up in the "failed" 184 # as ignoreFailure but it still shows up in the "failed"
153 # category? Maybe we should not rely on the result_type 185 # category? Maybe we should not rely on the result_type
154 # categories recorded within the gm_actuals AT ALL, and 186 # categories recorded within the gm_actuals AT ALL, and
155 # instead evaluate the result_type ourselves based on what 187 # instead evaluate the result_type ourselves based on what
156 # we see in expectations vs actual checksum? 188 # we see in expectations vs actual checksum?
157 if expected_image == actual_image: 189 if expected_image == actual_image:
158 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED 190 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED
159 else: 191 else:
160 updated_result_type = result_type 192 updated_result_type = result_type
161 193
162 # TODO(epoger): For now, don't include succeeded results.
163 # There are so many of them that they make the client too slow.
164 if updated_result_type == gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED:
165 continue
166
167 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups() 194 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups()
168 all_tests.append({ 195 results_for_this_test = {
169 "builder": builder, 196 "builder": builder,
170 "test": test, 197 "test": test,
171 "config": config, 198 "config": config,
172 "resultType": updated_result_type, 199 "resultType": updated_result_type,
173 "actualHashType": actual_image[0], 200 "actualHashType": actual_image[0],
174 "actualHashDigest": str(actual_image[1]), 201 "actualHashDigest": str(actual_image[1]),
175 "expectedHashType": expected_image[0], 202 "expectedHashType": expected_image[0],
176 "expectedHashDigest": str(expected_image[1]), 203 "expectedHashDigest": str(expected_image[1]),
177 }) 204 }
178 return all_tests 205 Results._AddToCategoryDict(category_dict, results_for_this_test)
206
207 # TODO(epoger): For now, don't include succeeded results in the raw
208 # data. There are so many of them that they make the client too slow.
209 if updated_result_type != gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED:
210 test_data.append(results_for_this_test)
211 return {"categories": category_dict, "testData": test_data}
212
213 @staticmethod
214 def _AddToCategoryDict(category_dict, test_results):
215 """Add test_results to the category dictionary we are building
216 (see documentation of self.GetAll() for the format of this dictionary).
217
218 params:
219 category_dict: category dict-of-dicts to add to; modify this in-place
220 test_results: test data with which to update category_list, in a dict:
221 {
222 "category_name": "category_value",
223 "category_name": "category_value",
224 ...
225 }
226 """
227 for category in CATEGORIES_TO_SUMMARIZE:
228 category_value = test_results.get(category)
229 if not category_value:
230 continue # test_results did not include this category, keep going
231 if not category_dict.get(category):
232 category_dict[category] = {}
233 if not category_dict[category].get(category_value):
234 category_dict[category][category_value] = 0
235 category_dict[category][category_value] += 1
OLDNEW
« no previous file with comments | « no previous file | gm/rebaseline_server/static/loader.js » ('j') | gm/rebaseline_server/static/view.html » ('J')

Powered by Google App Engine
This is Rietveld 408576698