OLD | NEW |
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 subprocess | 8 import subprocess |
9 import sys | 9 import sys |
10 import time | 10 import time |
11 import xml.dom.minidom | 11 import xml.dom.minidom |
12 | 12 |
13 from layout_package import path_utils | 13 from layout_package import path_utils |
14 from layout_package import test_failures | 14 from layout_package import test_failures |
15 | 15 |
16 sys.path.append(path_utils.PathFromBase('third_party')) | 16 sys.path.append(path_utils.PathFromBase('third_party')) |
17 import simplejson | 17 import simplejson |
18 | 18 |
19 class JSONResultsGenerator: | 19 class JSONResultsGenerator: |
20 | 20 |
21 MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 500 | 21 MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 500 |
22 # Min time (seconds) that will be added to the JSON. | 22 # Min time (seconds) that will be added to the JSON. |
23 MIN_TIME = 1 | 23 MIN_TIME = 1 |
24 JSON_PREFIX = "ADD_RESULTS(" | 24 JSON_PREFIX = "ADD_RESULTS(" |
25 JSON_SUFFIX = ");" | 25 JSON_SUFFIX = ");" |
26 WEBKIT_PATH = "WebKit" | 26 WEBKIT_PATH = "WebKit" |
27 LAYOUT_TESTS_PATH = "layout_tests" | 27 LAYOUT_TESTS_PATH = "layout_tests" |
28 PASS_RESULT = "P" | 28 PASS_RESULT = "P" |
| 29 SKIP_RESULT = "X" |
29 NO_DATA_RESULT = "N" | 30 NO_DATA_RESULT = "N" |
30 VERSION = 1 | 31 VERSION = 1 |
31 VERSION_KEY = "version" | 32 VERSION_KEY = "version" |
32 RESULTS = "results" | 33 RESULTS = "results" |
33 TIMES = "times" | 34 TIMES = "times" |
34 BUILD_NUMBERS = "buildNumbers" | 35 BUILD_NUMBERS = "buildNumbers" |
35 WEBKIT_SVN = "webkitRevision" | 36 WEBKIT_SVN = "webkitRevision" |
36 CHROME_SVN = "chromeRevision" | 37 CHROME_SVN = "chromeRevision" |
37 TIME = "secondsSinceEpoch" | 38 TIME = "secondsSinceEpoch" |
38 TESTS = "tests" | 39 TESTS = "tests" |
| 40 NON_WONTFIX = "nonWontfixCounts" |
| 41 DEFERRED = "deferredCounts" |
| 42 ALL = "allCounts" |
| 43 FIXABLE_COUNT = "fixableCount" |
| 44 FAILURE_CHARS = ["C", "T", "I", "S", "F", "O"] |
39 | 45 |
40 def __init__(self, failures, individual_test_timings, builder_name, | 46 def __init__(self, failures, individual_test_timings, builder_name, |
41 build_number, results_file_path, all_tests): | 47 build_number, results_file_path, all_tests, result_summary): |
42 """ | 48 """ |
43 failures: Map of test name to list of failures. | 49 failures: Map of test name to list of failures. |
44 individual_test_times: Map of test name to a tuple containing the | 50 individual_test_times: Map of test name to a tuple containing the |
45 test_run-time. | 51 test_run-time. |
46 builder_name: The name of the builder the tests are being run on. | 52 builder_name: The name of the builder the tests are being run on. |
47 build_number: The build number for this run. | 53 build_number: The build number for this run. |
48 results_file_path: Absolute path to the results json file. | 54 results_file_path: Absolute path to the results json file. |
49 all_tests: List of all the tests that were run. | 55 all_tests: List of all the tests that were run. |
| 56 result_summary: ResultsSummary object containing failure counts for |
| 57 different groups of tests. |
50 """ | 58 """ |
51 # Make sure all test paths are relative to the layout test root directory. | 59 # Make sure all test paths are relative to the layout test root directory. |
52 self._failures = {} | 60 self._failures = {} |
53 for test in failures: | 61 for test in failures: |
54 test_path = self._GetPathRelativeToLayoutTestRoot(test) | 62 test_path = self._GetPathRelativeToLayoutTestRoot(test) |
55 self._failures[test_path] = failures[test] | 63 self._failures[test_path] = failures[test] |
56 | 64 |
57 self._all_tests = [self._GetPathRelativeToLayoutTestRoot(test) | 65 self._all_tests = [self._GetPathRelativeToLayoutTestRoot(test) |
58 for test in all_tests] | 66 for test in all_tests] |
59 | 67 |
| 68 self._result_summary = result_summary |
| 69 |
60 self._test_timings = {} | 70 self._test_timings = {} |
61 for test_tuple in individual_test_timings: | 71 for test_tuple in individual_test_timings: |
62 test_path = self._GetPathRelativeToLayoutTestRoot(test_tuple.filename) | 72 test_path = self._GetPathRelativeToLayoutTestRoot(test_tuple.filename) |
63 self._test_timings[test_path] = test_tuple.test_run_time | 73 self._test_timings[test_path] = test_tuple.test_run_time |
64 | 74 |
65 self._builder_name = builder_name | 75 self._builder_name = builder_name |
66 self._build_number = build_number | 76 self._build_number = build_number |
67 self._results_file_path = results_file_path | 77 self._results_file_path = results_file_path |
68 | 78 |
69 def _GetSVNRevision(self, in_directory=None): | 79 def _GetSVNRevision(self, in_directory=None): |
(...skipping 36 matching lines...) Loading... |
106 relativePath = test[index + 1:] | 116 relativePath = test[index + 1:] |
107 | 117 |
108 # Make sure all paths are unix-style. | 118 # Make sure all paths are unix-style. |
109 return relativePath.replace('\\', '/') | 119 return relativePath.replace('\\', '/') |
110 | 120 |
111 def GetJSON(self): | 121 def GetJSON(self): |
112 """Gets the results for the results.json file.""" | 122 """Gets the results for the results.json file.""" |
113 failures_for_json = {} | 123 failures_for_json = {} |
114 for test in self._failures: | 124 for test in self._failures: |
115 failures_for_json[test] = ResultAndTime(test, self._all_tests) | 125 failures_for_json[test] = ResultAndTime(test, self._all_tests) |
116 failures_for_json[test].result = self._GetResultsCharForFailure(test) | 126 failures_for_json[test].result = self._GetResultsCharForTest(test) |
117 | 127 |
118 for test in self._test_timings: | 128 for test in self._test_timings: |
119 if not test in failures_for_json: | 129 if not test in failures_for_json: |
120 failures_for_json[test] = ResultAndTime(test, self._all_tests) | 130 failures_for_json[test] = ResultAndTime(test, self._all_tests) |
121 # Floor for now to get time in seconds. | 131 # Floor for now to get time in seconds. |
122 # TODO(ojan): As we make tests faster, reduce to tenth of a second | 132 # TODO(ojan): As we make tests faster, reduce to tenth of a second |
123 # granularity. | 133 # granularity. |
124 failures_for_json[test].time = int(self._test_timings[test]) | 134 failures_for_json[test].time = int(self._test_timings[test]) |
125 | 135 |
126 # If results file exists, read it out, put new info in it. | 136 # If results file exists, read it out, put new info in it. |
(...skipping 40 matching lines...) Loading... |
167 | 177 |
168 path_to_chrome_base = path_utils.PathFromBase() | 178 path_to_chrome_base = path_utils.PathFromBase() |
169 self._InsertItemIntoRawList(results_for_builder, | 179 self._InsertItemIntoRawList(results_for_builder, |
170 self._GetSVNRevision(path_to_chrome_base), | 180 self._GetSVNRevision(path_to_chrome_base), |
171 self.CHROME_SVN) | 181 self.CHROME_SVN) |
172 | 182 |
173 self._InsertItemIntoRawList(results_for_builder, | 183 self._InsertItemIntoRawList(results_for_builder, |
174 int(time.time()), | 184 int(time.time()), |
175 self.TIME) | 185 self.TIME) |
176 | 186 |
| 187 self._InsertFailureSummaries(results_for_builder) |
| 188 |
177 for test in all_failing_tests: | 189 for test in all_failing_tests: |
178 if test in failures_for_json: | 190 if test in failures_for_json: |
179 result_and_time = failures_for_json[test] | 191 result_and_time = failures_for_json[test] |
180 else: | 192 else: |
181 result_and_time = ResultAndTime(test, self._all_tests) | 193 result_and_time = ResultAndTime(test, self._all_tests) |
182 | 194 |
183 if test not in tests: | 195 if test not in tests: |
184 tests[test] = self._CreateResultsAndTimesJSON() | 196 tests[test] = self._CreateResultsAndTimesJSON() |
185 | 197 |
186 thisTest = tests[test] | 198 thisTest = tests[test] |
187 self._InsertItemRunLengthEncoded(result_and_time.result, | 199 self._InsertItemRunLengthEncoded(result_and_time.result, |
188 thisTest[self.RESULTS]) | 200 thisTest[self.RESULTS]) |
189 self._InsertItemRunLengthEncoded(result_and_time.time, | 201 self._InsertItemRunLengthEncoded(result_and_time.time, |
190 thisTest[self.TIMES]) | 202 thisTest[self.TIMES]) |
191 self._NormalizeResultsJSON(thisTest, test, tests) | 203 self._NormalizeResultsJSON(thisTest, test, tests) |
192 | 204 |
193 # Specify separators in order to get compact encoding. | 205 # Specify separators in order to get compact encoding. |
194 results_str = simplejson.dumps(results_json, separators=(',', ':')) | 206 results_str = simplejson.dumps(results_json, separators=(',', ':')) |
195 return self.JSON_PREFIX + results_str + self.JSON_SUFFIX | 207 return self.JSON_PREFIX + results_str + self.JSON_SUFFIX |
196 | 208 |
| 209 def _InsertFailureSummaries(self, results_for_builder): |
| 210 """Inserts aggregate pass/failure statistics into the JSON. |
| 211 |
| 212 Args: |
| 213 results_for_builder: Dictionary containing the test results for a single |
| 214 builder. |
| 215 """ |
| 216 self._InsertItemIntoRawList(results_for_builder, |
| 217 self._result_summary.fixable_count, |
| 218 self.FIXABLE_COUNT) |
| 219 |
| 220 self._InsertItemIntoRawList(results_for_builder, |
| 221 self._GetFailureSummaryEntry(self._result_summary.deferred), |
| 222 self.DEFERRED) |
| 223 self._InsertItemIntoRawList(results_for_builder, |
| 224 self._GetFailureSummaryEntry(self._result_summary.non_wontfix), |
| 225 self.NON_WONTFIX) |
| 226 self._InsertItemIntoRawList(results_for_builder, |
| 227 self._GetFailureSummaryEntry(self._result_summary.all), |
| 228 self.ALL) |
| 229 |
| 230 def _GetFailureSummaryEntry(self, result_summary_entry): |
| 231 """Creates a summary object to insert into the JSON. |
| 232 |
| 233 Args: |
| 234 result_summary_entry: ResultSummaryEntry for a group of tests |
| 235 (e.g. deferred tests). |
| 236 """ |
| 237 entry = {} |
| 238 entry[self.SKIP_RESULT] = result_summary_entry.skip_count |
| 239 entry[self.PASS_RESULT] = result_summary_entry.pass_count |
| 240 for char in self.FAILURE_CHARS: |
| 241 # There can be multiple failures that map to "O", so keep existing entry |
| 242 # values if they already exist. |
| 243 if char in entry: |
| 244 count = entry[char] |
| 245 else: |
| 246 count = 0 |
| 247 |
| 248 for failure in result_summary_entry.failure_counts: |
| 249 if char == self._GetResultsCharForFailure([failure]): |
| 250 count = result_summary_entry.failure_counts[failure] |
| 251 entry[char] = count |
| 252 return entry |
| 253 |
197 def _InsertItemIntoRawList(self, results_for_builder, item, key): | 254 def _InsertItemIntoRawList(self, results_for_builder, item, key): |
198 """Inserts the item into the list with the given key in the results for | 255 """Inserts the item into the list with the given key in the results for |
199 this builder. Creates the list if no such list exists. | 256 this builder. Creates the list if no such list exists. |
200 | 257 |
201 Args: | 258 Args: |
202 results_for_builder: Dictionary containing the test results for a single | 259 results_for_builder: Dictionary containing the test results for a single |
203 builder. | 260 builder. |
204 item: Number or string to insert into the list. | 261 item: Number or string to insert into the list. |
205 key: Key in results_for_builder for the list to insert into. | 262 key: Key in results_for_builder for the list to insert into. |
206 """ | 263 """ |
(...skipping 50 matching lines...) Loading... |
257 results_and_times = {} | 314 results_and_times = {} |
258 results_and_times[self.RESULTS] = [] | 315 results_and_times[self.RESULTS] = [] |
259 results_and_times[self.TIMES] = [] | 316 results_and_times[self.TIMES] = [] |
260 return results_and_times | 317 return results_and_times |
261 | 318 |
262 def _CreateResultsForBuilderJSON(self): | 319 def _CreateResultsForBuilderJSON(self): |
263 results_for_builder = {} | 320 results_for_builder = {} |
264 results_for_builder[self.TESTS] = {} | 321 results_for_builder[self.TESTS] = {} |
265 return results_for_builder | 322 return results_for_builder |
266 | 323 |
267 def _GetResultsCharForFailure(self, test): | 324 def _GetResultsCharForTest(self, test): |
268 """Returns the worst failure from the list of failures for this test | 325 """Returns the worst failure from the list of failures for this test |
269 since we can only show one failure per run for each test on the dashboard. | 326 since we can only show one failure per run for each test on the dashboard. |
270 """ | 327 """ |
271 failures = [failure.__class__ for failure in self._failures[test]] | 328 failures = [failure.__class__ for failure in self._failures[test]] |
| 329 return self._GetResultsCharForFailure(failures) |
272 | 330 |
| 331 def _GetResultsCharForFailure(self, failures): |
| 332 """Returns the worst failure from the list of failures |
| 333 since we can only show one failure per run for each test on the dashboard. |
| 334 """ |
273 if test_failures.FailureCrash in failures: | 335 if test_failures.FailureCrash in failures: |
274 return "C" | 336 return "C" |
275 elif test_failures.FailureTimeout in failures: | 337 elif test_failures.FailureTimeout in failures: |
276 return "T" | 338 return "T" |
277 elif test_failures.FailureImageHashMismatch in failures: | 339 elif test_failures.FailureImageHashMismatch in failures: |
278 return "I" | 340 return "I" |
279 elif test_failures.FailureSimplifiedTextMismatch in failures: | 341 elif test_failures.FailureSimplifiedTextMismatch in failures: |
280 return "S" | 342 return "S" |
281 elif test_failures.FailureTextMismatch in failures: | 343 elif test_failures.FailureTextMismatch in failures: |
282 return "F" | 344 return "F" |
(...skipping 50 matching lines...) Loading... |
333 | 395 |
334 class ResultAndTime: | 396 class ResultAndTime: |
335 """A holder for a single result and runtime for a test.""" | 397 """A holder for a single result and runtime for a test.""" |
336 def __init__(self, test, all_tests): | 398 def __init__(self, test, all_tests): |
337 self.time = 0 | 399 self.time = 0 |
338 # If the test was run, then we don't want to default the result to nodata. | 400 # If the test was run, then we don't want to default the result to nodata. |
339 if test in all_tests: | 401 if test in all_tests: |
340 self.result = JSONResultsGenerator.PASS_RESULT | 402 self.result = JSONResultsGenerator.PASS_RESULT |
341 else: | 403 else: |
342 self.result = JSONResultsGenerator.NO_DATA_RESULT | 404 self.result = JSONResultsGenerator.NO_DATA_RESULT |
OLD | NEW |