| 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 layout test expectations and baselines when updating w3c tests. | 5 """Updates layout test expectations and baselines when updating w3c 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.net.git_cl import GitCL | 17 from webkitpy.common.net.git_cl import GitCL |
| 17 from webkitpy.common.net.rietveld import Rietveld | 18 from webkitpy.common.net.rietveld import Rietveld |
| 18 from webkitpy.common.webkit_finder import WebKitFinder | 19 from webkitpy.common.webkit_finder import WebKitFinder |
| 19 from webkitpy.layout_tests.models.test_expectations import TestExpectationLine | 20 from webkitpy.layout_tests.models.test_expectations import TestExpectationLine,
TestExpectations |
| 20 from webkitpy.w3c.test_parser import TestParser | 21 from webkitpy.w3c.test_parser import TestParser |
| 21 | 22 |
| 22 _log = logging.getLogger(__name__) | 23 _log = logging.getLogger(__name__) |
| 23 | 24 |
| 24 MARKER_COMMENT = '# ====== New tests from w3c-test-autoroller added here ======' | 25 MARKER_COMMENT = '# ====== New tests from w3c-test-autoroller added here ======' |
| 25 | 26 |
| 26 | 27 |
| 27 class WPTExpectationsUpdater(object): | 28 class WPTExpectationsUpdater(object): |
| 28 | 29 |
| 29 def __init__(self, host): | 30 def __init__(self, host): |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 66 test_expectation_lines = self.create_line_list(test_expectations) | 67 test_expectation_lines = self.create_line_list(test_expectations) |
| 67 self.write_to_test_expectations(test_expectation_lines) | 68 self.write_to_test_expectations(test_expectation_lines) |
| 68 return 0 | 69 return 0 |
| 69 | 70 |
| 70 def get_issue_number(self): | 71 def get_issue_number(self): |
| 71 """Returns current CL number. Can be replaced in unit tests.""" | 72 """Returns current CL number. Can be replaced in unit tests.""" |
| 72 return GitCL(self.host).get_issue_number() | 73 return GitCL(self.host).get_issue_number() |
| 73 | 74 |
| 74 def get_try_bots(self): | 75 def get_try_bots(self): |
| 75 """Returns try bot names. Can be replaced in unit tests.""" | 76 """Returns try bot names. Can be replaced in unit tests.""" |
| 77 # TODO(qyearsley): This method is unnecessary; unit tests can set up |
| 78 # a BuilderList with try builder names, instead of overriding this. |
| 76 return self.host.builders.all_try_builder_names() | 79 return self.host.builders.all_try_builder_names() |
| 77 | 80 |
| 78 def get_failing_results_dict(self, build): | 81 def get_failing_results_dict(self, build): |
| 79 """Returns a nested dict of failing test results. | 82 """Returns a nested dict of failing test results. |
| 80 | 83 |
| 81 Retrieves a full list of layout test results from a builder result URL. | 84 Retrieves a full list of layout test results from a builder result URL. |
| 82 Collects the builder name, platform and a list of tests that did not | 85 Collects the builder name, platform and a list of tests that did not |
| 83 run as expected. | 86 run as expected. |
| 84 | 87 |
| 85 Args: | 88 Args: |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 121 test_name = result.test_name() | 124 test_name = result.test_name() |
| 122 test_dict[test_name] = { | 125 test_dict[test_name] = { |
| 123 full_port_name: { | 126 full_port_name: { |
| 124 'expected': result.expected_results(), | 127 'expected': result.expected_results(), |
| 125 'actual': result.actual_results(), | 128 'actual': result.actual_results(), |
| 126 'bug': 'crbug.com/626703' | 129 'bug': 'crbug.com/626703' |
| 127 } | 130 } |
| 128 } | 131 } |
| 129 return test_dict | 132 return test_dict |
| 130 | 133 |
| 131 def _port_name_to_platform_specifier(self, port_name): | |
| 132 """Maps a port name to the platform specifier used in expectation lines. | |
| 133 | |
| 134 For example: | |
| 135 linux-trusty -> Trusty | |
| 136 mac-mac10.11 -> Mac10.11. | |
| 137 """ | |
| 138 builder_name = self.host.builders.builder_name_for_port_name(port_name) | |
| 139 specifiers = self.host.builders.specifiers_for_builder(builder_name) | |
| 140 return specifiers[0] | |
| 141 | |
| 142 def merge_dicts(self, target, source, path=None): | 134 def merge_dicts(self, target, source, path=None): |
| 143 """Recursively merges nested dictionaries. | 135 """Recursively merges nested dictionaries. |
| 144 | 136 |
| 145 Args: | 137 Args: |
| 146 target: First dictionary, which is updated based on source. | 138 target: First dictionary, which is updated based on source. |
| 147 source: Second dictionary, not modified. | 139 source: Second dictionary, not modified. |
| 148 | 140 |
| 149 Returns: | 141 Returns: |
| 150 An updated target dictionary. | 142 An updated target dictionary. |
| 151 """ | 143 """ |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 254 'platform': { | 246 'platform': { |
| 255 'expected: 'PASS', | 247 'expected: 'PASS', |
| 256 'actual': 'FAIL', | 248 'actual': 'FAIL', |
| 257 'bug': 'crbug.com/11111' | 249 'bug': 'crbug.com/11111' |
| 258 } | 250 } |
| 259 } | 251 } |
| 260 } | 252 } |
| 261 | 253 |
| 262 Returns: | 254 Returns: |
| 263 A list of test expectations lines with the format: | 255 A list of test expectations lines with the format: |
| 264 ['BUG_URL [PLATFORM(S)] TEST_MAME [EXPECTATION(S)]'] | 256 ['BUG_URL [PLATFORM(S)] TEST_NAME [EXPECTATION(S)]'] |
| 265 """ | 257 """ |
| 266 line_list = [] | 258 line_list = [] |
| 267 for test_name, port_results in merged_results.iteritems(): | 259 for test_name, port_results in merged_results.iteritems(): |
| 268 for port_names in port_results: | 260 for port_names in sorted(port_results): |
| 269 if test_name.startswith('external'): | 261 if test_name.startswith('external'): |
| 270 bug_part = port_results[port_names]['bug'] | 262 line_parts = [port_results[port_names]['bug']] |
| 271 specifier_part = '[ %s ]' % ' '.join(self.to_list(port_names
)) | 263 specifier_part = self.specifier_part(self.to_list(port_names
), test_name) |
| 272 expectations_part = '[ %s ]' % ' '.join(self.get_expectation
s(port_results[port_names])) | 264 if specifier_part: |
| 273 line = ' '.join([bug_part, specifier_part, test_name, expect
ations_part]) | 265 line_parts.append(specifier_part) |
| 274 line_list.append(line) | 266 line_parts.append(test_name) |
| 267 line_parts.append('[ %s ]' % ' '.join(self.get_expectations(
port_results[port_names]))) |
| 268 line_list.append(' '.join(line_parts)) |
| 275 return line_list | 269 return line_list |
| 276 | 270 |
| 271 def specifier_part(self, port_names, test_name): |
| 272 """Returns the specifier part for a new test expectations line. |
| 273 |
| 274 Args: |
| 275 port_names: A list of full port names that the line should apply to. |
| 276 test_name: The test name for the expectation line. |
| 277 |
| 278 Returns: |
| 279 The specifier part of the new expectation line, e.g. "[ Mac ]". |
| 280 This will be an empty string if the line should apply to all platfor
ms. |
| 281 """ |
| 282 specifiers = [] |
| 283 for name in sorted(port_names): |
| 284 specifiers.append(self.host.builders.version_specifier_for_port_name
(name)) |
| 285 port = self.host.port_factory.get() |
| 286 specifiers = self.simplify_specifiers(specifiers, port.configuration_spe
cifier_macros()) |
| 287 specifiers.extend(self.skipped_specifiers(test_name)) |
| 288 if not specifiers: |
| 289 return '' |
| 290 return '[ %s ]' % ' '.join(specifiers) |
| 291 |
| 277 @staticmethod | 292 @staticmethod |
| 278 def to_list(tuple_or_value): | 293 def to_list(tuple_or_value): |
| 294 """Converts a tuple to a list, and a string value to a one-item list.""" |
| 279 if isinstance(tuple_or_value, tuple): | 295 if isinstance(tuple_or_value, tuple): |
| 280 return list(tuple_or_value) | 296 return list(tuple_or_value) |
| 281 return [tuple_or_value] | 297 return [tuple_or_value] |
| 282 | 298 |
| 299 def skipped_specifiers(self, test_name): |
| 300 """Returns a list of platform specifiers for which the test is skipped."
"" |
| 301 # TODO(qyearsley): Change Port.skips_test so that this can be simplified
. |
| 302 specifiers = [] |
| 303 for port in self.all_try_builder_ports(): |
| 304 generic_expectations = TestExpectations(port, tests=[test_name], inc
lude_overrides=False) |
| 305 full_expectations = TestExpectations(port, tests=[test_name], includ
e_overrides=True) |
| 306 if port.skips_test(test_name, generic_expectations, full_expectation
s): |
| 307 specifiers.append(self.host.builders.version_specifier_for_port_
name(port.name())) |
| 308 return specifiers |
| 309 |
| 310 @memoized |
| 311 def all_try_builder_ports(self): |
| 312 """Returns a list of Port objects for all try builders.""" |
| 313 return [self.host.port_factory.get_from_builder_name(name) for name in s
elf.get_try_bots()] |
| 314 |
| 315 @staticmethod |
| 316 def simplify_specifiers(specifiers, configuration_specifier_macros): # pyli
nt: disable=unused-argument |
| 317 """Converts some collection of specifiers to an equivalent and maybe sho
rter list. |
| 318 |
| 319 The input strings are all case-insensitive, but the strings in the |
| 320 return value will all be capitalized. |
| 321 |
| 322 Args: |
| 323 specifiers: A collection of lower-case specifiers. |
| 324 configuration_specifier_macros: A dict mapping "macros" for |
| 325 groups of specifiers to lists of specific specifiers. In |
| 326 practice, this is a dict mapping operating systems to |
| 327 supported versions, e.g. {"win": ["win7", "win10"]}. |
| 328 |
| 329 Returns: |
| 330 A shortened list of specifiers. For example, ["win7", "win10"] |
| 331 would be converted to ["Win"]. If the given list covers all |
| 332 supported platforms, then an empty list is returned. |
| 333 This list will be sorted and have capitalized specifier strings. |
| 334 """ |
| 335 specifiers = {specifier.lower() for specifier in specifiers} |
| 336 for macro_specifier, version_specifiers in configuration_specifier_macro
s.iteritems(): |
| 337 macro_specifier = macro_specifier.lower() |
| 338 version_specifiers = {specifier.lower() for specifier in version_spe
cifiers} |
| 339 if version_specifiers.issubset(specifiers): |
| 340 specifiers -= version_specifiers |
| 341 specifiers.add(macro_specifier) |
| 342 if specifiers == set(configuration_specifier_macros): |
| 343 return [] |
| 344 return sorted(specifier.capitalize() for specifier in specifiers) |
| 345 |
| 283 def write_to_test_expectations(self, line_list): | 346 def write_to_test_expectations(self, line_list): |
| 284 """Writes to TestExpectations. | 347 """Writes to TestExpectations. |
| 285 | 348 |
| 286 The place in the file where the new lines are inserted is after a | 349 The place in the file where the new lines are inserted is after a |
| 287 marker comment line. If this marker comment line is not found, it will | 350 marker comment line. If this marker comment line is not found, it will |
| 288 be added to the end of the file. | 351 be added to the end of the file. |
| 289 | 352 |
| 290 Args: | 353 Args: |
| 291 line_list: A list of lines to add to the TestExpectations file. | 354 line_list: A list of lines to add to the TestExpectations file. |
| 292 """ | 355 """ |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 376 | 439 |
| 377 Args: | 440 Args: |
| 378 test_path: A file path relative to the layout tests directory. | 441 test_path: A file path relative to the layout tests directory. |
| 379 This might correspond to a deleted file or a non-test. | 442 This might correspond to a deleted file or a non-test. |
| 380 """ | 443 """ |
| 381 absolute_path = self.host.filesystem.join(self.finder.layout_tests_dir()
, test_path) | 444 absolute_path = self.host.filesystem.join(self.finder.layout_tests_dir()
, test_path) |
| 382 test_parser = TestParser(absolute_path, self.host) | 445 test_parser = TestParser(absolute_path, self.host) |
| 383 if not test_parser.test_doc: | 446 if not test_parser.test_doc: |
| 384 return False | 447 return False |
| 385 return test_parser.is_jstest() | 448 return test_parser.is_jstest() |
| OLD | NEW |