Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(63)

Side by Side Diff: third_party/WebKit/Tools/Scripts/webkitpy/w3c/update_w3c_test_expectations.py

Issue 2136723002: Create test expectations line automatically from failing test results (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Modified doc strings for functions. Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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.
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)
qyearsley 2016/07/19 17:59:47 Note: As-is, get_build_bot is a function, not a Bu
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
qyearsley 2016/07/19 17:59:48 This method could be removed, and line 33 above co
50
51 def get_try_jobs_information(self, issue_number, try_bots):
qyearsley 2016/07/19 17:59:48 This method could also be called latest_try_jobs -
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 nested dict of failing test results.
qyearsley 2016/07/19 17:59:48 Extra space before "Returns"
75
76 Retrieves a full list oflayout test results from a builder result url. C ollects
qyearsley 2016/07/19 17:59:48 "oflayout" -> "of layout" "url" -> "URL"
77 the builder name, platform and a list of tests that did not run as expec ted.
78
79 Args:
80 builder: A Builder object.
81 build: A Build object.
82
83 Returns:
84 A dictionary with the structure: {
85 'key': {
86 'expected': 'TIMEOUT', 'actual': 'CRASH', 'bug': 'crbug.com/1111 1', ...}
87 }
qyearsley 2016/07/19 17:59:47 Could be reformatted as: A dictionar
88 """
89 layout_test_results = builder.fetch_layout_test_results(build.results_ur l())
90 builder_name = layout_test_results.builder_name()
91 platform = self._host.builders.port_name_for_builder_name(builder_name)
92 result_list = layout_test_results.didnt_run_as_expected_results()
93 failing_results_dict = self._generate_results_dict(platform, result_list )
94 return failing_results_dict
95
96 def merge_dicts(self, final, temp, path=None):
97 path = path or []
98 for key in temp:
99 if key in final:
100 if (isinstance(final[key], dict)) and isinstance(temp[key], dict ):
101 self.merge_dicts(final[key], temp[key], path + [str(key)])
102 elif final[key] == temp[key]:
103 pass
104 else:
105 raise Exception('conflict at %s' % '.'.join(path))
106 else:
107 final[key] = temp[key]
108 return final
109
110 def merge_same_valued_keys(self, dictionary):
111 """Merges keys in dictionary with same value.
112
113 Traverses through a dict and compares the values of keys to one another.
114 If the values match, the keys are combined to a tuple and the previous k eys
115 are removed from the dict.
116
117 Args:
118 dictionary: A dictionary with a dictionary as the value.
119
120 Returns:
121 A dictionary with updated keys to reflect matching values of keys.
122 Example: {
123 'one': {'foo': 'bar'},
124 'two': {'foo': 'bar'},
125 'three': {'foo': bar'}
126 } is converted to
qyearsley 2016/07/19 17:59:48 I'd dedent this line so that the closing curly bra
127 {('one', 'two', 'three'): {'foo': 'bar'}}
128 """
129 matching_value_keys = set()
130 keys = dictionary.keys()
131 isLastItem = False
qyearsley 2016/07/19 17:59:48 Variable names should be lower_with_underscores.
132 for index, item in enumerate(keys):
133 if isLastItem:
134 break
135 for i in range(index + 1, len(keys)):
136 next_item = keys[i]
137 if dictionary[item] == dictionary[next_item]:
138 matching_value_keys.update([item, next_item])
139 dictionary[tuple(matching_value_keys)] = dictionary[item]
140 isLastItem = next_item == keys[-1]
141 del dictionary[item]
142 del dictionary[next_item]
143 return dictionary
144
145 def get_expectations(self, results):
qyearsley 2016/07/19 17:59:48 This method could also use a docstring -- Is resul
146 expectations = []
147 failure_expectations = ['TEXT', 'FAIL', 'IMAGE+TEXT', 'IMAGE']
148 pass_crash_timeout = ['TIMEOUT', 'CRASH', 'PASS']
149 if results['expected'] in pass_crash_timeout and results['actual'] in fa ilure_expectations:
150 expectations.append('Failure')
151 if results['expected'] in failure_expectations and results['actual'] in pass_crash_timeout:
152 expectations.append(results['actual'].capitalize())
153 if results['expected'] in pass_crash_timeout and results['actual'] in pa ss_crash_timeout:
154 expectations.append(results['actual'].capitalize())
155 expectations.append(results['expected'].capitalize())
156 return expectations
157
158 def create_line_list(self, dictionary):
159 """Creates list of test expectations lines.
160
161 Traverses through a dictionary and parses the value to create a test
162 expectations line per key.
163
164 Args:
165 dictionary: A dictionary with the format {
qyearsley 2016/07/19 17:59:48 We should think of a name for the argument that de
dcampb 2016/07/20 03:29:43 agreed, I used merged_results.
166 'test_name': {
167 'platform': {
168 'expected: 'PASS',
169 'actual': 'FAIL',
170 'bug': 'crbug.com/11111'
171 }
172 }
173 }
qyearsley 2016/07/19 17:59:47 Indentation for closing braces usually lines up wi
174 It is possible for the dicitonary to have many test_name
175 keys.
176
177 Returns:
178 A list of test expectations lines with the format
179 ['BUG_URL [PLATFORM(S)] TEST_MAME [EXPECTATION(S)]']
180 """
181 line_list = []
182 for key, value in dictionary.iteritems():
183 test_name = key
qyearsley 2016/07/19 17:59:47 We can also think of more descriptive names than k
dcampb 2016/07/20 03:29:43 done
184 for key2 in value:
qyearsley 2016/07/19 17:59:48 Any time you find yourself adding digits after a v
dcampb 2016/07/20 03:29:43 agreed
185 platform = []
186 bug = []
187 expectations = []
188 if isinstance(key2, tuple):
189 platform = list(key2)
190 else:
191 platform.append(key2)
192 bug.append(value[key2]['bug'])
193 expectations = self.get_expectations(value[key2])
194 line = '%s [ %s ] %s [ %s ]' % (bug[0], ' '.join(platform), test _name, ' '.join(expectations))
195 line_list.append(str(line))
196 return line_list
197
198 def write_to_test_expectations(self, host, path, line_list):
199 """Writes to TestExpectations.
200
201 Writes to the test expectations lines in line_list
202 to LayoutTest/TestExpectations. Checks the file for the string
203 '#Tests added from W3C auto import bot' and writes expectation
qyearsley 2016/07/19 17:59:48 "#Tests" -> "# Tests"
204 lines directly under it. If not found, it writes to the end of
205 the file.
206
207 Args:
208 host: A Host object.
209 path: The path to the file LayoutTest/TestExpectations.
210 line_list: A list of w3c test expectations lines.
211
212 Returns:
213 Writes to a file on the filesystem called LayoutTests/TestExpectatio ns.
214 """
215 file_contents = host.filesystem.read_text_file(path)
216 w3c_comment_line_index = file_contents.find('# Tests added from W3C auto import bot')
217 all_lines = ''
218 for line in line_list:
219 all_lines += str(line) + '\n'
220 all_lines = all_lines[:-1]
221 if w3c_comment_line_index == -1:
222 file_contents += '\n\n# Tests added from W3C auto import bot\n'
223 file_contents += all_lines
224 else:
225 end_of_comment_line = (file_contents[w3c_comment_line_index:].find(' \n')) + w3c_comment_line_index
226 new_data = file_contents[: end_of_comment_line + 1] + all_lines + fi le_contents[end_of_comment_line:]
227 file_contents = new_data
228 host.filesystem.write_text_file(path, file_contents)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698