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 """Updates expectations and baselines when updating web-platform-tests. | 5 """Updates expectations and baselines when updating web-platform-tests. |
6 | 6 |
7 Specifically, this class fetches results from try bots for the current CL, then | 7 Specifically, this class fetches results from try bots for the current CL, then |
8 (1) downloads new baseline files for any tests that can be rebaselined, and | 8 (1) downloads new baseline files for any tests that can be rebaselined, and |
9 (2) updates the generic TestExpectations file for any other failing tests. | 9 (2) updates the generic TestExpectations file for any other failing tests. |
10 """ | 10 """ |
11 | 11 |
12 import argparse | 12 import argparse |
13 import copy | 13 import copy |
14 import logging | 14 import logging |
15 | 15 |
16 from webkitpy.common.memoized import memoized | 16 from webkitpy.common.memoized import memoized |
17 from webkitpy.common.net.git_cl import GitCL | 17 from webkitpy.common.net.git_cl import GitCL |
18 from webkitpy.common.webkit_finder import WebKitFinder | 18 from webkitpy.common.webkit_finder import WebKitFinder |
19 from webkitpy.layout_tests.models.test_expectations import TestExpectationLine,
TestExpectations | 19 from webkitpy.layout_tests.models.test_expectations import TestExpectationLine,
TestExpectations |
20 from webkitpy.w3c.test_parser import TestParser | 20 from webkitpy.w3c.wpt_manifest import WPTManifest |
21 | 21 |
22 _log = logging.getLogger(__name__) | 22 _log = logging.getLogger(__name__) |
23 | 23 |
24 MARKER_COMMENT = '# ====== New tests from wpt-importer added here ======' | 24 MARKER_COMMENT = '# ====== New tests from wpt-importer added here ======' |
25 | 25 |
26 | 26 |
27 class WPTExpectationsUpdater(object): | 27 class WPTExpectationsUpdater(object): |
28 | 28 |
29 def __init__(self, host): | 29 def __init__(self, host): |
30 self.host = host | 30 self.host = host |
31 self.finder = WebKitFinder(self.host.filesystem) | 31 self.finder = WebKitFinder(self.host.filesystem) |
| 32 self.port = self.host.port_factory.get() |
32 | 33 |
33 def run(self, args=None): | 34 def run(self, args=None): |
34 """Downloads text new baselines and adds test expectations lines.""" | 35 """Downloads text new baselines and adds test expectations lines.""" |
35 parser = argparse.ArgumentParser(description=__doc__) | 36 parser = argparse.ArgumentParser(description=__doc__) |
36 parser.add_argument('-v', '--verbose', action='store_true', help='More v
erbose logging.') | 37 parser.add_argument('-v', '--verbose', action='store_true', help='More v
erbose logging.') |
37 args = parser.parse_args(args) | 38 args = parser.parse_args(args) |
38 | 39 |
39 log_level = logging.DEBUG if args.verbose else logging.INFO | 40 log_level = logging.DEBUG if args.verbose else logging.INFO |
40 logging.basicConfig(level=log_level, format='%(message)s') | 41 logging.basicConfig(level=log_level, format='%(message)s') |
41 | 42 |
42 issue_number = self.get_issue_number() | 43 issue_number = self.get_issue_number() |
43 if issue_number == 'None': | 44 if issue_number == 'None': |
44 _log.error('No issue on current branch.') | 45 _log.error('No issue on current branch.') |
45 return 1 | 46 return 1 |
46 | 47 |
47 builds = self.get_latest_try_jobs() | 48 builds = self.get_latest_try_jobs() |
48 _log.debug('Latest try jobs: %r', builds) | 49 _log.debug('Latest try jobs: %r', builds) |
49 if not builds: | 50 if not builds: |
50 _log.error('No try job information was collected.') | 51 _log.error('No try job information was collected.') |
51 return 1 | 52 return 1 |
52 | 53 |
| 54 # The manifest may be used below to do check which tests are reference t
ests. |
| 55 WPTManifest.ensure_manifest(self.host) |
| 56 |
53 # Here we build up a dict of failing test results for all platforms. | 57 # Here we build up a dict of failing test results for all platforms. |
54 test_expectations = {} | 58 test_expectations = {} |
55 for build in builds: | 59 for build in builds: |
56 port_results = self.get_failing_results_dict(build) | 60 port_results = self.get_failing_results_dict(build) |
57 test_expectations = self.merge_dicts(test_expectations, port_results
) | 61 test_expectations = self.merge_dicts(test_expectations, port_results
) |
58 | 62 |
59 # And then we merge results for different platforms that had the same re
sults. | 63 # And then we merge results for different platforms that had the same re
sults. |
60 for test_name, platform_result in test_expectations.iteritems(): | 64 for test_name, platform_result in test_expectations.iteritems(): |
61 # platform_result is a dict mapping platforms to results. | 65 # platform_result is a dict mapping platforms to results. |
62 test_expectations[test_name] = self.merge_same_valued_keys(platform_
result) | 66 test_expectations[test_name] = self.merge_same_valued_keys(platform_
result) |
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
279 port_names: A list of full port names that the line should apply to. | 283 port_names: A list of full port names that the line should apply to. |
280 test_name: The test name for the expectation line. | 284 test_name: The test name for the expectation line. |
281 | 285 |
282 Returns: | 286 Returns: |
283 The specifier part of the new expectation line, e.g. "[ Mac ]". | 287 The specifier part of the new expectation line, e.g. "[ Mac ]". |
284 This will be an empty string if the line should apply to all platfor
ms. | 288 This will be an empty string if the line should apply to all platfor
ms. |
285 """ | 289 """ |
286 specifiers = [] | 290 specifiers = [] |
287 for name in sorted(port_names): | 291 for name in sorted(port_names): |
288 specifiers.append(self.host.builders.version_specifier_for_port_name
(name)) | 292 specifiers.append(self.host.builders.version_specifier_for_port_name
(name)) |
289 port = self.host.port_factory.get() | 293 |
290 specifiers.extend(self.skipped_specifiers(test_name)) | 294 specifiers.extend(self.skipped_specifiers(test_name)) |
291 specifiers = self.simplify_specifiers(specifiers, port.configuration_spe
cifier_macros()) | 295 specifiers = self.simplify_specifiers(specifiers, self.port.configuratio
n_specifier_macros()) |
292 if not specifiers: | 296 if not specifiers: |
293 return '' | 297 return '' |
294 return '[ %s ]' % ' '.join(specifiers) | 298 return '[ %s ]' % ' '.join(specifiers) |
295 | 299 |
296 @staticmethod | 300 @staticmethod |
297 def to_list(tuple_or_value): | 301 def to_list(tuple_or_value): |
298 """Converts a tuple to a list, and a string value to a one-item list.""" | 302 """Converts a tuple to a list, and a string value to a one-item list.""" |
299 if isinstance(tuple_or_value, tuple): | 303 if isinstance(tuple_or_value, tuple): |
300 return list(tuple_or_value) | 304 return list(tuple_or_value) |
301 return [tuple_or_value] | 305 return [tuple_or_value] |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
353 The place in the file where the new lines are inserted is after a | 357 The place in the file where the new lines are inserted is after a |
354 marker comment line. If this marker comment line is not found, it will | 358 marker comment line. If this marker comment line is not found, it will |
355 be added to the end of the file. | 359 be added to the end of the file. |
356 | 360 |
357 Args: | 361 Args: |
358 line_list: A list of lines to add to the TestExpectations file. | 362 line_list: A list of lines to add to the TestExpectations file. |
359 """ | 363 """ |
360 _log.info('Lines to write to TestExpectations:') | 364 _log.info('Lines to write to TestExpectations:') |
361 for line in line_list: | 365 for line in line_list: |
362 _log.info(' %s', line) | 366 _log.info(' %s', line) |
363 port = self.host.port_factory.get() | 367 expectations_file_path = self.port.path_to_generic_test_expectations_fil
e() |
364 expectations_file_path = port.path_to_generic_test_expectations_file() | |
365 file_contents = self.host.filesystem.read_text_file(expectations_file_pa
th) | 368 file_contents = self.host.filesystem.read_text_file(expectations_file_pa
th) |
366 marker_comment_index = file_contents.find(MARKER_COMMENT) | 369 marker_comment_index = file_contents.find(MARKER_COMMENT) |
367 line_list = [line for line in line_list if self._test_name_from_expectat
ion_string(line) not in file_contents] | 370 line_list = [line for line in line_list if self._test_name_from_expectat
ion_string(line) not in file_contents] |
368 if not line_list: | 371 if not line_list: |
369 return | 372 return |
370 if marker_comment_index == -1: | 373 if marker_comment_index == -1: |
371 file_contents += '\n%s\n' % MARKER_COMMENT | 374 file_contents += '\n%s\n' % MARKER_COMMENT |
372 file_contents += '\n'.join(line_list) | 375 file_contents += '\n'.join(line_list) |
373 else: | 376 else: |
374 end_of_marker_line = (file_contents[marker_comment_index:].find('\n'
)) + marker_comment_index | 377 end_of_marker_line = (file_contents[marker_comment_index:].find('\n'
)) + marker_comment_index |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
430 new_test_results = copy.deepcopy(test_results) | 433 new_test_results = copy.deepcopy(test_results) |
431 tests_to_rebaseline = set() | 434 tests_to_rebaseline = set() |
432 for test_path in test_results: | 435 for test_path in test_results: |
433 for platform, result in test_results[test_path].iteritems(): | 436 for platform, result in test_results[test_path].iteritems(): |
434 if self.can_rebaseline(test_path, result): | 437 if self.can_rebaseline(test_path, result): |
435 del new_test_results[test_path][platform] | 438 del new_test_results[test_path][platform] |
436 tests_to_rebaseline.add(test_path) | 439 tests_to_rebaseline.add(test_path) |
437 return sorted(tests_to_rebaseline), new_test_results | 440 return sorted(tests_to_rebaseline), new_test_results |
438 | 441 |
439 def can_rebaseline(self, test_path, result): | 442 def can_rebaseline(self, test_path, result): |
440 return (self.is_js_test(test_path) and | 443 if self.is_reference_test(test_path): |
441 result['actual'] not in ('CRASH', 'TIMEOUT')) | 444 return False |
| 445 if result['actual'] in ('CRASH', 'TIMEOUT', 'MISSING'): |
| 446 return False |
| 447 return True |
442 | 448 |
443 def is_js_test(self, test_path): | 449 def is_reference_test(self, test_path): |
444 """Checks whether a given file is a testharness.js test. | 450 """Checks whether a given file is a testharness.js test.""" |
445 | 451 return bool(self.port.reference_files(test_path)) |
446 TODO(qyearsley): This may not behave how we want it to for virtual tests
. | |
447 TODO(qyearsley): Avoid using TestParser; maybe this should use | |
448 Port.test_type, or Port.reference_files to see whether it's not | |
449 a reference test? | |
450 | |
451 Args: | |
452 test_path: A file path relative to the layout tests directory. | |
453 This might correspond to a deleted file or a non-test. | |
454 """ | |
455 absolute_path = self.host.filesystem.join(self.finder.layout_tests_dir()
, test_path) | |
456 test_parser = TestParser(absolute_path, self.host) | |
457 if not test_parser.test_doc: | |
458 return False | |
459 return test_parser.is_jstest() | |
460 | 452 |
461 def _get_try_bots(self): | 453 def _get_try_bots(self): |
462 return self.host.builders.all_try_builder_names() | 454 return self.host.builders.all_try_builder_names() |
OLD | NEW |