OLD | NEW |
---|---|
(Empty) | |
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 | |
3 # found in the LICENSE file. | |
4 | |
5 '''A script to modify TestExpectations lines based layout test failures in try j obs. | |
qyearsley
2016/07/18 22:55:29
Usually triple-double-quotes (""") are used for do
| |
6 | |
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. | |
9 ''' | |
10 | |
11 import logging | |
12 | |
13 from webkitpy.common.net import buildbot | |
14 from webkitpy.common.net import rietveld | |
15 | |
16 | |
17 _log = logging.getLogger(__name__) | |
18 | |
19 | |
20 def main(host, port): | |
21 expectations_file = port.path_to_generic_test_expectations_file() | |
22 expectations_line_adder = W3CExpectationsLineAdder(host) | |
23 issue_number = expectations_line_adder.get_issue_number() | |
24 try_bots = expectations_line_adder.get_try_bots() | |
25 try_jobs_info = expectations_line_adder.get_try_jobs_information(issue_numbe r, try_bots) | |
26 line_expectations_dict = {} | |
27 if not try_jobs_info: | |
28 print 'No Try Job information was collected.' | |
29 return 1 | |
30 for try_job in try_jobs_info: | |
31 builder_name = try_job[0] | |
32 build_number = try_job[1] | |
33 builder = buildbot.Builder(builder_name, expectations_line_adder.get_bui ld_bot) | |
34 build = buildbot.Build(builder, build_number) | |
35 platform_results_dict = expectations_line_adder.get_failing_results_dict (builder, build) | |
36 line_expectations_dict = expectations_line_adder.merge_dicts(line_expect ations_dict, platform_results_dict) | |
37 for platform_results_dicts in line_expectations_dict.values(): | |
38 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) | |
40 expectations_line_adder.write_to_test_expectations(host, expectations_file, line_list) | |
41 | |
42 | |
43 class W3CExpectationsLineAdder(object): | |
44 | |
45 def __init__(self, host): | |
46 self._host = host | |
47 | |
48 def get_build_bot(self): | |
49 return self._host.buildbot | |
50 | |
51 def get_try_jobs_information(self, issue_number, try_bots): | |
52 return rietveld.latest_try_jobs(issue_number, try_bots, self._host.web) | |
53 | |
54 def get_issue_number(self): | |
55 return self._host._scm.get_issue_number() | |
56 | |
57 def get_try_bots(self): | |
58 return self._host.builders.all_try_builder_names() | |
59 | |
60 def _generate_results_dict(self, platform, result_list): | |
61 test_dict = {} | |
62 if '-' in platform: | |
63 platform = platform[platform.find('-') + 1:].capitalize() | |
64 for result in result_list: | |
65 test_dict[result.test_name()] = { | |
66 platform: { | |
67 'expected': result.expected_results(), | |
68 'actual': result.actual_results(), | |
69 'bug': 'crbug.com/626703' | |
70 }} | |
71 return test_dict | |
72 | |
73 def get_failing_results_dict(self, builder, build): | |
74 ''' returns a dict of dicts with the format | |
75 {'key': {'expected': results, 'actual': results, 'bug': bug_url, ...}} | |
76 ''' | |
77 layout_test_results = builder.fetch_layout_test_results(build.results_ur l()) | |
78 builder_name = layout_test_results.builder_name() | |
79 platform = self._host.builders.port_name_for_builder_name(builder_name) | |
80 result_list = layout_test_results.didnt_run_as_expected_results() | |
81 failing_results_dict = self._generate_results_dict(platform, result_list ) | |
82 return failing_results_dict | |
83 | |
84 def merge_dicts(self, final, temp, path=None): | |
85 path = path or [] | |
86 for key in temp: | |
87 if key in final: | |
88 if (isinstance(final[key], dict)) and isinstance(temp[key], dict ): | |
89 self.merge_dicts(final[key], temp[key], path + [str(key)]) | |
90 elif final[key] == temp[key]: | |
91 pass | |
92 else: | |
93 raise Exception('conflict at %s' % '.'.join(path)) | |
94 else: | |
95 final[key] = temp[key] | |
96 return final | |
97 | |
98 def merge_same_valued_keys(self, dictionary): | |
99 '''This function takes a dictionary of dictionaries and creates a new tu ple key | |
qyearsley
2016/07/18 22:55:29
You could omit "This function", and just start wit
| |
100 if two or more values match. Example: { | |
qyearsley
2016/07/18 22:55:28
You should add a blank line after the first senten
| |
101 'one': {'foo': 'bar'}, | |
102 'two': {'foo': 'bar'}, | |
103 'three': {'foo': bar'} | |
104 } is converted to | |
105 {('one', 'two', 'three'): {'foo': 'bar'}} | |
106 ''' | |
107 matching_value_keys = set() | |
108 keys = dictionary.keys() | |
109 isLastItem = False | |
110 for index, item in enumerate(keys): | |
111 if isLastItem: | |
112 break | |
113 for i in range(index + 1, len(keys)): | |
114 next_item = keys[i] | |
115 if dictionary[item] == dictionary[next_item]: | |
116 matching_value_keys.update([item, next_item]) | |
117 dictionary[tuple(matching_value_keys)] = dictionary[item] | |
118 isLastItem = next_item == keys[-1] | |
119 del dictionary[item] | |
120 del dictionary[next_item] | |
121 return dictionary | |
122 | |
123 def get_expectations(self, results): | |
124 expectations = [] | |
125 failure_expectations = ['TEXT', 'FAIL', 'IMAGE+TEXT', 'IMAGE'] | |
126 pass_crash_timeout = ['TIMEOUT', 'CRASH', 'PASS'] | |
127 if results['expected'] in pass_crash_timeout and results['actual'] in fa ilure_expectations: | |
128 expectations.append('Failure') | |
129 if results['expected'] in failure_expectations and results['actual'] in pass_crash_timeout: | |
130 expectations.append(results['actual'].capitalize()) | |
131 if results['expected'] in pass_crash_timeout and results['actual'] in pa ss_crash_timeout: | |
132 expectations.append(results['actual'].capitalize()) | |
133 expectations.append(results['expected'].capitalize()) | |
134 return expectations | |
135 | |
136 def create_line_list(self, dictionary): | |
137 '''Returns a list of test expectations lines with the format | |
138 ['BUG_URL [PLATFORM(S)] TEST_MAME [EXPECTATION(S)]'] | |
139 ''' | |
140 line_list = [] | |
141 for key, value in dictionary.iteritems(): | |
142 test_name = key | |
143 for key2 in value: | |
144 platform = [] | |
145 bug = [] | |
146 expectations = [] | |
147 if isinstance(key2, tuple): | |
148 platform = list(key2) | |
149 else: | |
150 platform.append(key2) | |
151 bug.append(value[key2]['bug']) | |
152 expectations = self.get_expectations(value[key2]) | |
153 line = '%s [ %s ] %s [ %s ]' % (bug[0], ' '.join(platform), test _name, ' '.join(expectations)) | |
154 line_list.append(str(line)) | |
155 return line_list | |
156 | |
157 def write_to_test_expectations(self, host, path, line_list): | |
158 '''Writes to test expectations file on the filesystem. Checks the file f or | |
159 '#Tests added from W3C auto import bot' and writes expectation lines dir ectly under it. If not found, | |
160 it writes to the end of the file. | |
161 ''' | |
162 file_contents = host.filesystem.read_text_file(path) | |
163 w3c_comment_line_index = file_contents.find('# Tests added from W3C auto import bot') | |
164 all_lines = '' | |
165 for line in line_list: | |
166 all_lines += str(line) + '\n' | |
167 all_lines = all_lines[:-1] | |
168 if w3c_comment_line_index == -1: | |
169 file_contents += '\n\n# Tests added from W3C auto import bot\n' | |
170 file_contents += all_lines | |
171 else: | |
172 end_of_comment_line = (file_contents[w3c_comment_line_index:].find(' \n')) + w3c_comment_line_index | |
173 new_data = file_contents[: end_of_comment_line + 1] + all_lines + fi le_contents[end_of_comment_line:] | |
174 file_contents = new_data | |
175 host.filesystem.write_text_file(path, file_contents) | |
OLD | NEW |