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

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

Issue 2277343002: Refactor update_w3c_test_expectations. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebased Created 4 years, 3 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
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 """Functionality for adding TestExpectations lines and downloading baselines 5 """A class for updating layout test expectations when updating w3c tests.
6 based on layout test failures in try jobs.
7 6
8 This script is used as part of the w3c test auto-import process. 7 Specifically, this class fetches results from try bots for the current CL, and:
8 1. Downloads new baseline files for any tests that can be rebaselined.
9 2. Updates the generic TestExpectations file for any other failing tests.
10
11 This is used as part of the w3c test auto-import process.
9 """ 12 """
10 13
11 import logging 14 import logging
12 15
13 from webkitpy.common.net.rietveld import Rietveld
14 from webkitpy.common.net.buildbot import BuildBot, Build 16 from webkitpy.common.net.buildbot import BuildBot, Build
15 from webkitpy.common.net.git_cl import GitCL 17 from webkitpy.common.net.git_cl import GitCL
18 from webkitpy.common.net.rietveld import Rietveld
16 from webkitpy.common.webkit_finder import WebKitFinder 19 from webkitpy.common.webkit_finder import WebKitFinder
17 from webkitpy.w3c.test_parser import TestParser 20 from webkitpy.w3c.test_parser import TestParser
18 21
19 _log = logging.getLogger(__name__) 22 _log = logging.getLogger(__name__)
20 23
21 24
22 def main(host):
23 # TODO(qyearsley): Add a "main" function to W3CExpectationsLineAdder
24 # and move most or all of this logic in there.
25 host.initialize_scm()
26 port = host.port_factory.get()
27 expectations_file = port.path_to_generic_test_expectations_file()
28 expectations_line_adder = W3CExpectationsLineAdder(host)
29 issue_number = expectations_line_adder.get_issue_number()
30 try_bots = expectations_line_adder.get_try_bots()
31 rietveld = Rietveld(host.web)
32 try_jobs = rietveld.latest_try_jobs(issue_number, try_bots)
33 test_expectations = {}
34 if not try_jobs:
35 print 'No Try Job information was collected.'
36 return 1
37
38 for job in try_jobs:
39 platform_results = expectations_line_adder.get_failing_results_dict(Buil dBot(), job.builder_name, job.build_number)
40 test_expectations = expectations_line_adder.merge_dicts(test_expectation s, platform_results)
41
42 for test_name, platform_result in test_expectations.iteritems():
43 test_expectations[test_name] = expectations_line_adder.merge_same_valued _keys(platform_result)
44 test_expectations = expectations_line_adder.get_expected_txt_files(test_expe ctations)
45 test_expectation_lines = expectations_line_adder.create_line_list(test_expec tations)
46 expectations_line_adder.write_to_test_expectations(host, expectations_file, test_expectation_lines)
47
48
49 class W3CExpectationsLineAdder(object): 25 class W3CExpectationsLineAdder(object):
50 26
51 def __init__(self, host): 27 def __init__(self, host):
52 self._host = host 28 self.host = host
53 self.filesystem = host.filesystem 29 self.host.initialize_scm()
30 self.finder = WebKitFinder(self.host.filesystem)
31
32 def run(self):
33 issue_number = self.get_issue_number()
34 try_bots = self.get_try_bots()
35 rietveld = Rietveld(self.host.web)
36 try_jobs = rietveld.latest_try_jobs(issue_number, try_bots)
37
38 if not try_jobs:
39 print 'No Try Job information was collected.'
40 return 1
41
42 test_expectations = {}
43 for job in try_jobs:
44 platform_results = self.get_failing_results_dict(job)
45 test_expectations = self.merge_dicts(test_expectations, platform_res ults)
46
47 for test_name, platform_result in test_expectations.iteritems():
48 test_expectations[test_name] = self.merge_same_valued_keys(platform_ result)
49
50 test_expectations = self.get_expected_txt_files(test_expectations)
51 test_expectation_lines = self.create_line_list(test_expectations)
52 self.write_to_test_expectations(test_expectation_lines)
53 return 0
54 54
55 def get_issue_number(self): 55 def get_issue_number(self):
56 return GitCL(self._host.executive).get_issue_number() 56 return GitCL(self.host.executive).get_issue_number()
57 57
58 def get_try_bots(self): 58 def get_try_bots(self):
59 return self._host.builders.all_try_builder_names() 59 return self.host.builders.all_try_builder_names()
60 60
61 def _generate_results_dict(self, platform, result_list): 61 def generate_results_dict(self, platform, result_list):
62 test_dict = {} 62 test_dict = {}
63 if '-' in platform: 63 if '-' in platform:
64 platform = platform[platform.find('-') + 1:].capitalize() 64 platform = platform[platform.find('-') + 1:].capitalize()
65 for result in result_list: 65 for result in result_list:
66 test_dict[result.test_name()] = { 66 test_dict[result.test_name()] = {
67 platform: { 67 platform: {
68 'expected': result.expected_results(), 68 'expected': result.expected_results(),
69 'actual': result.actual_results(), 69 'actual': result.actual_results(),
70 'bug': 'crbug.com/626703' 70 'bug': 'crbug.com/626703'
71 }} 71 }}
72 return test_dict 72 return test_dict
73 73
74 def get_failing_results_dict(self, buildbot, builder_name, build_number): 74 def get_failing_results_dict(self, build):
75 """Returns a nested dict of failing test results. 75 """Returns a nested dict of failing test results.
76 76
77 Retrieves a full list of layout test results from a builder result URL. 77 Retrieves a full list of layout test results from a builder result URL.
78 Collects the builder name, platform and a list of tests that did not 78 Collects the builder name, platform and a list of tests that did not
79 run as expected. 79 run as expected.
80 80
81 TODO(qyearsley): Rather than taking a BuildBot object, this should use
82 the Host's BuildBot object.
83
84 Args: 81 Args:
85 buildbot: A BuildBot object.
86 builder: A Builder object.
87 build: A Build object. 82 build: A Build object.
88 83
89 Returns: 84 Returns:
90 A dictionary with the structure: { 85 A dictionary with the structure: {
91 'key': { 86 'key': {
92 'expected': 'TIMEOUT', 87 'expected': 'TIMEOUT',
93 'actual': 'CRASH', 88 'actual': 'CRASH',
94 'bug': 'crbug.com/11111' 89 'bug': 'crbug.com/11111'
95 } 90 }
96 } 91 }
97 If there are no failing results or no results could be fetched, 92 If there are no failing results or no results could be fetched,
98 this will return an empty dict. 93 this will return an empty dict.
99 """ 94 """
100 layout_test_results = buildbot.fetch_results(Build(builder_name, build_n umber)) 95 layout_test_results = self.host.buildbot.fetch_results(build)
101 if layout_test_results is None: 96 if layout_test_results is None:
102 _log.warning('No results for builder %s, build %s', builder_name, bu ild_number) 97 _log.warning('No results for build %s', build)
103 return {} 98 return {}
104 platform = self._host.builders.port_name_for_builder_name(builder_name) 99 platform = self.host.builders.port_name_for_builder_name(build.builder_n ame)
105 result_list = layout_test_results.didnt_run_as_expected_results() 100 result_list = layout_test_results.didnt_run_as_expected_results()
106 failing_results_dict = self._generate_results_dict(platform, result_list ) 101 failing_results_dict = self.generate_results_dict(platform, result_list)
107 return failing_results_dict 102 return failing_results_dict
108 103
109 def merge_dicts(self, target, source, path=None): 104 def merge_dicts(self, target, source, path=None):
110 """Recursively merges nested dictionaries. 105 """Recursively merges nested dictionaries.
111 106
112 Args: 107 Args:
113 target: First dictionary, which is updated based on source. 108 target: First dictionary, which is updated based on source.
114 source: Second dictionary, not modified. 109 source: Second dictionary, not modified.
115 110
116 Returns: 111 Returns:
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after
240 if isinstance(platform, tuple): 235 if isinstance(platform, tuple):
241 platform_list = list(platform) 236 platform_list = list(platform)
242 else: 237 else:
243 platform_list.append(platform) 238 platform_list.append(platform)
244 bug.append(platform_results[platform]['bug']) 239 bug.append(platform_results[platform]['bug'])
245 expectations = self.get_expectations(platform_results[platfo rm]) 240 expectations = self.get_expectations(platform_results[platfo rm])
246 line = '%s [ %s ] %s [ %s ]' % (bug[0], ' '.join(platform_li st), test_name, ' '.join(expectations)) 241 line = '%s [ %s ] %s [ %s ]' % (bug[0], ' '.join(platform_li st), test_name, ' '.join(expectations))
247 line_list.append(str(line)) 242 line_list.append(str(line))
248 return line_list 243 return line_list
249 244
250 def write_to_test_expectations(self, host, path, line_list): 245 def write_to_test_expectations(self, line_list):
251 """Writes to TestExpectations. 246 """Writes to TestExpectations.
252 247
253 Writes the test expectations lines in |line_list| to the test 248 Writes the test expectations lines in |line_list| to the test
254 expectations file. 249 expectations file.
255 250
256 The place in the file where the new lines are inserted is after a 251 The place in the file where the new lines are inserted is after a
257 marker comment line. If this marker comment line is not found, it will 252 marker comment line. If this marker comment line is not found, it will
258 be added to the end of the file. 253 be added to the end of the file.
259 254
260 Args: 255 Args:
261 host: A Host object.
262 path: The path to the file general TestExpectations file.
263 line_list: A list of w3c test expectations lines. 256 line_list: A list of w3c test expectations lines.
264 """ 257 """
258 port = self.host.port_factory.get()
259 expectations_file = port.path_to_generic_test_expectations_file()
265 comment_line = '# Tests added from W3C auto import bot' 260 comment_line = '# Tests added from W3C auto import bot'
266 file_contents = host.filesystem.read_text_file(path) 261 file_contents = self.host.filesystem.read_text_file(expectations_file)
267 w3c_comment_line_index = file_contents.find(comment_line) 262 w3c_comment_line_index = file_contents.find(comment_line)
268 all_lines = '' 263 all_lines = ''
269 for line in line_list: 264 for line in line_list:
270 end_bracket_index = line.split().index(']') 265 end_bracket_index = line.split().index(']')
271 test_name = line.split()[end_bracket_index + 1] 266 test_name = line.split()[end_bracket_index + 1]
272 if test_name in file_contents: 267 if test_name in file_contents:
273 continue 268 continue
274 all_lines += str(line) + '\n' 269 all_lines += str(line) + '\n'
275 all_lines = all_lines[:-1] 270 all_lines = all_lines[:-1]
276 if w3c_comment_line_index == -1: 271 if w3c_comment_line_index == -1:
277 file_contents += '\n%s\n' % comment_line 272 file_contents += '\n%s\n' % comment_line
278 file_contents += all_lines 273 file_contents += all_lines
279 else: 274 else:
280 end_of_comment_line = (file_contents[w3c_comment_line_index:].find(' \n')) + w3c_comment_line_index 275 end_of_comment_line = (file_contents[w3c_comment_line_index:].find(' \n')) + w3c_comment_line_index
281 new_data = file_contents[: end_of_comment_line + 1] + all_lines + fi le_contents[end_of_comment_line:] 276 new_data = file_contents[: end_of_comment_line + 1] + all_lines + fi le_contents[end_of_comment_line:]
282 file_contents = new_data 277 file_contents = new_data
283 host.filesystem.write_text_file(path, file_contents) 278 self.host.filesystem.write_text_file(expectations_file, file_contents)
284 279
285 def get_expected_txt_files(self, tests_results): 280 def get_expected_txt_files(self, tests_results):
286 """Fetches new baseline files for tests that sould be rebaselined. 281 """Fetches new baseline files for tests that should be rebaselined.
287 282
288 Invokes webkit-patch rebaseline-from-try-jobs in order to download new 283 Invokes webkit-patch rebaseline-from-try-jobs in order to download new
289 -expected.txt files for testharness.js tests that did not crash or time 284 -expected.txt files for testharness.js tests that did not crash or time
290 out. Then, the platform-specific test is removed from the overall 285 out. Then, the platform-specific test is removed from the overall
291 failure test dictionary. 286 failure test dictionary.
292 287
293 Args: 288 Args:
294 tests_results: A dictmapping test name to platform to test results. 289 tests_results: A dict mapping test name to platform to test results.
295 290
296 Returns: 291 Returns:
297 An updated tests_results dictionary without the platform-specific 292 An updated tests_results dictionary without the platform-specific
298 testharness.js tests that required new baselines to be downloaded 293 testharness.js tests that required new baselines to be downloaded
299 from `webkit-patch rebaseline-from-try-jobs`. 294 from `webkit-patch rebaseline-from-try-jobs`.
300 """ 295 """
301 finder = WebKitFinder(self._host.filesystem) 296 tests = self.host.executive.run_command(['git', 'diff', 'master', '--nam e-only']).splitlines()
302 tests = self._host.executive.run_command(['git', 'diff', 'master', '--na me-only']).splitlines() 297 tests_to_rebaseline, tests_results = self.get_tests_to_rebaseline(tests, tests_results)
303 tests_to_rebaseline, tests_results = self.get_tests_to_rebaseline(finder , tests, tests_results)
304 if tests_to_rebaseline: 298 if tests_to_rebaseline:
305 webkit_patch = self._host.filesystem.join( 299 webkit_patch = self.host.filesystem.join(
306 finder.chromium_base(), finder.webkit_base(), finder.path_to_scr ipt('webkit-patch')) 300 self.finder.chromium_base(), self.finder.webkit_base(), self.fin der.path_to_script('webkit-patch'))
307 self._host.executive.run_command([ 301 self.host.executive.run_command([
308 'python', 302 'python',
309 webkit_patch, 303 webkit_patch,
310 'rebaseline-cl', 304 'rebaseline-cl',
311 '--verbose', 305 '--verbose',
312 '--no-trigger-jobs', 306 '--no-trigger-jobs',
313 ] + tests_to_rebaseline) 307 ] + tests_to_rebaseline)
314 return tests_results 308 return tests_results
315 309
316 def get_tests_to_rebaseline(self, webkit_finder, tests, tests_results): 310 def get_tests_to_rebaseline(self, tests, tests_results):
317 """Returns a list of tests to download new baselines for. 311 """Returns a list of tests to download new baselines for.
318 312
319 Creates a list of tests to rebaseline depending on the tests' platform- 313 Creates a list of tests to rebaseline depending on the tests' platform-
320 specific results. In general, this will be non-ref tests that failed 314 specific results. In general, this will be non-ref tests that failed
321 due to a baseline mismatch (rather than crash or timeout). 315 due to a baseline mismatch (rather than crash or timeout).
322 316
323 Args: 317 Args:
324 webkit_finder: A WebKitFinder object.
325 tests: A list of new imported tests. 318 tests: A list of new imported tests.
326 tests_results: A dictionary of failing tests results. 319 tests_results: A dictionary of failing tests results.
327 320
328 Returns: 321 Returns:
329 A pair: A set of tests to be rebaselined, and an updated 322 A pair: A set of tests to be rebaselined, and an updated
330 tests_results dictionary. These tests to be rebaselined includes 323 tests_results dictionary. These tests to be rebaselined includes
331 both testharness.js tests and ref tests that failed some try job. 324 both testharness.js tests and ref tests that failed some try job.
332 """ 325 """
333 tests_to_rebaseline = set() 326 tests_to_rebaseline = set()
334 layout_tests_rel_path = self._host.filesystem.relpath( 327 layout_tests_rel_path = self.host.filesystem.relpath(
335 webkit_finder.layout_tests_dir(), webkit_finder.chromium_base()) 328 self.finder.layout_tests_dir(), self.finder.chromium_base())
336 for test in tests: 329 for test in tests:
337 test_path = self._host.filesystem.relpath(test, layout_tests_rel_pat h) 330 test_path = self.host.filesystem.relpath(test, layout_tests_rel_path )
338 if self.is_js_test(webkit_finder, test) and tests_results.get(test_p ath): 331 if self.is_js_test(test) and tests_results.get(test_path):
339 for platform in tests_results[test_path].keys(): 332 for platform in tests_results[test_path].keys():
340 if tests_results[test_path][platform]['actual'] not in ['CRA SH', 'TIMEOUT']: 333 if tests_results[test_path][platform]['actual'] not in ['CRA SH', 'TIMEOUT']:
341 del tests_results[test_path][platform] 334 del tests_results[test_path][platform]
342 tests_to_rebaseline.add(test_path) 335 tests_to_rebaseline.add(test_path)
343 return list(tests_to_rebaseline), tests_results 336 return list(tests_to_rebaseline), tests_results
344 337
345 def is_js_test(self, webkit_finder, test_path): 338 def is_js_test(self, test_path):
346 absolute_path = self._host.filesystem.join(webkit_finder.chromium_base() , test_path) 339 absolute_path = self.host.filesystem.join(self.finder.chromium_base(), t est_path)
347 test_parser = TestParser(absolute_path, self._host) 340 test_parser = TestParser(absolute_path, self.host)
348 return test_parser.is_jstest() 341 return test_parser.is_jstest()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698