Chromium Code Reviews| 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_MAME [EXPECTATION(S)]'] |
|
jeffcarp
2017/02/13 20:42:18
I know this isn't part of the CL but should this b
qyearsley
2017/02/14 00:00:51
Yep! Unless mame has some meaning I'm not aware of
| |
| 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)) | |
|
qyearsley
2017/02/13 19:54:23
The purpose of this is so that Android is included
| |
| 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 a list of specifiers to an equivalent and maybe shorter 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 list 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 """ | |
| 334 specifiers = {specifier.lower() for specifier in specifiers} | |
| 335 for macro, versions in configuration_specifier_macros.iteritems(): | |
| 336 versions = set(versions) | |
| 337 if versions <= specifiers: | |
| 338 specifiers -= versions | |
| 339 specifiers.add(macro) | |
| 340 if specifiers == set(configuration_specifier_macros): | |
| 341 return [] | |
| 342 return sorted(specifier.capitalize() for specifier in specifiers) | |
|
qyearsley
2017/02/13 19:54:23
This is the main part of this change.
| |
| 343 | |
| 283 def write_to_test_expectations(self, line_list): | 344 def write_to_test_expectations(self, line_list): |
| 284 """Writes to TestExpectations. | 345 """Writes to TestExpectations. |
| 285 | 346 |
| 286 The place in the file where the new lines are inserted is after a | 347 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 | 348 marker comment line. If this marker comment line is not found, it will |
| 288 be added to the end of the file. | 349 be added to the end of the file. |
| 289 | 350 |
| 290 Args: | 351 Args: |
| 291 line_list: A list of lines to add to the TestExpectations file. | 352 line_list: A list of lines to add to the TestExpectations file. |
| 292 """ | 353 """ |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 376 | 437 |
| 377 Args: | 438 Args: |
| 378 test_path: A file path relative to the layout tests directory. | 439 test_path: A file path relative to the layout tests directory. |
| 379 This might correspond to a deleted file or a non-test. | 440 This might correspond to a deleted file or a non-test. |
| 380 """ | 441 """ |
| 381 absolute_path = self.host.filesystem.join(self.finder.layout_tests_dir() , test_path) | 442 absolute_path = self.host.filesystem.join(self.finder.layout_tests_dir() , test_path) |
| 382 test_parser = TestParser(absolute_path, self.host) | 443 test_parser = TestParser(absolute_path, self.host) |
| 383 if not test_parser.test_doc: | 444 if not test_parser.test_doc: |
| 384 return False | 445 return False |
| 385 return test_parser.is_jstest() | 446 return test_parser.is_jstest() |
| OLD | NEW |