Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 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 """A script to modify TestExpectations lines based layout test failures in try j obs. | 5 """A script to modify TestExpectations lines based layout test failures in try j obs. |
| 6 | 6 |
| 7 This script outputs a list of test expectation lines to add to a 'TestExpectatio ns' file | 7 This script outputs a list of test expectation lines to add to a 'TestExpectatio ns' file |
| 8 by retrieving the try job results for the current CL. | 8 by retrieving the try job results for the current CL. |
| 9 """ | 9 """ |
| 10 | 10 |
| 11 import logging | 11 import logging |
| 12 | 12 |
| 13 from webkitpy.common.net import buildbot | 13 from webkitpy.common.net.buildbot import BuildBot |
| 14 from webkitpy.common.net import rietveld | 14 from webkitpy.common.net import rietveld |
| 15 | 15 |
| 16 | 16 |
| 17 _log = logging.getLogger(__name__) | 17 _log = logging.getLogger(__name__) |
| 18 | 18 |
| 19 | 19 |
| 20 def main(host, port): | 20 def main(host, port): |
| 21 expectations_file = port.path_to_generic_test_expectations_file() | 21 expectations_file = port.path_to_generic_test_expectations_file() |
| 22 expectations_line_adder = W3CExpectationsLineAdder(host) | 22 expectations_line_adder = W3CExpectationsLineAdder(host) |
| 23 issue_number = expectations_line_adder.get_issue_number() | 23 issue_number = expectations_line_adder.get_issue_number() |
| 24 try_bots = expectations_line_adder.get_try_bots() | 24 try_bots = expectations_line_adder.get_try_bots() |
| 25 try_jobs = rietveld.latest_try_jobs(issue_number, try_bots, host.web) | 25 try_jobs = rietveld.latest_try_jobs(issue_number, try_bots, host.web) |
| 26 line_expectations_dict = {} | 26 test_expectations_dict = {} |
| 27 if not try_jobs: | 27 if not try_jobs: |
| 28 print 'No Try Job information was collected.' | 28 print 'No Try Job information was collected.' |
| 29 return 1 | 29 return 1 |
| 30 for try_job in try_jobs: | 30 for try_job in try_jobs: |
| 31 builder_name = try_job[0] | 31 builder_name = try_job[0] |
| 32 build_number = try_job[1] | 32 build_number = try_job[1] |
| 33 builder = buildbot.Builder(builder_name, host.buildbot) | 33 buildbot = BuildBot() |
| 34 build = buildbot.Build(builder, build_number) | 34 platform_results_dict = expectations_line_adder.get_failing_results_dict (buildbot, builder_name, build_number) |
|
qyearsley
2016/07/26 21:40:35
Somewhat unrelated to this CL: An alternative way
dcampb
2016/07/26 22:08:35
suggestions noted and changed made.
| |
| 35 platform_results_dict = expectations_line_adder.get_failing_results_dict (builder, build) | 35 test_expectations_dict = expectations_line_adder.merge_dicts(test_expect ations_dict, platform_results_dict) |
| 36 line_expectations_dict = expectations_line_adder.merge_dicts(line_expect ations_dict, platform_results_dict) | 36 for test_name, platform_results_dicts in test_expectations_dict.iteritems(): |
| 37 for platform_results_dicts in line_expectations_dict.values(): | |
| 38 platform_results_dicts = expectations_line_adder.merge_same_valued_keys( platform_results_dicts) | 37 platform_results_dicts = expectations_line_adder.merge_same_valued_keys( platform_results_dicts) |
| 39 line_list = expectations_line_adder.create_line_list(line_expectations_dict) | 38 test_expectations_dict[test_name] = platform_results_dicts |
| 39 line_list = expectations_line_adder.create_line_list(test_expectations_dict) | |
| 40 expectations_line_adder.write_to_test_expectations(host, expectations_file, line_list) | 40 expectations_line_adder.write_to_test_expectations(host, expectations_file, line_list) |
| 41 | 41 |
| 42 | 42 |
| 43 class W3CExpectationsLineAdder(object): | 43 class W3CExpectationsLineAdder(object): |
| 44 | 44 |
| 45 def __init__(self, host): | 45 def __init__(self, host): |
| 46 self._host = host | 46 self._host = host |
| 47 self.filesystem = host.filesystem | 47 self.filesystem = host.filesystem |
| 48 | 48 |
| 49 def get_issue_number(self): | 49 def get_issue_number(self): |
| 50 return self._host.scm().get_issue_number() | 50 return self._host.scm().get_issue_number() |
| 51 | 51 |
| 52 def get_try_bots(self): | 52 def get_try_bots(self): |
| 53 return self._host.builders.all_try_builder_names() | 53 return self._host.builders.all_try_builder_names() |
| 54 | 54 |
| 55 def _generate_results_dict(self, platform, result_list): | 55 def _generate_results_dict(self, platform, result_list): |
| 56 test_dict = {} | 56 test_dict = {} |
| 57 if '-' in platform: | 57 if '-' in platform: |
| 58 platform = platform[platform.find('-') + 1:].capitalize() | 58 platform = platform[platform.find('-') + 1:].capitalize() |
| 59 for result in result_list: | 59 for result in result_list: |
| 60 test_dict[result.test_name()] = { | 60 test_dict[result.test_name()] = { |
| 61 platform: { | 61 platform: { |
| 62 'expected': result.expected_results(), | 62 'expected': result.expected_results(), |
| 63 'actual': result.actual_results(), | 63 'actual': result.actual_results(), |
| 64 'bug': 'crbug.com/626703' | 64 'bug': 'crbug.com/626703' |
| 65 }} | 65 }} |
| 66 return test_dict | 66 return test_dict |
| 67 | 67 |
| 68 def get_failing_results_dict(self, builder, build): | 68 def get_failing_results_dict(self, buildbot, builder_name, build_number): |
| 69 """Returns a nested dict of failing test results. | 69 """Returns a nested dict of failing test results. |
| 70 | 70 |
| 71 Retrieves a full list of layout test results from a builder result URL. Collects | 71 Retrieves a full list of layout test results from a builder result URL. Collects |
| 72 the builder name, platform and a list of tests that did not run as expec ted. | 72 the builder name, platform and a list of tests that did not run as expec ted. |
| 73 | 73 |
| 74 Args: | 74 Args: |
| 75 builder: A Builder object. | 75 builder: A Builder object. |
| 76 build: A Build object. | 76 build: A Build object. |
| 77 | 77 |
| 78 Returns: | 78 Returns: |
| 79 A dictionary with the structure: { | 79 A dictionary with the structure: { |
| 80 'key': { | 80 'key': { |
| 81 'expected': 'TIMEOUT', | 81 'expected': 'TIMEOUT', |
| 82 'actual': 'CRASH', | 82 'actual': 'CRASH', |
| 83 'bug': 'crbug.com/11111' | 83 'bug': 'crbug.com/11111' |
| 84 } | 84 } |
| 85 } | 85 } |
| 86 """ | 86 """ |
| 87 layout_test_results = builder.fetch_layout_test_results(build.results_ur l()) | 87 results_url = buildbot.results_url(builder_name, build_number) |
| 88 builder_name = layout_test_results.builder_name() | 88 layout_test_results = buildbot.fetch_layout_test_results(results_url) |
|
qyearsley
2016/07/26 21:40:35
Side note: In a subsequent CL (after this CL is la
dcampb
2016/07/26 22:08:35
sounds good.
| |
| 89 platform = self._host.builders.port_name_for_builder_name(builder_name) | 89 platform = self._host.builders.port_name_for_builder_name(builder_name) |
| 90 result_list = layout_test_results.didnt_run_as_expected_results() | 90 result_list = layout_test_results.didnt_run_as_expected_results() |
| 91 failing_results_dict = self._generate_results_dict(platform, result_list ) | 91 failing_results_dict = self._generate_results_dict(platform, result_list ) |
| 92 return failing_results_dict | 92 return failing_results_dict |
| 93 | 93 |
| 94 def merge_dicts(self, target, source, path=None): | 94 def merge_dicts(self, target, source, path=None): |
| 95 """Recursively merge nested dictionaries, returning the target dictionar y | 95 """Recursively merge nested dictionaries, returning the target dictionar y |
| 96 | 96 |
| 97 Merges the keys and values from the source dict into the target dict. | 97 Merges the keys and values from the source dict into the target dict. |
| 98 | 98 |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 122 """Merges keys in dictionary with same value. | 122 """Merges keys in dictionary with same value. |
| 123 | 123 |
| 124 Traverses through a dict and compares the values of keys to one another. | 124 Traverses through a dict and compares the values of keys to one another. |
| 125 If the values match, the keys are combined to a tuple and the previous k eys | 125 If the values match, the keys are combined to a tuple and the previous k eys |
| 126 are removed from the dict. | 126 are removed from the dict. |
| 127 | 127 |
| 128 Args: | 128 Args: |
| 129 dictionary: A dictionary with a dictionary as the value. | 129 dictionary: A dictionary with a dictionary as the value. |
| 130 | 130 |
| 131 Returns: | 131 Returns: |
| 132 A dictionary with updated keys to reflect matching values of keys. | 132 A new dictionary with updated keys to reflect matching values of key s. |
| 133 Example: { | 133 Example: { |
| 134 'one': {'foo': 'bar'}, | 134 'one': {'foo': 'bar'}, |
| 135 'two': {'foo': 'bar'}, | 135 'two': {'foo': 'bar'}, |
| 136 'three': {'foo': bar'} | 136 'three': {'foo': bar'} |
|
qyearsley
2016/07/26 21:40:35
Unrelated to this CL, but there's a quote missing
dcampb
2016/07/26 22:08:35
ahh, nice eye. done
| |
| 137 } | 137 } |
| 138 is converted to {('one', 'two', 'three'): {'foo': 'bar'}} | 138 is converted to a new dictionary with that contains |
| 139 {('one', 'two', 'three'): {'foo': 'bar'}} | |
| 139 """ | 140 """ |
| 141 merged_dict = {} | |
| 140 matching_value_keys = set() | 142 matching_value_keys = set() |
| 141 keys = dictionary.keys() | 143 keys = sorted(dictionary.keys()) |
|
qyearsley
2016/07/26 21:40:35
Same as `keys = sorted(dictionary)` (both are OK).
dcampb
2016/07/26 22:08:35
noted
| |
| 142 is_last_item = False | 144 while keys: |
| 143 for index, item in enumerate(keys): | 145 current_key = keys[0] |
| 144 if is_last_item: | 146 found_match = False |
| 147 if current_key == keys[-1]: | |
| 148 merged_dict[current_key] = dictionary[current_key] | |
| 149 keys.remove(current_key) | |
| 145 break | 150 break |
| 146 for i in range(index + 1, len(keys)): | 151 for i in range(1, len(keys)): |
| 147 next_item = keys[i] | 152 next_item = keys[i] |
|
qyearsley
2016/07/26 21:40:36
If `i` isn't used below, then maybe you could also
dcampb
2016/07/26 22:08:35
nicee, noted.
| |
| 148 if dictionary[item] == dictionary[next_item]: | 153 if dictionary[current_key] == dictionary[next_item]: |
| 149 matching_value_keys.update([item, next_item]) | 154 found_match = True |
| 150 dictionary[tuple(matching_value_keys)] = dictionary[item] | 155 matching_value_keys.update([current_key, next_item]) |
| 151 is_last_item = next_item == keys[-1] | 156 if next_item == keys[-1]: |
| 152 del dictionary[item] | 157 if found_match: |
| 153 del dictionary[next_item] | 158 merged_dict[tuple(matching_value_keys)] = dictionary[cur rent_key] |
| 154 return dictionary | 159 for key in matching_value_keys: |
| 160 keys.remove(key) | |
|
qyearsley
2016/07/26 21:40:35
For many short for loops, you can sometimes write
dcampb
2016/07/26 22:08:35
nice tip, thanks.
done
| |
| 161 else: | |
| 162 merged_dict[current_key] = dictionary[current_key] | |
| 163 keys.remove(current_key) | |
| 164 matching_value_keys = set() | |
| 165 return merged_dict | |
| 155 | 166 |
| 156 def get_expectations(self, results): | 167 def get_expectations(self, results): |
| 157 """Returns a list of test expectations for a given test dict. | 168 """Returns a list of test expectations for a given test dict. |
| 158 | 169 |
| 159 Returns a list of one or more test expectations based on the expected | 170 Returns a list of one or more test expectations based on the expected |
| 160 and actual results of a given test name. | 171 and actual results of a given test name. |
| 161 | 172 |
| 162 Args: | 173 Args: |
| 163 results: A dictionary that maps one test to its results. Example: { | 174 results: A dictionary that maps one test to its results. Example: { |
| 164 'test_name': { | 175 'test_name': { |
| 165 'expected': 'PASS', | 176 'expected': 'PASS', |
| 166 'actual': 'FAIL', | 177 'actual': 'FAIL', |
| 167 'bug': 'crbug.com/11111' | 178 'bug': 'crbug.com/11111' |
| 168 } | 179 } |
| 169 } | 180 } |
| 170 | 181 |
| 171 Returns: | 182 Returns: |
| 172 A list of one or more test expectations with the first letter capita lized. Example: | 183 A list of one or more test expectations with the first letter capita lized. Example: |
| 173 ['Failure', 'Timeout'] | 184 ['Failure', 'Timeout'] |
| 174 """ | 185 """ |
| 175 expectations = [] | 186 expectations = set() |
| 176 failure_expectations = ['TEXT', 'FAIL', 'IMAGE+TEXT', 'IMAGE'] | 187 failure_expectations = ['TIMEOUT', 'CRASH', 'SLOW', 'TEXT', 'FAIL', 'IMA GE+TEXT', 'IMAGE'] |
| 177 pass_crash_timeout = ['TIMEOUT', 'CRASH', 'PASS'] | 188 pass_crash_timeout = ['TIMEOUT', 'CRASH', 'PASS'] |
| 178 if results['expected'] in pass_crash_timeout and results['actual'] in fa ilure_expectations: | 189 for expected in results['expected'].split(): |
| 179 expectations.append('Failure') | 190 for actual in results['actual'].split(): |
|
qyearsley
2016/07/26 21:40:35
For this change, I think it will be good to have u
dcampb
2016/07/26 22:08:35
working on this now.
| |
| 180 if results['expected'] in failure_expectations and results['actual'] in pass_crash_timeout: | 191 if expected in pass_crash_timeout and actual in failure_expectat ions: |
| 181 expectations.append(results['actual'].capitalize()) | 192 expectations.update(['Failure']) |
| 182 if results['expected'] in pass_crash_timeout and results['actual'] in pa ss_crash_timeout: | 193 if expected in failure_expectations and actual in pass_crash_tim eout: |
| 183 expectations.append(results['actual'].capitalize()) | 194 expectations.update([actual.capitalize()]) |
| 184 expectations.append(results['expected'].capitalize()) | 195 if expected in pass_crash_timeout and actual in pass_crash_timeo ut: |
| 196 expectations.update([actual.capitalize()]) | |
| 197 expectations.update([expected.capitalize()]) | |
|
qyearsley
2016/07/26 21:40:35
For sets, if you're adding only one item, you can
dcampb
2016/07/26 22:08:35
noted and done.
| |
| 185 return expectations | 198 return expectations |
| 186 | 199 |
| 187 def create_line_list(self, merged_results): | 200 def create_line_list(self, merged_results): |
| 188 """Creates list of test expectations lines. | 201 """Creates list of test expectations lines. |
| 189 | 202 |
| 190 Traverses through a merged_results and parses the value to create a test | 203 Traverses through a merged_results and parses the value to create a test |
| 191 expectations line per key. | 204 expectations line per key. |
| 192 | 205 |
| 193 Args: | 206 Args: |
| 194 merged_results: A merged_results with the format { | 207 merged_results: A merged_results with the format { |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 253 all_lines += str(line) + '\n' | 266 all_lines += str(line) + '\n' |
| 254 all_lines = all_lines[:-1] | 267 all_lines = all_lines[:-1] |
| 255 if w3c_comment_line_index == -1: | 268 if w3c_comment_line_index == -1: |
| 256 file_contents += '\n%s\n' % comment_line | 269 file_contents += '\n%s\n' % comment_line |
| 257 file_contents += all_lines | 270 file_contents += all_lines |
| 258 else: | 271 else: |
| 259 end_of_comment_line = (file_contents[w3c_comment_line_index:].find(' \n')) + w3c_comment_line_index | 272 end_of_comment_line = (file_contents[w3c_comment_line_index:].find(' \n')) + w3c_comment_line_index |
| 260 new_data = file_contents[: end_of_comment_line + 1] + all_lines + fi le_contents[end_of_comment_line:] | 273 new_data = file_contents[: end_of_comment_line + 1] + all_lines + fi le_contents[end_of_comment_line:] |
| 261 file_contents = new_data | 274 file_contents = new_data |
| 262 host.filesystem.write_text_file(path, file_contents) | 275 host.filesystem.write_text_file(path, file_contents) |
| OLD | NEW |