| 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 |