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

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: Refactored unittests 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 = rietveld.latest_try_jobs(issue_number, try_bots, host.web)
26 line_expectations_dict = {}
27 if not try_jobs:
28 print 'No Try Job information was collected.'
29 return 1
30 for try_job in try_jobs:
31 builder_name = try_job[0]
32 build_number = try_job[1]
33 builder = buildbot.Builder(builder_name, host.buildbot)
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)
Dirk Pranke 2016/08/09 03:59:39 Nit: it's usually a good idea to break up longer f
dcampb 2016/08/09 18:10:44 Interesting, noted.
41
42
43 class W3CExpectationsLineAdder(object):
44
45 def __init__(self, host):
46 self._host = host
47 self.filesystem = host.filesystem
48
49 def get_issue_number(self):
50 return self._host.scm().get_issue_number()
51
52 def get_try_bots(self):
53 return self._host.builders.all_try_builder_names()
54
55 def _generate_results_dict(self, platform, result_list):
56 test_dict = {}
57 if '-' in platform:
58 platform = platform[platform.find('-') + 1:].capitalize()
59 for result in result_list:
60 test_dict[result.test_name()] = {
61 platform: {
62 'expected': result.expected_results(),
63 'actual': result.actual_results(),
64 'bug': 'crbug.com/626703'
65 }}
66 return test_dict
67
68 def get_failing_results_dict(self, builder, build):
69 """Returns a nested dict of failing test results.
70
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.
73
74 Args:
75 builder: A Builder object.
76 build: A Build object.
77
78 Returns:
79 A dictionary with the structure: {
80 'key': {
81 'expected': 'TIMEOUT',
82 'actual': 'CRASH',
83 'bug': 'crbug.com/11111'
84 }
85 }
86 """
87 layout_test_results = builder.fetch_layout_test_results(build.results_ur l())
88 builder_name = layout_test_results.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()
91 failing_results_dict = self._generate_results_dict(platform, result_list )
92 return failing_results_dict
93
94 def merge_dicts(self, target, source, path=None):
95 """Recursively merge nested dictionaries, returning the target dictionar y
96
97 Merges the keys and values from the source dict into the target dict.
98
99 Args:
100 target: A dictionary where the keys and values from source are updat ed
101 in.
102 source: A dictionary that provides the new keys and values to be add ed
103 into target.
104
105 Returns:
106 An updated target dictionary.
107 """
108 path = path or []
109 for key in source:
110 if key in target:
111 if (isinstance(target[key], dict)) and isinstance(source[key], d ict):
112 self.merge_dicts(target[key], source[key], path + [str(key)] )
113 elif target[key] == source[key]:
114 pass
115 else:
116 raise ValueError('The key: %s already exist in the target di ctionary.' % '.'.join(path))
117 else:
118 target[key] = source[key]
119 return target
120
121 def merge_same_valued_keys(self, dictionary):
122 """Merges keys in dictionary with same value.
123
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
126 are removed from the dict.
127
128 Args:
129 dictionary: A dictionary with a dictionary as the value.
130
131 Returns:
132 A dictionary with updated keys to reflect matching values of keys.
133 Example: {
134 'one': {'foo': 'bar'},
135 'two': {'foo': 'bar'},
136 'three': {'foo': bar'}
137 }
138 is converted to {('one', 'two', 'three'): {'foo': 'bar'}}
139 """
140 matching_value_keys = set()
141 keys = dictionary.keys()
142 is_last_item = False
143 for index, item in enumerate(keys):
144 if is_last_item:
145 break
146 for i in range(index + 1, len(keys)):
147 next_item = keys[i]
148 if dictionary[item] == dictionary[next_item]:
149 matching_value_keys.update([item, next_item])
150 dictionary[tuple(matching_value_keys)] = dictionary[item]
151 is_last_item = next_item == keys[-1]
152 del dictionary[item]
153 del dictionary[next_item]
154 return dictionary
155
156 def get_expectations(self, results):
157 """Returns a list of test expectations for a given test dict.
158
159 Returns a list of one or more test expectations based on the expected
160 and actual results of a given test name.
161
162 Args:
163 results: A dictionary that maps one test to its results. Example: {
164 'test_name': {
165 'expected': 'PASS',
166 'actual': 'FAIL',
167 'bug': 'crbug.com/11111'
168 }
169 }
170
171 Returns:
172 A list of one or more test expectations with the first letter capita lized. Example:
173 ['Failure', 'Timeout']
174 """
175 expectations = []
176 failure_expectations = ['TEXT', 'FAIL', 'IMAGE+TEXT', 'IMAGE']
177 pass_crash_timeout = ['TIMEOUT', 'CRASH', 'PASS']
178 if results['expected'] in pass_crash_timeout and results['actual'] in fa ilure_expectations:
179 expectations.append('Failure')
180 if results['expected'] in failure_expectations and results['actual'] in pass_crash_timeout:
181 expectations.append(results['actual'].capitalize())
182 if results['expected'] in pass_crash_timeout and results['actual'] in pa ss_crash_timeout:
183 expectations.append(results['actual'].capitalize())
184 expectations.append(results['expected'].capitalize())
185 return expectations
186
187 def create_line_list(self, merged_results):
188 """Creates list of test expectations lines.
189
190 Traverses through a merged_results and parses the value to create a test
191 expectations line per key.
192
193 Args:
194 merged_results: A merged_results with the format {
195 'test_name': {
196 'platform': {
197 'expected: 'PASS',
198 'actual': 'FAIL',
199 'bug': 'crbug.com/11111'
200 }
201 }
202 }
203 It is possible for the dicitonary to have many test_name
204 keys.
205
206 Returns:
207 A list of test expectations lines with the format
208 ['BUG_URL [PLATFORM(S)] TEST_MAME [EXPECTATION(S)]']
Dirk Pranke 2016/08/09 03:59:39 Nit: typo ("TEST_MAME").
dcampb 2016/08/09 18:10:44 done
209 """
210 line_list = []
211 for test_name, platform_results in merged_results.iteritems():
212 for platform in platform_results:
213 platform_list = []
214 bug = []
215 expectations = []
216 if isinstance(platform, tuple):
217 platform_list = list(platform)
218 else:
219 platform_list.append(platform)
220 bug.append(platform_results[platform]['bug'])
221 expectations = self.get_expectations(platform_results[platform])
222 line = '%s [ %s ] %s [ %s ]' % (bug[0], ' '.join(platform_list), test_name, ' '.join(expectations))
223 line_list.append(str(line))
Dirk Pranke 2016/08/09 03:59:39 similar comment about blank lines.
224 return line_list
225
226 def write_to_test_expectations(self, host, path, line_list):
227 """Writes to TestExpectations.
228
229 Writes to the test expectations lines in line_list
230 to LayoutTest/TestExpectations. Checks the file for the string
231 '# Tests added from W3C auto import bot' and writes expectation
232 lines directly under it. If not found, it writes to the end of
233 the file.
234
235 Args:
236 host: A Host object.
237 path: The path to the file LayoutTest/TestExpectations.
238 line_list: A list of w3c test expectations lines.
239
240 Returns:
241 Writes to a file on the filesystem called LayoutTests/TestExpectatio ns.
Dirk Pranke 2016/08/09 03:59:39 This doc string is a bit weird. You take the path
dcampb 2016/08/09 18:10:44 noted
242 """
243 comment_line = '# Tests added from W3C auto import bot'
244 file_contents = host.filesystem.read_text_file(path)
245 w3c_comment_line_index = file_contents.find(comment_line)
246 all_lines = ''
247 for line in line_list:
248 all_lines += str(line) + '\n'
249 all_lines = all_lines[:-1]
250 if w3c_comment_line_index == -1:
251 file_contents += '\n%s\n' % comment_line
252 file_contents += all_lines
253 else:
254 end_of_comment_line = (file_contents[w3c_comment_line_index:].find(' \n')) + w3c_comment_line_index
255 new_data = file_contents[: end_of_comment_line + 1] + all_lines + fi le_contents[end_of_comment_line:]
256 file_contents = new_data
257 host.filesystem.write_text_file(path, file_contents)
Dirk Pranke 2016/08/09 03:59:39 same.
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698