| 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...) Expand 10 before | Expand all | Expand 10 after 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...) Expand 10 before | Expand all | Expand 10 after 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...) Expand 10 before | Expand all | Expand 10 after 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...) Expand 10 before | Expand all | Expand 10 after 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 |