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 """ |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 47 rietveld = Rietveld(self.host.web) | 47 rietveld = Rietveld(self.host.web) |
| 48 builds = rietveld.latest_try_jobs(issue_number, self.get_try_bots()) | 48 builds = rietveld.latest_try_jobs(issue_number, self.get_try_bots()) |
| 49 _log.debug('Latest try jobs: %r', builds) | 49 _log.debug('Latest try jobs: %r', builds) |
| 50 if not builds: | 50 if not builds: |
| 51 _log.error('No try job information was collected.') | 51 _log.error('No try job information was collected.') |
| 52 return 1 | 52 return 1 |
| 53 | 53 |
| 54 # Here we build up a dict of failing test results for all platforms. | 54 # Here we build up a dict of failing test results for all platforms. |
| 55 test_expectations = {} | 55 test_expectations = {} |
| 56 for build in builds: | 56 for build in builds: |
| 57 platform_results = self.get_failing_results_dict(build) | 57 port_results = self.get_failing_results_dict(build) |
| 58 test_expectations = self.merge_dicts(test_expectations, platform_res ults) | 58 test_expectations = self.merge_dicts(test_expectations, port_results ) |
| 59 | 59 |
| 60 # And then we merge results for different platforms that had the same re sults. | 60 # And then we merge results for different platforms that had the same re sults. |
| 61 for test_name, platform_result in test_expectations.iteritems(): | 61 for test_name, platform_result in test_expectations.iteritems(): |
| 62 # platform_result is a dict mapping platforms to results. | 62 # platform_result is a dict mapping platforms to results. |
| 63 test_expectations[test_name] = self.merge_same_valued_keys(platform_ result) | 63 test_expectations[test_name] = self.merge_same_valued_keys(platform_ result) |
| 64 | 64 |
| 65 test_expectations = self.download_text_baselines(test_expectations) | 65 test_expectations = self.download_text_baselines(test_expectations) |
| 66 test_expectation_lines = self.create_line_list(test_expectations) | 66 test_expectation_lines = self.create_line_list(test_expectations) |
| 67 self.write_to_test_expectations(test_expectation_lines) | 67 self.write_to_test_expectations(test_expectation_lines) |
| 68 return 0 | 68 return 0 |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 80 | 80 |
| 81 Retrieves a full list of layout test results from a builder result URL. | 81 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 | 82 Collects the builder name, platform and a list of tests that did not |
| 83 run as expected. | 83 run as expected. |
| 84 | 84 |
| 85 Args: | 85 Args: |
| 86 build: A Build object. | 86 build: A Build object. |
| 87 | 87 |
| 88 Returns: | 88 Returns: |
| 89 A dictionary with the structure: { | 89 A dictionary with the structure: { |
| 90 'key': { | 90 'full-port-name': { |
| 91 'expected': 'TIMEOUT', | 91 'expected': 'TIMEOUT', |
| 92 'actual': 'CRASH', | 92 'actual': 'CRASH', |
| 93 'bug': 'crbug.com/11111' | 93 'bug': 'crbug.com/11111' |
| 94 } | 94 } |
| 95 } | 95 } |
| 96 If there are no failing results or no results could be fetched, | 96 If there are no failing results or no results could be fetched, |
| 97 this will return an empty dict. | 97 this will return an empty dictionary. |
| 98 """ | 98 """ |
| 99 layout_test_results = self.host.buildbot.fetch_results(build) | 99 layout_test_results = self.host.buildbot.fetch_results(build) |
| 100 if layout_test_results is None: | 100 if layout_test_results is None: |
| 101 _log.warning('No results for build %s', build) | 101 _log.warning('No results for build %s', build) |
| 102 return {} | 102 return {} |
| 103 platform = self.host.builders.port_name_for_builder_name(build.builder_n ame) | 103 port_name = self.host.builders.port_name_for_builder_name(build.builder_ name) |
| 104 test_results = layout_test_results.didnt_run_as_expected_results() | 104 test_results = layout_test_results.didnt_run_as_expected_results() |
| 105 failing_results_dict = self.generate_results_dict(platform, test_results ) | 105 failing_results_dict = self.generate_results_dict(port_name, test_result s) |
| 106 return failing_results_dict | 106 return failing_results_dict |
| 107 | 107 |
| 108 def generate_results_dict(self, full_port_name, test_results): | 108 def generate_results_dict(self, full_port_name, test_results): |
| 109 """Makes a dict with results for one platform. | 109 """Makes a dict with results for one platform. |
| 110 | 110 |
| 111 Args: | 111 Args: |
| 112 full_port_name: The full port name, e.g. "win-win10". | 112 full_port_name: The full-qualified port name, e.g. "win-win10". |
|
jeffcarp
2017/02/09 00:50:17
Should this be fully-qualified?
qyearsley
2017/02/09 17:01:55
Yep, it should be :-) The term "fully-qualified" w
| |
| 113 test_results: A list of LayoutTestResult objects. | 113 test_results: A list of LayoutTestResult objects. |
| 114 | 114 |
| 115 Returns: | 115 Returns: |
| 116 A dict mapping to platform string (e.g. "Win10") to a dict with | 116 A dict mapping the full port name to a dict with the results for |
| 117 the results for that test and that platform. | 117 the given test and platform. |
| 118 """ | 118 """ |
| 119 platform = self._port_name_to_platform_specifier(full_port_name) | |
| 120 test_dict = {} | 119 test_dict = {} |
| 121 for result in test_results: | 120 for result in test_results: |
| 122 test_dict[result.test_name()] = { | 121 test_name = result.test_name() |
| 123 platform: { | 122 test_dict[test_name] = { |
| 123 full_port_name: { | |
| 124 'expected': result.expected_results(), | 124 'expected': result.expected_results(), |
| 125 'actual': result.actual_results(), | 125 'actual': result.actual_results(), |
| 126 'bug': 'crbug.com/626703' | 126 'bug': 'crbug.com/626703' |
| 127 }} | 127 } |
| 128 } | |
| 128 return test_dict | 129 return test_dict |
| 129 | 130 |
| 130 def _port_name_to_platform_specifier(self, port_name): | 131 def _port_name_to_platform_specifier(self, port_name): |
| 131 """Maps a port name to the string used in test expectations lines. | 132 """Maps a port name to the platform specifier used in expectation lines. |
| 132 | 133 |
| 133 For example: | 134 For example: |
| 134 linux-trusty -> Trusty | 135 linux-trusty -> Trusty |
| 135 mac-mac10.11 -> Mac10.11. | 136 mac-mac10.11 -> Mac10.11. |
| 136 """ | 137 """ |
| 137 # TODO(qyearsley): Do this in a more robust way with Port classes. | 138 builder_name = self.host.builders.builder_name_for_port_name(port_name) |
| 138 if '-' in port_name: | 139 specifiers = self.host.builders.specifiers_for_builder(builder_name) |
| 139 return port_name[port_name.find('-') + 1:].capitalize() | 140 return specifiers[0] |
| 140 return port_name | |
| 141 | 141 |
| 142 def merge_dicts(self, target, source, path=None): | 142 def merge_dicts(self, target, source, path=None): |
| 143 """Recursively merges nested dictionaries. | 143 """Recursively merges nested dictionaries. |
| 144 | 144 |
| 145 Args: | 145 Args: |
| 146 target: First dictionary, which is updated based on source. | 146 target: First dictionary, which is updated based on source. |
| 147 source: Second dictionary, not modified. | 147 source: Second dictionary, not modified. |
| 148 | 148 |
| 149 Returns: | 149 Returns: |
| 150 An updated target dictionary. | 150 An updated target dictionary. |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 257 'bug': 'crbug.com/11111' | 257 'bug': 'crbug.com/11111' |
| 258 } | 258 } |
| 259 } | 259 } |
| 260 } | 260 } |
| 261 | 261 |
| 262 Returns: | 262 Returns: |
| 263 A list of test expectations lines with the format: | 263 A list of test expectations lines with the format: |
| 264 ['BUG_URL [PLATFORM(S)] TEST_MAME [EXPECTATION(S)]'] | 264 ['BUG_URL [PLATFORM(S)] TEST_MAME [EXPECTATION(S)]'] |
| 265 """ | 265 """ |
| 266 line_list = [] | 266 line_list = [] |
| 267 for test_name, platform_results in merged_results.iteritems(): | 267 for test_name, port_results in merged_results.iteritems(): |
| 268 for platform in platform_results: | 268 for port_names in port_results: |
| 269 if test_name.startswith('external'): | 269 if test_name.startswith('external'): |
| 270 platform_list = [] | 270 bug_part = port_results[port_names]['bug'] |
| 271 bug = [] | 271 specifier_part = '[ %s ]' % ' '.join(self.to_list(port_names )) |
| 272 expectations = [] | 272 expectations_part = '[ %s ]' % ' '.join(self.get_expectation s(port_results[port_names])) |
| 273 if isinstance(platform, tuple): | 273 line = ' '.join([bug_part, specifier_part, test_name, expect ations_part]) |
| 274 platform_list = list(platform) | 274 line_list.append(line) |
| 275 else: | |
| 276 platform_list.append(platform) | |
| 277 bug.append(platform_results[platform]['bug']) | |
| 278 expectations = self.get_expectations(platform_results[platfo rm]) | |
| 279 line = '%s [ %s ] %s [ %s ]' % (bug[0], ' '.join(platform_li st), test_name, ' '.join(expectations)) | |
| 280 line_list.append(str(line)) | |
| 281 return line_list | 275 return line_list |
| 282 | 276 |
| 277 @staticmethod | |
| 278 def to_list(tuple_or_value): | |
| 279 if isinstance(tuple_or_value, tuple): | |
| 280 return list(tuple_or_value) | |
| 281 return [tuple_or_value] | |
| 282 | |
| 283 def write_to_test_expectations(self, line_list): | 283 def write_to_test_expectations(self, line_list): |
| 284 """Writes to TestExpectations. | 284 """Writes to TestExpectations. |
| 285 | 285 |
| 286 The place in the file where the new lines are inserted is after a | 286 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 | 287 marker comment line. If this marker comment line is not found, it will |
| 288 be added to the end of the file. | 288 be added to the end of the file. |
| 289 | 289 |
| 290 Args: | 290 Args: |
| 291 line_list: A list of lines to add to the TestExpectations file. | 291 line_list: A list of lines to add to the TestExpectations file. |
| 292 """ | 292 """ |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 376 | 376 |
| 377 Args: | 377 Args: |
| 378 test_path: A file path relative to the layout tests directory. | 378 test_path: A file path relative to the layout tests directory. |
| 379 This might correspond to a deleted file or a non-test. | 379 This might correspond to a deleted file or a non-test. |
| 380 """ | 380 """ |
| 381 absolute_path = self.host.filesystem.join(self.finder.layout_tests_dir() , test_path) | 381 absolute_path = self.host.filesystem.join(self.finder.layout_tests_dir() , test_path) |
| 382 test_parser = TestParser(absolute_path, self.host) | 382 test_parser = TestParser(absolute_path, self.host) |
| 383 if not test_parser.test_doc: | 383 if not test_parser.test_doc: |
| 384 return False | 384 return False |
| 385 return test_parser.is_jstest() | 385 return test_parser.is_jstest() |
| OLD | NEW |