| 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 | |
| 8 import subprocess | 7 import subprocess |
| 9 import sys | 8 import sys |
| 10 import time | 9 import time |
| 11 import urllib2 | 10 import urllib2 |
| 12 import xml.dom.minidom | 11 import xml.dom.minidom |
| 13 | 12 |
| 14 from layout_package import path_utils | 13 from layout_package import path_utils |
| 15 from layout_package import test_expectations | 14 from layout_package import test_expectations |
| 16 from layout_package import test_failures | |
| 17 | 15 |
| 18 sys.path.append(path_utils.PathFromBase('third_party')) | 16 sys.path.append(path_utils.PathFromBase('third_party')) |
| 19 import simplejson | 17 import simplejson |
| 20 | 18 |
| 21 class JSONResultsGenerator: | 19 class JSONResultsGenerator(object): |
| 22 | 20 |
| 23 MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 750 | 21 MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 750 |
| 24 # Min time (seconds) that will be added to the JSON. | 22 # Min time (seconds) that will be added to the JSON. |
| 25 MIN_TIME = 1 | 23 MIN_TIME = 1 |
| 26 JSON_PREFIX = "ADD_RESULTS(" | 24 JSON_PREFIX = "ADD_RESULTS(" |
| 27 JSON_SUFFIX = ");" | 25 JSON_SUFFIX = ");" |
| 28 LAYOUT_TESTS_PATH = "LayoutTests" | |
| 29 PASS_RESULT = "P" | 26 PASS_RESULT = "P" |
| 30 SKIP_RESULT = "X" | 27 SKIP_RESULT = "X" |
| 31 NO_DATA_RESULT = "N" | 28 NO_DATA_RESULT = "N" |
| 32 VERSION = 3 | 29 VERSION = 3 |
| 33 VERSION_KEY = "version" | 30 VERSION_KEY = "version" |
| 34 RESULTS = "results" | 31 RESULTS = "results" |
| 35 TIMES = "times" | 32 TIMES = "times" |
| 36 BUILD_NUMBERS = "buildNumbers" | 33 BUILD_NUMBERS = "buildNumbers" |
| 37 WEBKIT_SVN = "webkitRevision" | 34 WEBKIT_SVN = "webkitRevision" |
| 38 CHROME_SVN = "chromeRevision" | 35 CHROME_SVN = "chromeRevision" |
| 39 TIME = "secondsSinceEpoch" | 36 TIME = "secondsSinceEpoch" |
| 40 TESTS = "tests" | 37 TESTS = "tests" |
| 41 WONTFIX = "wontfixCounts" | 38 |
| 42 DEFERRED = "deferredCounts" | 39 FIXABLE_COUNT = "fixableCount" |
| 43 FIXABLE = "fixableCounts" | 40 FIXABLE = "fixableCounts" |
| 44 ALL_FIXABLE_COUNT = "allFixableCount" | 41 ALL_FIXABLE_COUNT = "allFixableCount" |
| 45 FIXABLE_COUNT = "fixableCount" | |
| 46 | 42 |
| 47 # Note that we omit test_expectations.FAIL from this list because | 43 # Note that we omit test_expectations.FAIL from this list because |
| 48 # it should never show up (it's a legacy input expectation, never | 44 # it should never show up (it's a legacy input expectation, never |
| 49 # an output expectation). | 45 # an output expectation). |
| 50 FAILURE_TO_CHAR = { | 46 FAILURE_TO_CHAR = { |
| 51 test_expectations.CRASH : "C", | 47 test_expectations.CRASH : "C", |
| 52 test_expectations.TIMEOUT : "T", | 48 test_expectations.TIMEOUT : "T", |
| 53 test_expectations.IMAGE : "I", | 49 test_expectations.IMAGE : "I", |
| 54 test_expectations.TEXT : "F", | 50 test_expectations.TEXT : "F", |
| 55 test_expectations.MISSING : "O", | 51 test_expectations.MISSING : "O", |
| 56 test_expectations.IMAGE_PLUS_TEXT : "Z" | 52 test_expectations.IMAGE_PLUS_TEXT : "Z" |
| 57 } | 53 } |
| 58 FAILURE_CHARS = FAILURE_TO_CHAR.values() | 54 FAILURE_CHARS = FAILURE_TO_CHAR.values() |
| 59 | 55 |
| 60 BUILDER_BASE_URL = "http://build.chromium.org/buildbot/layout_test_results/" | |
| 61 RESULTS_FILENAME = "results.json" | 56 RESULTS_FILENAME = "results.json" |
| 62 | 57 |
| 63 def __init__(self, options, expectations, result_summary, | 58 def __init__(self, builder_name, build_name, build_number, |
| 64 individual_test_timings, results_file_base_path, all_tests): | 59 results_file_base_path, builder_base_url, |
| 60 test_timings, failures, passed_tests, skipped_tests, all_tests): |
| 65 """Modifies the results.json file. Grabs it off the archive directory if it | 61 """Modifies the results.json file. Grabs it off the archive directory if it |
| 66 is not found locally. | 62 is not found locally. |
| 67 | 63 |
| 68 Args | 64 Args |
| 69 options: a dictionary of command line options | 65 builder_name: the builder name (e.g. Webkit). |
| 70 expectations: TestExpectations object with what we expected to get | 66 build_name: the build name (e.g. webkit-rel). |
| 71 result_summary: ResultsSummary object containing failure counts for | 67 build_number: the build number. |
| 72 different groups of tests. | |
| 73 individual_test_times: Map of test name to a tuple containing the | |
| 74 test_run-time. | |
| 75 results_file_base_path: Absolute path to the directory containing the | 68 results_file_base_path: Absolute path to the directory containing the |
| 76 results json file. | 69 results json file. |
| 77 all_tests: List of all the tests that were run. | 70 builder_base_url: the URL where we have the archived test results. |
| 71 test_timings: Map of test name to a test_run-time. |
| 72 failures: Map of test name to a failure type (of test_expectations). |
| 73 passed_tests: A set containing all the passed tests. |
| 74 skipped_tests: A set containing all the skipped tests. |
| 75 all_tests: List of all the tests that were run. This should not include |
| 76 skipped tests. |
| 78 """ | 77 """ |
| 79 # Make sure all test paths are relative to the layout test root directory. | 78 self._builder_name = builder_name |
| 80 self._failures = {} | 79 self._build_name = build_name |
| 81 for test in result_summary.failures: | 80 self._build_number = build_number |
| 82 test_path = self._GetPathRelativeToLayoutTestRoot(test) | 81 self._builder_base_url = builder_base_url |
| 83 self._failures[test_path] = result_summary.failures[test] | |
| 84 | |
| 85 self._all_tests = [self._GetPathRelativeToLayoutTestRoot(test) | |
| 86 for test in all_tests] | |
| 87 | |
| 88 self._expectations = expectations | |
| 89 self._result_summary = result_summary | |
| 90 | |
| 91 self._test_timings = {} | |
| 92 for test_tuple in individual_test_timings: | |
| 93 test_path = self._GetPathRelativeToLayoutTestRoot(test_tuple.filename) | |
| 94 self._test_timings[test_path] = test_tuple.test_run_time | |
| 95 | |
| 96 self._options = options | |
| 97 self._results_file_path = os.path.join(results_file_base_path, | 82 self._results_file_path = os.path.join(results_file_base_path, |
| 98 self.RESULTS_FILENAME) | 83 self.RESULTS_FILENAME) |
| 84 self._test_timings = test_timings |
| 85 self._failures = failures |
| 86 self._passed_tests = passed_tests |
| 87 self._skipped_tests = skipped_tests |
| 88 self._all_tests = all_tests |
| 99 | 89 |
| 90 self._GenerateJSONOutput() |
| 91 |
| 92 def _GenerateJSONOutput(self): |
| 93 """Generates the JSON output file.""" |
| 100 json = self._GetJSON() | 94 json = self._GetJSON() |
| 101 if json: | 95 if json: |
| 102 results_file = open(self._results_file_path, "w") | 96 results_file = open(self._results_file_path, "w") |
| 103 results_file.write(json) | 97 results_file.write(json) |
| 104 results_file.close() | 98 results_file.close() |
| 105 | 99 |
| 106 def _GetSVNRevision(self, in_directory=None): | 100 def _GetSVNRevision(self, in_directory=None): |
| 107 """Returns the svn revision for the given directory. | 101 """Returns the svn revision for the given directory. |
| 108 | 102 |
| 109 Args: | 103 Args: |
| 110 in_directory: The directory where svn is to be run. | 104 in_directory: The directory where svn is to be run. |
| 111 """ | 105 """ |
| 112 output = subprocess.Popen(["svn", "info", "--xml"], | 106 output = subprocess.Popen(["svn", "info", "--xml"], |
| 113 cwd=in_directory, | 107 cwd=in_directory, |
| 114 shell=(sys.platform == 'win32'), | 108 shell=(sys.platform == 'win32'), |
| 115 stdout=subprocess.PIPE).communicate()[0] | 109 stdout=subprocess.PIPE).communicate()[0] |
| 116 try : | 110 try : |
| 117 dom = xml.dom.minidom.parseString(output) | 111 dom = xml.dom.minidom.parseString(output) |
| 118 return dom.getElementsByTagName('entry')[0].getAttribute('revision'); | 112 return dom.getElementsByTagName('entry')[0].getAttribute('revision'); |
| 119 except xml.parsers.expat.ExpatError: | 113 except xml.parsers.expat.ExpatError: |
| 120 return "" | 114 return "" |
| 121 | 115 |
| 122 def _GetPathRelativeToLayoutTestRoot(self, test): | 116 def _GetArchivedJSONResults(self): |
| 123 """Returns the path of the test relative to the layout test root. | 117 """Reads old results JSON file if it exists. |
| 124 For example, for: | 118 Returns (archived_results, error) tuple where error is None if results |
| 125 src/third_party/WebKit/LayoutTests/fast/forms/foo.html | 119 were successfully read. |
| 126 We would return | |
| 127 fast/forms/foo.html | |
| 128 """ | 120 """ |
| 129 index = test.find(self.LAYOUT_TESTS_PATH) | 121 results_json = {} |
| 130 if index is not -1: | 122 old_results = None |
| 131 index += len(self.LAYOUT_TESTS_PATH) | 123 error = None |
| 132 | 124 |
| 133 if index is -1: | |
| 134 # Already a relative path. | |
| 135 relativePath = test | |
| 136 else: | |
| 137 relativePath = test[index + 1:] | |
| 138 | |
| 139 # Make sure all paths are unix-style. | |
| 140 return relativePath.replace('\\', '/') | |
| 141 | |
| 142 def _GetJSON(self): | |
| 143 """Gets the results for the results.json file.""" | |
| 144 failures_for_json = {} | |
| 145 for test in self._failures: | |
| 146 failures_for_json[test] = ResultAndTime(test, self._all_tests) | |
| 147 failures_for_json[test].result = self._GetResultsCharForTest(test) | |
| 148 | |
| 149 for test in self._test_timings: | |
| 150 if not test in failures_for_json: | |
| 151 failures_for_json[test] = ResultAndTime(test, self._all_tests) | |
| 152 # Floor for now to get time in seconds. | |
| 153 failures_for_json[test].time = int(self._test_timings[test]) | |
| 154 | |
| 155 # If results file exists, read it out, put new info in it. | |
| 156 if os.path.exists(self._results_file_path): | 125 if os.path.exists(self._results_file_path): |
| 157 old_results_file = open(self._results_file_path, "r") | 126 old_results_file = open(self._results_file_path, "r") |
| 158 old_results = old_results_file.read() | 127 old_results = old_results_file.read() |
| 159 else: | 128 elif self._builder_base_url: |
| 160 results_file_url = (self.BUILDER_BASE_URL + self._options.build_name + | 129 # Check if we have the archived JSON file on the buildbot server. |
| 161 "/" + self.RESULTS_FILENAME) | 130 results_file_url = (self._builder_base_url + |
| 131 self._build_name + "/" + self.RESULTS_FILENAME) |
| 162 logging.error("Local results.json file does not exist. Grabbing it off " | 132 logging.error("Local results.json file does not exist. Grabbing it off " |
| 163 "the archive at " + results_file_url) | 133 "the archive at " + results_file_url) |
| 164 | 134 |
| 165 error = None | |
| 166 old_results = None | |
| 167 try: | 135 try: |
| 168 results_file = urllib2.urlopen(results_file_url) | 136 results_file = urllib2.urlopen(results_file_url) |
| 169 info = results_file.info() | 137 info = results_file.info() |
| 170 old_results = results_file.read() | 138 old_results = results_file.read() |
| 171 except urllib2.HTTPError, http_error: | 139 except urllib2.HTTPError, http_error: |
| 172 # A non-4xx status code means the bot is hosed for some reason and we | 140 # A non-4xx status code means the bot is hosed for some reason and we |
| 173 # can't grab the results.json file off of it. | 141 # can't grab the results.json file off of it. |
| 174 if (http_error.code < 400 and http_error.code >= 500): | 142 if (http_error.code < 400 and http_error.code >= 500): |
| 175 error = http_error | 143 error = http_error |
| 176 except urllib2.URLError, url_error: | 144 except urllib2.URLError, url_error: |
| 177 error = url_error | 145 error = url_error |
| 178 | 146 |
| 179 if error: | |
| 180 # If there was a URL/HTTPError don't write a results.json | |
| 181 # file at all as it would lose all the information on the bot. | |
| 182 logging.error("Archive directory is inaccessible. Not modifying or " | |
| 183 "clobbering the results.json file: " + str(error)) | |
| 184 return None | |
| 185 | |
| 186 builder_name = self._options.builder_name | |
| 187 if old_results: | 147 if old_results: |
| 188 # Strip the prefix and suffix so we can get the actual JSON object. | 148 # Strip the prefix and suffix so we can get the actual JSON object. |
| 189 old_results = old_results[ | 149 old_results = old_results[ |
| 190 len(self.JSON_PREFIX) : len(old_results) - len(self.JSON_SUFFIX)] | 150 len(self.JSON_PREFIX) : len(old_results) - len(self.JSON_SUFFIX)] |
| 191 | 151 |
| 192 try: | 152 try: |
| 193 results_json = simplejson.loads(old_results) | 153 results_json = simplejson.loads(old_results) |
| 194 except: | 154 except: |
| 195 logging.debug("results.json was not valid JSON. Clobbering.") | 155 logging.debug("results.json was not valid JSON. Clobbering.") |
| 196 # The JSON file is not valid JSON. Just clobber the results. | 156 # The JSON file is not valid JSON. Just clobber the results. |
| 197 results_json = {} | 157 results_json = {} |
| 198 | |
| 199 if builder_name not in results_json: | |
| 200 logging.debug("Builder name (%s) is not in the results.json file." % | |
| 201 builder_name); | |
| 202 else: | 158 else: |
| 203 logging.debug('Old JSON results do not exist. Starting fresh.'); | 159 logging.debug('Old JSON results do not exist. Starting fresh.'); |
| 204 results_json = {} | 160 results_json = {} |
| 205 | 161 |
| 162 return results_json, error |
| 163 |
| 164 def _GetJSON(self): |
| 165 """Gets the results for the results.json file.""" |
| 166 results_json, error = self._GetArchivedJSONResults() |
| 167 if error: |
| 168 # If there was an error don't write a results.json |
| 169 # file at all as it would lose all the information on the bot. |
| 170 logging.error("Archive directory is inaccessible. Not modifying or " |
| 171 "clobbering the results.json file: " + str(error)) |
| 172 return None |
| 173 |
| 174 builder_name = self._builder_name |
| 175 if results_json and builder_name not in results_json: |
| 176 logging.debug("Builder name (%s) is not in the results.json file." % |
| 177 builder_name); |
| 178 |
| 206 self._ConvertJSONToCurrentVersion(results_json) | 179 self._ConvertJSONToCurrentVersion(results_json) |
| 207 | 180 |
| 208 if builder_name not in results_json: | 181 if builder_name not in results_json: |
| 209 results_json[builder_name] = self._CreateResultsForBuilderJSON() | 182 results_json[builder_name] = self._CreateResultsForBuilderJSON() |
| 210 | 183 |
| 211 results_for_builder = results_json[builder_name] | 184 results_for_builder = results_json[builder_name] |
| 185 |
| 186 self._InsertGenericMetadata(results_for_builder) |
| 187 |
| 188 self._InsertFailureSummaries(results_for_builder) |
| 189 |
| 190 # Update the all failing tests with result type and time. |
| 212 tests = results_for_builder[self.TESTS] | 191 tests = results_for_builder[self.TESTS] |
| 213 all_failing_tests = set(self._failures.iterkeys()) | 192 all_failing_tests = set(self._failures.iterkeys()) |
| 214 all_failing_tests.update(tests.iterkeys()) | 193 all_failing_tests.update(tests.iterkeys()) |
| 215 | |
| 216 self._InsertItemIntoRawList(results_for_builder, | |
| 217 self._options.build_number, self.BUILD_NUMBERS) | |
| 218 | |
| 219 path_to_webkit = path_utils.PathFromBase('third_party', 'WebKit', 'WebCore') | |
| 220 self._InsertItemIntoRawList(results_for_builder, | |
| 221 self._GetSVNRevision(path_to_webkit), | |
| 222 self.WEBKIT_SVN) | |
| 223 | |
| 224 path_to_chrome_base = path_utils.PathFromBase() | |
| 225 self._InsertItemIntoRawList(results_for_builder, | |
| 226 self._GetSVNRevision(path_to_chrome_base), | |
| 227 self.CHROME_SVN) | |
| 228 | |
| 229 self._InsertItemIntoRawList(results_for_builder, | |
| 230 int(time.time()), | |
| 231 self.TIME) | |
| 232 | |
| 233 self._InsertFailureSummaries(results_for_builder) | |
| 234 | |
| 235 for test in all_failing_tests: | 194 for test in all_failing_tests: |
| 236 if test in failures_for_json: | 195 self._InsertTestTimeAndResult(test, tests) |
| 237 result_and_time = failures_for_json[test] | |
| 238 else: | |
| 239 result_and_time = ResultAndTime(test, self._all_tests) | |
| 240 | |
| 241 if test not in tests: | |
| 242 tests[test] = self._CreateResultsAndTimesJSON() | |
| 243 | |
| 244 thisTest = tests[test] | |
| 245 self._InsertItemRunLengthEncoded(result_and_time.result, | |
| 246 thisTest[self.RESULTS]) | |
| 247 self._InsertItemRunLengthEncoded(result_and_time.time, | |
| 248 thisTest[self.TIMES]) | |
| 249 self._NormalizeResultsJSON(thisTest, test, tests) | |
| 250 | 196 |
| 251 # Specify separators in order to get compact encoding. | 197 # Specify separators in order to get compact encoding. |
| 252 results_str = simplejson.dumps(results_json, separators=(',', ':')) | 198 results_str = simplejson.dumps(results_json, separators=(',', ':')) |
| 253 return self.JSON_PREFIX + results_str + self.JSON_SUFFIX | 199 return self.JSON_PREFIX + results_str + self.JSON_SUFFIX |
| 254 | 200 |
| 255 def _InsertFailureSummaries(self, results_for_builder): | 201 def _InsertFailureSummaries(self, results_for_builder): |
| 256 """Inserts aggregate pass/failure statistics into the JSON. | 202 """Inserts aggregate pass/failure statistics into the JSON. |
| 203 This method reads self._skipped_tests, self._passed_tests and |
| 204 self._failures and inserts FIXABLE, FIXABLE_COUNT and ALL_FIXABLE_COUNT |
| 205 entries. |
| 257 | 206 |
| 258 Args: | 207 Args: |
| 259 results_for_builder: Dictionary containing the test results for a single | 208 results_for_builder: Dictionary containing the test results for a single |
| 260 builder. | 209 builder. |
| 261 """ | 210 """ |
| 262 summary = self._result_summary | 211 # Insert the number of tests that failed. |
| 263 self._InsertItemIntoRawList(results_for_builder, | 212 self._InsertItemIntoRawList(results_for_builder, |
| 264 len((set(summary.failures.keys()) | | 213 len(set(self._failures.keys()) | self._skipped_tests), |
| 265 summary.tests_by_expectation[test_expectations.SKIP]) & | |
| 266 summary.tests_by_timeline[test_expectations.NOW]), | |
| 267 self.FIXABLE_COUNT) | 214 self.FIXABLE_COUNT) |
| 215 |
| 216 # Create a pass/skip/failure summary dictionary. |
| 217 entry = {} |
| 218 entry[self.SKIP_RESULT] = len(self._skipped_tests) |
| 219 entry[self.PASS_RESULT] = len(self._passed_tests) |
| 220 get = entry.get |
| 221 for failure_type in self._failures.values(): |
| 222 failure_char = self.FAILURE_TO_CHAR[failure_type] |
| 223 entry[failure_char] = get(failure_char, 0) + 1 |
| 224 |
| 225 # Insert the pass/skip/failure summary dictionary. |
| 226 self._InsertItemIntoRawList(results_for_builder, entry, self.FIXABLE) |
| 227 |
| 228 # Insert the number of all the tests that are supposed to pass. |
| 268 self._InsertItemIntoRawList(results_for_builder, | 229 self._InsertItemIntoRawList(results_for_builder, |
| 269 len(self._expectations.GetTestsWithTimeline(test_expectations.NOW)), | 230 len(self._skipped_tests | self._all_tests), |
| 270 self.ALL_FIXABLE_COUNT) | 231 self.ALL_FIXABLE_COUNT) |
| 271 self._InsertItemIntoRawList(results_for_builder, | |
| 272 self._GetFailureSummaryEntry(test_expectations.DEFER), | |
| 273 self.DEFERRED) | |
| 274 self._InsertItemIntoRawList(results_for_builder, | |
| 275 self._GetFailureSummaryEntry(test_expectations.WONTFIX), | |
| 276 self.WONTFIX) | |
| 277 self._InsertItemIntoRawList(results_for_builder, | |
| 278 self._GetFailureSummaryEntry(test_expectations.NOW), | |
| 279 self.FIXABLE) | |
| 280 | |
| 281 def _GetFailureSummaryEntry(self, timeline): | |
| 282 """Creates a summary object to insert into the JSON. | |
| 283 | |
| 284 Args: | |
| 285 summary ResultSummary object with test results | |
| 286 timeline current test_expectations timeline to build entry for (e.g., | |
| 287 test_expectations.NOW, etc.) | |
| 288 """ | |
| 289 entry = {} | |
| 290 summary = self._result_summary | |
| 291 timeline_tests = summary.tests_by_timeline[timeline] | |
| 292 entry[self.SKIP_RESULT] = len( | |
| 293 summary.tests_by_expectation[test_expectations.SKIP] & timeline_tests) | |
| 294 entry[self.PASS_RESULT] = len( | |
| 295 summary.tests_by_expectation[test_expectations.PASS] & timeline_tests) | |
| 296 for failure_type in summary.tests_by_expectation.keys(): | |
| 297 if failure_type not in self.FAILURE_TO_CHAR: | |
| 298 continue | |
| 299 count = len(summary.tests_by_expectation[failure_type] & timeline_tests) | |
| 300 entry[self.FAILURE_TO_CHAR[failure_type]] = count | |
| 301 return entry | |
| 302 | |
| 303 def _GetResultsCharForTest(self, test): | |
| 304 """Returns a single character description of the test result.""" | |
| 305 result = test_failures.DetermineResultType(self._failures[test]) | |
| 306 return self.FAILURE_TO_CHAR[result] | |
| 307 | 232 |
| 308 def _InsertItemIntoRawList(self, results_for_builder, item, key): | 233 def _InsertItemIntoRawList(self, results_for_builder, item, key): |
| 309 """Inserts the item into the list with the given key in the results for | 234 """Inserts the item into the list with the given key in the results for |
| 310 this builder. Creates the list if no such list exists. | 235 this builder. Creates the list if no such list exists. |
| 311 | 236 |
| 312 Args: | 237 Args: |
| 313 results_for_builder: Dictionary containing the test results for a single | 238 results_for_builder: Dictionary containing the test results for a single |
| 314 builder. | 239 builder. |
| 315 item: Number or string to insert into the list. | 240 item: Number or string to insert into the list. |
| 316 key: Key in results_for_builder for the list to insert into. | 241 key: Key in results_for_builder for the list to insert into. |
| (...skipping 17 matching lines...) Expand all Loading... |
| 334 """ | 259 """ |
| 335 if len(encoded_results) and item == encoded_results[0][1]: | 260 if len(encoded_results) and item == encoded_results[0][1]: |
| 336 num_results = encoded_results[0][0] | 261 num_results = encoded_results[0][0] |
| 337 if num_results <= self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG: | 262 if num_results <= self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG: |
| 338 encoded_results[0][0] = num_results + 1 | 263 encoded_results[0][0] = num_results + 1 |
| 339 else: | 264 else: |
| 340 # Use a list instead of a class for the run-length encoding since we | 265 # Use a list instead of a class for the run-length encoding since we |
| 341 # want the serialized form to be concise. | 266 # want the serialized form to be concise. |
| 342 encoded_results.insert(0, [1, item]) | 267 encoded_results.insert(0, [1, item]) |
| 343 | 268 |
| 269 def _InsertGenericMetadata(self, results_for_builder): |
| 270 """ Inserts generic metadata (such as version number, current time etc) |
| 271 into the JSON. |
| 272 |
| 273 Args: |
| 274 results_for_builder: Dictionary containing the test results for a single |
| 275 builder. |
| 276 """ |
| 277 self._InsertItemIntoRawList(results_for_builder, |
| 278 self._build_number, self.BUILD_NUMBERS) |
| 279 |
| 280 path_to_webkit = path_utils.PathFromBase('third_party', 'WebKit', 'WebCore') |
| 281 self._InsertItemIntoRawList(results_for_builder, |
| 282 self._GetSVNRevision(path_to_webkit), |
| 283 self.WEBKIT_SVN) |
| 284 |
| 285 path_to_chrome_base = path_utils.PathFromBase() |
| 286 self._InsertItemIntoRawList(results_for_builder, |
| 287 self._GetSVNRevision(path_to_chrome_base), |
| 288 self.CHROME_SVN) |
| 289 |
| 290 self._InsertItemIntoRawList(results_for_builder, |
| 291 int(time.time()), |
| 292 self.TIME) |
| 293 |
| 294 def _InsertTestTimeAndResult(self, test_name, tests): |
| 295 """ Insert a test item with its results to the given tests dictionary. |
| 296 |
| 297 Args: |
| 298 tests: Dictionary containing test result entries. |
| 299 """ |
| 300 |
| 301 result = JSONResultsGenerator.PASS_RESULT |
| 302 time = 0 |
| 303 |
| 304 if test_name not in self._all_tests: |
| 305 result = JSONResultsGenerator.NO_DATA_RESULT |
| 306 |
| 307 if test_name in self._failures: |
| 308 result = self.FAILURE_TO_CHAR[self._failures[test_name]] |
| 309 |
| 310 if test_name in self._test_timings: |
| 311 # Floor for now to get time in seconds. |
| 312 time = int(self._test_timings[test_name]) |
| 313 |
| 314 if test_name not in tests: |
| 315 tests[test_name] = self._CreateResultsAndTimesJSON() |
| 316 |
| 317 thisTest = tests[test_name] |
| 318 self._InsertItemRunLengthEncoded(result, thisTest[self.RESULTS]) |
| 319 self._InsertItemRunLengthEncoded(time, thisTest[self.TIMES]) |
| 320 self._NormalizeResultsJSON(thisTest, test_name, tests) |
| 321 |
| 344 def _ConvertJSONToCurrentVersion(self, results_json): | 322 def _ConvertJSONToCurrentVersion(self, results_json): |
| 345 """If the JSON does not match the current version, converts it to the | 323 """If the JSON does not match the current version, converts it to the |
| 346 current version and adds in the new version number. | 324 current version and adds in the new version number. |
| 347 """ | 325 """ |
| 348 if (self.VERSION_KEY in results_json and | 326 if (self.VERSION_KEY in results_json and |
| 349 results_json[self.VERSION_KEY] == self.VERSION): | 327 results_json[self.VERSION_KEY] == self.VERSION): |
| 350 return | 328 return |
| 351 | 329 |
| 352 if results_json[self.VERSION_KEY] == 2: | |
| 353 for results_for_builder in results_json.itervalues(): | |
| 354 try: | |
| 355 test_results = results_for_builder[self.TESTS] | |
| 356 except: | |
| 357 continue | |
| 358 | |
| 359 for test in test_results: | |
| 360 # Make sure all paths are relative | |
| 361 test_path = self._GetPathRelativeToLayoutTestRoot(test) | |
| 362 if test_path != test: | |
| 363 test_results[test_path] = test_results[test] | |
| 364 del test_results[test] | |
| 365 | |
| 366 results_json[self.VERSION_KEY] = self.VERSION | 330 results_json[self.VERSION_KEY] = self.VERSION |
| 367 | 331 |
| 368 def _CreateResultsAndTimesJSON(self): | 332 def _CreateResultsAndTimesJSON(self): |
| 369 results_and_times = {} | 333 results_and_times = {} |
| 370 results_and_times[self.RESULTS] = [] | 334 results_and_times[self.RESULTS] = [] |
| 371 results_and_times[self.TIMES] = [] | 335 results_and_times[self.TIMES] = [] |
| 372 return results_and_times | 336 return results_and_times |
| 373 | 337 |
| 374 def _CreateResultsForBuilderJSON(self): | 338 def _CreateResultsForBuilderJSON(self): |
| 375 results_for_builder = {} | 339 results_for_builder = {} |
| (...skipping 10 matching lines...) Expand all Loading... |
| 386 """ | 350 """ |
| 387 num_builds = 0 | 351 num_builds = 0 |
| 388 index = 0 | 352 index = 0 |
| 389 for result in encoded_list: | 353 for result in encoded_list: |
| 390 num_builds = num_builds + result[0] | 354 num_builds = num_builds + result[0] |
| 391 index = index + 1 | 355 index = index + 1 |
| 392 if num_builds > self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG: | 356 if num_builds > self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG: |
| 393 return encoded_list[:index] | 357 return encoded_list[:index] |
| 394 return encoded_list | 358 return encoded_list |
| 395 | 359 |
| 396 def _NormalizeResultsJSON(self, test, test_path, tests): | 360 def _NormalizeResultsJSON(self, test, test_name, tests): |
| 397 """ Prune tests where all runs pass or tests that no longer exist and | 361 """ Prune tests where all runs pass or tests that no longer exist and |
| 398 truncate all results to maxNumberOfBuilds. | 362 truncate all results to maxNumberOfBuilds. |
| 399 | 363 |
| 400 Args: | 364 Args: |
| 401 test: ResultsAndTimes object for this test. | 365 test: ResultsAndTimes object for this test. |
| 402 test_path: Path to the test. | 366 test_name: Name of the test. |
| 403 tests: The JSON object with all the test results for this builder. | 367 tests: The JSON object with all the test results for this builder. |
| 404 """ | 368 """ |
| 405 test[self.RESULTS] = self._RemoveItemsOverMaxNumberOfBuilds( | 369 test[self.RESULTS] = self._RemoveItemsOverMaxNumberOfBuilds( |
| 406 test[self.RESULTS]) | 370 test[self.RESULTS]) |
| 407 test[self.TIMES] = self._RemoveItemsOverMaxNumberOfBuilds(test[self.TIMES]) | 371 test[self.TIMES] = self._RemoveItemsOverMaxNumberOfBuilds(test[self.TIMES]) |
| 408 | 372 |
| 409 is_all_pass = self._IsResultsAllOfType(test[self.RESULTS], self.PASS_RESULT) | 373 is_all_pass = self._IsResultsAllOfType(test[self.RESULTS], self.PASS_RESULT) |
| 410 is_all_no_data = self._IsResultsAllOfType(test[self.RESULTS], | 374 is_all_no_data = self._IsResultsAllOfType(test[self.RESULTS], |
| 411 self.NO_DATA_RESULT) | 375 self.NO_DATA_RESULT) |
| 412 max_time = max([time[1] for time in test[self.TIMES]]) | 376 max_time = max([time[1] for time in test[self.TIMES]]) |
| 413 | 377 |
| 414 # Remove all passes/no-data from the results to reduce noise and filesize. | 378 # Remove all passes/no-data from the results to reduce noise and filesize. |
| 415 # If a test passes every run, but takes > MIN_TIME to run, don't throw away | 379 # If a test passes every run, but takes > MIN_TIME to run, don't throw away |
| 416 # the data. | 380 # the data. |
| 417 if is_all_no_data or (is_all_pass and max_time <= self.MIN_TIME): | 381 if is_all_no_data or (is_all_pass and max_time <= self.MIN_TIME): |
| 418 del tests[test_path] | 382 del tests[test_name] |
| 419 else: | |
| 420 # Remove tests that don't exist anymore. | |
| 421 full_path = os.path.join(path_utils.LayoutTestsDir(), test_path) | |
| 422 full_path = os.path.normpath(full_path) | |
| 423 if not os.path.exists(full_path): | |
| 424 del tests[test_path] | |
| 425 | 383 |
| 426 def _IsResultsAllOfType(self, results, type): | 384 def _IsResultsAllOfType(self, results, type): |
| 427 """Returns whether all teh results are of the given type (e.g. all passes). | 385 """Returns whether all teh results are of the given type (e.g. all passes). |
| 428 """ | 386 """ |
| 429 return len(results) == 1 and results[0][1] == type | 387 return len(results) == 1 and results[0][1] == type |
| 430 | |
| 431 class ResultAndTime: | |
| 432 """A holder for a single result and runtime for a test.""" | |
| 433 def __init__(self, test, all_tests): | |
| 434 self.time = 0 | |
| 435 # If the test was run, then we don't want to default the result to nodata. | |
| 436 if test in all_tests: | |
| 437 self.result = JSONResultsGenerator.PASS_RESULT | |
| 438 else: | |
| 439 self.result = JSONResultsGenerator.NO_DATA_RESULT | |
| OLD | NEW |