OLD | NEW |
---|---|
1 # Copyright (C) 2013 Google Inc. All rights reserved. | 1 # Copyright (C) 2013 Google Inc. All rights reserved. |
2 # | 2 # |
3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
5 # met: | 5 # met: |
6 # | 6 # |
7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
(...skipping 18 matching lines...) Expand all Loading... | |
29 """Generates a fake TestExpectations file consisting of flaky tests from the bot | 29 """Generates a fake TestExpectations file consisting of flaky tests from the bot |
30 corresponding to the give port.""" | 30 corresponding to the give port.""" |
31 | 31 |
32 import json | 32 import json |
33 import logging | 33 import logging |
34 import os.path | 34 import os.path |
35 import urllib | 35 import urllib |
36 import urllib2 | 36 import urllib2 |
37 | 37 |
38 from webkitpy.layout_tests.port import builders | 38 from webkitpy.layout_tests.port import builders |
39 from webkitpy.layout_tests.models.test_expectations import TestExpectations | 39 from webkitpy.layout_tests.models.test_expectations import TestExpectations, PAS S |
40 from webkitpy.layout_tests.models.test_expectations import TestExpectationLine | 40 from webkitpy.layout_tests.models.test_expectations import TestExpectationLine |
41 | 41 |
42 | 42 |
43 _log = logging.getLogger(__name__) | 43 _log = logging.getLogger(__name__) |
44 | 44 |
45 | 45 |
46 # results.json v4 format: | 46 # results.json v4 format: |
47 # { | 47 # { |
48 # 'version': 4, | 48 # 'version': 4, |
49 # 'builder name' : { | 49 # 'builder name' : { |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
141 def expectations_for_builder(self, builder): | 141 def expectations_for_builder(self, builder): |
142 results_json = self._results_json_for_builder(builder) | 142 results_json = self._results_json_for_builder(builder) |
143 if not results_json: | 143 if not results_json: |
144 return None | 144 return None |
145 return BotTestExpectations(results_json) | 145 return BotTestExpectations(results_json) |
146 | 146 |
147 class BotTestExpectations(object): | 147 class BotTestExpectations(object): |
148 # FIXME: Get this from the json instead of hard-coding it. | 148 # FIXME: Get this from the json instead of hard-coding it. |
149 RESULT_TYPES_TO_IGNORE = ['N', 'X', 'Y'] # NO_DATA, SKIP, NOTRUN | 149 RESULT_TYPES_TO_IGNORE = ['N', 'X', 'Y'] # NO_DATA, SKIP, NOTRUN |
150 | 150 |
151 # TODO(ojan): Remove this once crbug.com/514378 is fixed. | 151 # TODO(ojan): Remove this once crbug.com/514378 is fixed. |
joelo
2015/08/17 23:43:29
should be able to remove now?
| |
152 # The JSON can contain results for expectations, not just actual result type s. | 152 # The JSON can contain results for expectations, not just actual result type s. |
153 NON_RESULT_TYPES = ['S', 'X'] # SLOW, SKIP | 153 NON_RESULT_TYPES = ['S', 'X'] # SLOW, SKIP |
154 | 154 |
155 PASS_EXPECTATION = 'PASS' | |
156 | |
157 # specifiers arg is used in unittests to avoid the static dependency on buil ders. | 155 # specifiers arg is used in unittests to avoid the static dependency on buil ders. |
158 def __init__(self, results_json, specifiers=None): | 156 def __init__(self, results_json, specifiers=None): |
159 self.results_json = results_json | 157 self.results_json = results_json |
160 self.specifiers = specifiers or set(builders.specifiers_for_builder(resu lts_json.builder_name)) | 158 self.specifiers = specifiers or set(builders.specifiers_for_builder(resu lts_json.builder_name)) |
161 | 159 |
162 def _line_from_test_and_flaky_types(self, test_path, flaky_types): | 160 def _line_from_test_and_flaky_types(self, test_path, flaky_types): |
163 line = TestExpectationLine() | 161 line = TestExpectationLine() |
164 line.original_string = test_path | 162 line.original_string = test_path |
165 line.name = test_path | 163 line.name = test_path |
166 line.filename = test_path | 164 line.filename = test_path |
167 line.path = test_path # FIXME: Should this be normpath? | 165 line.path = test_path # FIXME: Should this be normpath? |
168 line.matching_tests = [test_path] | 166 line.matching_tests = [test_path] |
169 line.bugs = ["crbug.com/FILE_A_BUG_BEFORE_COMMITTING_THIS"] | 167 line.bugs = ["crbug.com/FILE_A_BUG_BEFORE_COMMITTING_THIS"] |
170 line.expectations = sorted(map(self.results_json.expectation_for_type, f laky_types)) | 168 line.expectations = sorted(flaky_types) |
171 line.specifiers = self.specifiers | 169 line.specifiers = self.specifiers |
172 return line | 170 return line |
173 | 171 |
174 def flakes_by_path(self, only_ignore_very_flaky): | 172 def flakes_by_path(self, only_ignore_very_flaky): |
175 """Sets test expectations to bot results if there are at least two disti nct results.""" | 173 """Sets test expectations to bot results if there are at least two disti nct results.""" |
176 flakes_by_path = {} | 174 flakes_by_path = {} |
177 for test_path, entry in self.results_json.walk_results(): | 175 for test_path, entry in self.results_json.walk_results(): |
178 flaky_types = self._flaky_types_in_results(entry, only_ignore_very_f laky) | 176 flaky_types = self._flaky_types_in_results(entry, only_ignore_very_f laky) |
179 if len(flaky_types) <= 1: | 177 if len(flaky_types) <= 1: |
180 continue | 178 continue |
181 flakes_by_path[test_path] = sorted(map(self.results_json.expectation _for_type, flaky_types)) | 179 flakes_by_path[test_path] = sorted(flaky_types) |
182 return flakes_by_path | 180 return flakes_by_path |
183 | 181 |
184 def unexpected_results_by_path(self): | 182 def unexpected_results_by_path(self): |
185 """For tests with unexpected results, returns original expectations + re sults.""" | 183 """For tests with unexpected results, returns original expectations + re sults.""" |
186 def exp_to_string(exp): | 184 def exp_to_string(exp): |
187 return TestExpectations.EXPECTATIONS_TO_STRING.get(exp, None).upper( ) | 185 return TestExpectations.EXPECTATIONS_TO_STRING.get(exp, None).upper( ) |
188 | 186 |
189 def string_to_exp(string): | 187 def string_to_exp(string): |
190 # Needs a bit more logic than the method above, | 188 # Needs a bit more logic than the method above, |
191 # since a PASS is 0 and evaluates to False. | 189 # since a PASS is 0 and evaluates to False. |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
240 | 238 |
241 for result_item in run_length_encoded_results: | 239 for result_item in run_length_encoded_results: |
242 _, result_types = self.results_json.occurances_and_type_from_result_ item(result_item) | 240 _, result_types = self.results_json.occurances_and_type_from_result_ item(result_item) |
243 | 241 |
244 for result_type in result_types: | 242 for result_type in result_types: |
245 if result_type not in self.RESULT_TYPES_TO_IGNORE: | 243 if result_type not in self.RESULT_TYPES_TO_IGNORE: |
246 results.add(result_type) | 244 results.add(result_type) |
247 | 245 |
248 return results | 246 return results |
249 | 247 |
248 def _result_to_enum(self, result): | |
249 return TestExpectations.EXPECTATIONS[result.lower()] | |
250 | |
250 def _flaky_types_in_results(self, results_entry, only_ignore_very_flaky): | 251 def _flaky_types_in_results(self, results_entry, only_ignore_very_flaky): |
251 flaky_results = set() | 252 flaky_results = set() |
252 | 253 |
253 latest_expectations = [self.PASS_EXPECTATION] | 254 # Always include pass as an expected result. Passes will never turn the bot red. |
255 # This fixes cases where the expectations have an implicit Pass, e.g. [ Slow ]. | |
256 latest_expectations = [PASS] | |
254 if self.results_json.EXPECTATIONS_KEY in results_entry: | 257 if self.results_json.EXPECTATIONS_KEY in results_entry: |
255 latest_expectations = results_entry[self.results_json.EXPECTATIONS_K EY].split(' ') | 258 expectations_list = results_entry[self.results_json.EXPECTATIONS_KEY ].split(' ') |
259 latest_expectations += [self._result_to_enum(expectation) for expect ation in expectations_list] | |
256 | 260 |
257 for result_item in results_entry[self.results_json.RESULTS_KEY]: | 261 for result_item in results_entry[self.results_json.RESULTS_KEY]: |
258 _, result_types_str = self.results_json.occurances_and_type_from_res ult_item(result_item) | 262 _, result_types_str = self.results_json.occurances_and_type_from_res ult_item(result_item) |
259 | 263 |
260 result_types = [] | 264 result_types = [] |
261 for result_type in result_types_str: | 265 for result_type in result_types_str: |
262 # TODO(ojan): Remove this if-statement once crbug.com/514378 is fixed. | 266 # TODO(ojan): Remove this if-statement once crbug.com/514378 is fixed. |
joelo
2015/08/17 23:43:30
same here
ojan
2015/08/19 00:42:54
Last I checked, some of the slower bots still had
| |
263 if result_type not in self.NON_RESULT_TYPES: | 267 if result_type not in self.NON_RESULT_TYPES: |
264 result_types.append(result_type) | 268 result_types.append(self.results_json.expectation_for_type(r esult_type)) |
265 | 269 |
266 # It didn't flake if it didn't retry. | 270 # It didn't flake if it didn't retry. |
267 if len(result_types) <= 1: | 271 if len(result_types) <= 1: |
268 continue | 272 continue |
269 | 273 |
270 # If the test ran as expected after only one retry, it's not very fl aky. | 274 # If the test ran as expected after only one retry, it's not very fl aky. |
271 # It's only very flaky if it failed the first run and the first retr y | 275 # It's only very flaky if it failed the first run and the first retr y |
272 # and then ran as expected in one of the subsequent retries. | 276 # and then ran as expected in one of the subsequent retries. |
273 if only_ignore_very_flaky and self.results_json.expectation_for_type (result_types[1]) in latest_expectations: | 277 # If there are only two entries, then that means it failed on the fi rst |
274 # Once we get a pass, we don't retry again. So if the first retr y passed, | 278 # try and ran as expected on the second because otherwise we'd have |
275 # then there should only be 2 entries because we don't do anothe r retry. | 279 # a third entry from the next try. |
276 # TODO(ojan): Reenable this assert once crbug.com/514378 is fixe d. | 280 second_result_type_enum_value = self._result_to_enum(result_types[1] ) |
277 # assert(len(result_types) == 2) | 281 if only_ignore_very_flaky and len(result_types) == 2: |
278 continue | 282 continue |
279 | 283 |
280 flaky_results = flaky_results.union(set(result_types)) | 284 has_unexpected_results = False |
285 for result_type in result_types: | |
286 result_enum = self._result_to_enum(result_type) | |
287 # TODO(ojan): We really should be grabbing the expected results from the time | |
288 # of the run instead of looking at the latest expected results. That's a lot | |
289 # more complicated though. So far we've been looking at the aggr egated | |
290 # results_small.json off test_results.appspot, which has all the information | |
291 # for the last 100 runs. In order to do this, we'd need to look at the | |
292 # individual runs' full_results.json, which would be slow and mo re complicated. | |
293 # The only thing we lose by not fixing this is that a test that was flaky | |
294 # and got fixed will still get printed out until 100 runs have p assed. | |
295 if not TestExpectations.result_was_expected(result_enum, latest_ expectations, test_needs_rebaselining=False): | |
296 has_unexpected_results = True | |
297 break | |
298 | |
299 if has_unexpected_results: | |
300 flaky_results = flaky_results.union(set(result_types)) | |
281 | 301 |
282 return flaky_results | 302 return flaky_results |
OLD | NEW |