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

Side by Side Diff: webkit/tools/layout_tests/layout_package/json_results_generator.py

Issue 201073: Run-length encode the JSON results. This makes them considerably... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 3 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 # Copyright (c) 2009 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import logging 5 import logging
6 import os 6 import os
7 import re 7 import re
8 import sys 8 import sys
9 9
10 from layout_package import path_utils 10 from layout_package import path_utils
11 from layout_package import test_failures 11 from layout_package import test_failures
12 12
13 sys.path.append(path_utils.PathFromBase('third_party')) 13 sys.path.append(path_utils.PathFromBase('third_party'))
14 import simplejson 14 import simplejson
15 15
16 class JSONResultsGenerator: 16 class JSONResultsGenerator:
17 17
18 MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 200 18 MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 500
19 # Min time (seconds) that will be added to the JSON. 19 # Min time (seconds) that will be added to the JSON.
20 MIN_TIME = 1 20 MIN_TIME = 1
21 JSON_PREFIX = "ADD_RESULTS(" 21 JSON_PREFIX = "ADD_RESULTS("
22 JSON_SUFFIX = ");" 22 JSON_SUFFIX = ");"
23 WEBKIT_PATH = "WebKit" 23 WEBKIT_PATH = "WebKit"
24 LAYOUT_TESTS_PATH = "layout_tests" 24 LAYOUT_TESTS_PATH = "layout_tests"
25 PASS_RESULT = "P" 25 PASS_RESULT = "P"
26 NO_DATA_RESULT = "N" 26 NO_DATA_RESULT = "N"
27 VERSION = 1
28 VERSION_KEY = "version"
29 RESULTS = "results"
30 TIMES = "times"
31 BUILD_NUMBERS = "buildNumbers"
32 TESTS = "tests"
27 33
28 def __init__(self, failures, individual_test_timings, builder_name, 34 def __init__(self, failures, individual_test_timings, builder_name,
29 build_number, results_file_path, all_tests): 35 build_number, results_file_path, all_tests):
30 """ 36 """
31 failures: Map of test name to list of failures. 37 failures: Map of test name to list of failures.
32 individual_test_times: Map of test name to a tuple containing the 38 individual_test_times: Map of test name to a tuple containing the
33 test_run-time. 39 test_run-time.
34 builder_name: The name of the builder the tests are being run on. 40 builder_name: The name of the builder the tests are being run on.
35 build_number: The build number for this run. 41 build_number: The build number for this run.
36 results_file_path: Absolute path to the results json file. 42 results_file_path: Absolute path to the results json file.
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
112 118
113 if self._builder_name not in results_json: 119 if self._builder_name not in results_json:
114 logging.error("Builder name (%s) is not in the results.json file." % 120 logging.error("Builder name (%s) is not in the results.json file." %
115 self._builder_name); 121 self._builder_name);
116 else: 122 else:
117 # TODO(ojan): If the build output directory gets clobbered, we should 123 # TODO(ojan): If the build output directory gets clobbered, we should
118 # grab this file off wherever it's archived to. Maybe we should always 124 # grab this file off wherever it's archived to. Maybe we should always
119 # just grab it from wherever it's archived to. 125 # just grab it from wherever it's archived to.
120 results_json = {} 126 results_json = {}
121 127
128 self._ConvertJSONToCurrentVersion(results_json)
129
122 if self._builder_name not in results_json: 130 if self._builder_name not in results_json:
123 results_json[self._builder_name] = self._CreateResultsForBuilderJSON() 131 results_json[self._builder_name] = self._CreateResultsForBuilderJSON()
124 132
125 tests = results_json[self._builder_name]["tests"] 133 tests = results_json[self._builder_name][self.TESTS]
126 all_failing_tests = set(self._failures.iterkeys()) 134 all_failing_tests = set(self._failures.iterkeys())
127 all_failing_tests.update(tests.iterkeys()) 135 all_failing_tests.update(tests.iterkeys())
128 136
129 build_numbers = results_json[self._builder_name]["buildNumbers"] 137 build_numbers = results_json[self._builder_name][self.BUILD_NUMBERS]
130 build_numbers.insert(0, self._build_number) 138 build_numbers.insert(0, self._build_number)
131 build_numbers = build_numbers[:self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG] 139 build_numbers = build_numbers[:self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG]
132 results_json[self._builder_name]["buildNumbers"] = build_numbers 140 results_json[self._builder_name][self.BUILD_NUMBERS] = build_numbers
133 num_build_numbers = len(build_numbers) 141 num_build_numbers = len(build_numbers)
134 142
135 for test in all_failing_tests: 143 for test in all_failing_tests:
136 if test in failures_for_json: 144 if test in failures_for_json:
137 result_and_time = failures_for_json[test] 145 result_and_time = failures_for_json[test]
138 else: 146 else:
139 result_and_time = ResultAndTime(test, self._all_tests) 147 result_and_time = ResultAndTime(test, self._all_tests)
140 148
141 if test not in tests: 149 if test not in tests:
142 tests[test] = self._CreateResultsAndTimesJSON() 150 tests[test] = self._CreateResultsAndTimesJSON()
143 151
144 thisTest = tests[test] 152 thisTest = tests[test]
145 thisTest["results"] = result_and_time.result + thisTest["results"] 153 self._InsertItemRunLengthEncoded(result_and_time.result,
146 thisTest["times"].insert(0, result_and_time.time) 154 thisTest[self.RESULTS])
147 155 self._InsertItemRunLengthEncoded(result_and_time.time,
156 thisTest[self.TIMES])
148 self._NormalizeResultsJSON(thisTest, test, tests, num_build_numbers) 157 self._NormalizeResultsJSON(thisTest, test, tests, num_build_numbers)
149 158
150 # Specify separators in order to get compact encoding. 159 # Specify separators in order to get compact encoding.
151 results_str = simplejson.dumps(results_json, separators=(',', ':')) 160 results_str = simplejson.dumps(results_json, separators=(',', ':'))
152 return self.JSON_PREFIX + results_str + self.JSON_SUFFIX 161 return self.JSON_PREFIX + results_str + self.JSON_SUFFIX
153 162
163 def _InsertItemRunLengthEncoded(self, item, encoded_results):
164 """Inserts the item into the run-length encoded results.
165
166 Args:
167 item: String or number to insert.
168 encoded_results: run-length encoded results. An array of arrays, e.g.
169 [[3,'A'],[1,'Q']] encodes AAAQ.
170 """
171 if len(encoded_results) and item == encoded_results[0][1]:
172 encoded_results[0][0] += 1
173 else:
174 # Use a list instead of a class for the run-length encoding since we
175 # want the serialized form to be concise.
176 encoded_results.insert(0, [1, item])
177
178 def _ConvertJSONToCurrentVersion(self, results_json):
179 """If the JSON does not match the current version, converts it to the
180 current version and adds in the new version number.
181 """
182 if (self.VERSION_KEY in results_json and
183 results_json[self.VERSION_KEY] == self.VERSION):
184 return
185
186 for builder in results_json:
187 tests = results_json[builder][self.TESTS]
188 for path in tests:
189 test = tests[path]
190 test[self.RESULTS] = self._RunLengthEncode(test[self.RESULTS])
191 test[self.TIMES] = self._RunLengthEncode(test[self.TIMES])
192
193 results_json[self.VERSION_KEY] = self.VERSION
194
195 def _RunLengthEncode(self, result_list):
196 """Run-length encodes a list or string of results."""
197 encoded_results = [];
198 current_result = None;
199 for item in reversed(result_list):
200 self._InsertItemRunLengthEncoded(item, encoded_results)
201 return encoded_results
202
154 def _CreateResultsAndTimesJSON(self): 203 def _CreateResultsAndTimesJSON(self):
155 results_and_times = {} 204 results_and_times = {}
156 results_and_times["results"] = "" 205 results_and_times[self.RESULTS] = []
157 results_and_times["times"] = [] 206 results_and_times[self.TIMES] = []
158 return results_and_times 207 return results_and_times
159 208
160 def _CreateResultsForBuilderJSON(self): 209 def _CreateResultsForBuilderJSON(self):
161 results_for_builder = {} 210 results_for_builder = {}
162 results_for_builder['buildNumbers'] = [] 211 results_for_builder[self.BUILD_NUMBERS] = []
163 results_for_builder['tests'] = {} 212 results_for_builder[self.TESTS] = {}
164 return results_for_builder 213 return results_for_builder
165 214
166 def _GetResultsCharForFailure(self, test): 215 def _GetResultsCharForFailure(self, test):
167 """Returns the worst failure from the list of failures for this test 216 """Returns the worst failure from the list of failures for this test
168 since we can only show one failure per run for each test on the dashboard. 217 since we can only show one failure per run for each test on the dashboard.
169 """ 218 """
170 failures = [failure.__class__ for failure in self._failures[test]] 219 failures = [failure.__class__ for failure in self._failures[test]]
171 220
172 if test_failures.FailureCrash in failures: 221 if test_failures.FailureCrash in failures:
173 return "C" 222 return "C"
174 elif test_failures.FailureTimeout in failures: 223 elif test_failures.FailureTimeout in failures:
175 return "T" 224 return "T"
176 elif test_failures.FailureImageHashMismatch in failures: 225 elif test_failures.FailureImageHashMismatch in failures:
177 return "I" 226 return "I"
178 elif test_failures.FailureSimplifiedTextMismatch in failures: 227 elif test_failures.FailureSimplifiedTextMismatch in failures:
179 return "S" 228 return "S"
180 elif test_failures.FailureTextMismatch in failures: 229 elif test_failures.FailureTextMismatch in failures:
181 return "F" 230 return "F"
182 else: 231 else:
183 return "O" 232 return "O"
184 233
234 def _RemoveItemsOverMaxNumberOfBuilds(self, encoded_list):
235 """Removes items from the run-length encoded list after the final itme that
236 exceeds the max number of builds to track.
237
238 Args:
239 encoded_results: run-length encoded results. An array of arrays, e.g.
240 [[3,'A'],[1,'Q']] encodes AAAQ.
241 """
242 num_builds = 0
243 index = 0
244 for result in encoded_list:
245 num_builds = num_builds + result[0]
246 index = index + 1
247 if num_builds > self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG:
248 return encoded_list[:index]
249 return encoded_list
250
185 def _NormalizeResultsJSON(self, test, test_path, tests, num_build_numbers): 251 def _NormalizeResultsJSON(self, test, test_path, tests, num_build_numbers):
186 """ Prune tests where all runs pass or tests that no longer exist and 252 """ Prune tests where all runs pass or tests that no longer exist and
187 truncate all results to maxNumberOfBuilds and pad results that don't 253 truncate all results to maxNumberOfBuilds and pad results that don't
188 have encough runs for maxNumberOfBuilds. 254 have encough runs for maxNumberOfBuilds.
189 255
190 Args: 256 Args:
191 test: ResultsAndTimes object for this test. 257 test: ResultsAndTimes object for this test.
192 test_path: Path to the test. 258 test_path: Path to the test.
193 tests: The JSON object with all the test results for this builder. 259 tests: The JSON object with all the test results for this builder.
194 num_build_numbers: The number to truncate/pad results to. 260 num_build_numbers: The number to truncate/pad results to.
195 """ 261 """
196 results = test["results"] 262 test[self.RESULTS] = self._RemoveItemsOverMaxNumberOfBuilds(
197 num_results = len(results) 263 test[self.RESULTS])
198 times = test["times"] 264 test[self.TIMES] = self._RemoveItemsOverMaxNumberOfBuilds(test[self.TIMES])
199
200 if num_results != len(times):
201 logging.error("Test has different number of build times versus results")
202 times = []
203 results = ""
204 num_results = 0
205
206 # Truncate or right-pad so there are exactly maxNumberOfBuilds results.
207 if num_results > num_build_numbers:
208 results = results[:num_build_numbers]
209 times = times[:num_build_numbers]
210 elif num_results < num_build_numbers:
211 num_to_pad = num_build_numbers - num_results
212 results = results + num_to_pad * self.NO_DATA_RESULT
213 times.extend(num_to_pad * [0])
214
215 test["results"] = results
216 test["times"] = times
217 265
218 # Remove all passes/no-data from the results to reduce noise and filesize. 266 # Remove all passes/no-data from the results to reduce noise and filesize.
219 if (results == num_build_numbers * self.NO_DATA_RESULT or 267 if (self._IsResultsAllOfType(test[self.RESULTS], self.PASS_RESULT) or
220 (max(times) <= self.MIN_TIME and num_results and 268 (self._IsResultsAllOfType(test[self.RESULTS], self.NO_DATA_RESULT) and
221 results == num_build_numbers * self.PASS_RESULT)): 269 max(test[self.TIMES],
270 lambda x, y : cmp(x[1], y[1])) <= self.MIN_TIME)):
222 del tests[test_path] 271 del tests[test_path]
223 272
224 # Remove tests that don't exist anymore. 273 # Remove tests that don't exist anymore.
225 full_path = os.path.join(path_utils.LayoutTestsDir(test_path), test_path) 274 full_path = os.path.join(path_utils.LayoutTestsDir(test_path), test_path)
226 full_path = os.path.normpath(full_path) 275 full_path = os.path.normpath(full_path)
227 if not os.path.exists(full_path): 276 if not os.path.exists(full_path):
228 del tests[test_path] 277 del tests[test_path]
229 278
279 def _IsResultsAllOfType(self, results, type):
280 """Returns whether all teh results are of the given type (e.g. all passes).
281 """
282 return len(results) == 1 and results[0][1] == type
283
230 class ResultAndTime: 284 class ResultAndTime:
231 """A holder for a single result and runtime for a test.""" 285 """A holder for a single result and runtime for a test."""
232 def __init__(self, test, all_tests): 286 def __init__(self, test, all_tests):
233 self.time = 0 287 self.time = 0
234 # If the test was run, then we don't want to default the result to nodata. 288 # If the test was run, then we don't want to default the result to nodata.
235 if test in all_tests: 289 if test in all_tests:
236 self.result = JSONResultsGenerator.PASS_RESULT 290 self.result = JSONResultsGenerator.PASS_RESULT
237 else: 291 else:
238 self.result = JSONResultsGenerator.NO_DATA_RESULT 292 self.result = JSONResultsGenerator.NO_DATA_RESULT
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698