| 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 """A command to fetch new baselines from try jobs for a Rietveld issue. | 5 """A command to fetch new baselines from try jobs for a Rietveld issue. |
| 6 | 6 |
| 7 This command interacts with the Rietveld API to get information about try jobs | 7 This command interacts with the Rietveld API to get information about try jobs |
| 8 with layout test results. | 8 with layout test results. |
| 9 """ | 9 """ |
| 10 | 10 |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 50 ]) | 50 ]) |
| 51 self.rietveld = Rietveld(Web()) | 51 self.rietveld = Rietveld(Web()) |
| 52 | 52 |
| 53 def execute(self, options, args, tool): | 53 def execute(self, options, args, tool): |
| 54 self._tool = tool | 54 self._tool = tool |
| 55 issue_number = self._get_issue_number(options) | 55 issue_number = self._get_issue_number(options) |
| 56 if not issue_number: | 56 if not issue_number: |
| 57 return | 57 return |
| 58 | 58 |
| 59 builds = self.rietveld.latest_try_jobs(issue_number, self._try_bots()) | 59 builds = self.rietveld.latest_try_jobs(issue_number, self._try_bots()) |
| 60 |
| 60 if options.trigger_jobs: | 61 if options.trigger_jobs: |
| 61 if self.trigger_jobs_for_missing_builds(builds): | 62 if self.trigger_jobs_for_missing_builds(builds): |
| 62 _log.info('Please re-run webkit-patch rebaseline-cl once all pen
ding try jobs have finished.') | 63 _log.info('Please re-run webkit-patch rebaseline-cl once all pen
ding try jobs have finished.') |
| 63 return | 64 return |
| 64 if not builds: | 65 if not builds: |
| 65 _log.info('No builds to download baselines from.') | 66 _log.info('No builds to download baselines from.') |
| 66 | 67 |
| 68 _log.debug('Getting results for Rietveld issue %d.', issue_number) |
| 69 builds_to_results = self._fetch_results(builds) |
| 70 if builds_to_results is None: |
| 71 return |
| 72 |
| 73 test_prefix_list = {} |
| 67 if args: | 74 if args: |
| 68 test_prefix_list = {} | |
| 69 for test in args: | 75 for test in args: |
| 70 test_prefix_list[test] = {b: BASELINE_SUFFIX_LIST for b in build
s} | 76 test_prefix_list[test] = {b: BASELINE_SUFFIX_LIST for b in build
s} |
| 71 else: | 77 else: |
| 72 test_prefix_list = self._test_prefix_list( | 78 test_prefix_list = self._test_prefix_list( |
| 73 issue_number, only_changed_tests=options.only_changed_tests) | 79 issue_number, |
| 80 builds_to_results, |
| 81 only_changed_tests=options.only_changed_tests) |
| 74 | 82 |
| 75 self._log_test_prefix_list(test_prefix_list) | 83 self._log_test_prefix_list(test_prefix_list) |
| 76 | 84 |
| 77 if options.dry_run: | 85 if options.dry_run: |
| 78 return | 86 return |
| 79 self.rebaseline(options, test_prefix_list) | 87 self.rebaseline(options, test_prefix_list) |
| 80 | 88 |
| 81 def _get_issue_number(self, options): | 89 def _get_issue_number(self, options): |
| 82 """Gets the Rietveld CL number from either |options| or from the current
local branch.""" | 90 """Gets the Rietveld CL number from either |options| or from the current
local branch.""" |
| 83 if options.issue: | 91 if options.issue: |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 117 if builders_without_builds: | 125 if builders_without_builds: |
| 118 _log.info('Triggering try jobs for:') | 126 _log.info('Triggering try jobs for:') |
| 119 command = ['try'] | 127 command = ['try'] |
| 120 for builder in sorted(builders_without_builds): | 128 for builder in sorted(builders_without_builds): |
| 121 _log.info(' %s', builder) | 129 _log.info(' %s', builder) |
| 122 command.extend(['-b', builder]) | 130 command.extend(['-b', builder]) |
| 123 self.git_cl().run(command) | 131 self.git_cl().run(command) |
| 124 | 132 |
| 125 return bool(builders_with_pending_builds or builders_without_builds) | 133 return bool(builders_with_pending_builds or builders_without_builds) |
| 126 | 134 |
| 127 def _test_prefix_list(self, issue_number, only_changed_tests): | 135 def _try_bots(self): |
| 128 """Returns a collection of test, builder and file extensions to get new
baselines for. | 136 """Returns a collection of try bot builders to fetch results for.""" |
| 137 return self._tool.builders.all_try_builder_names() |
| 138 |
| 139 def _fetch_results(self, builds): |
| 140 """Fetches results for each build. |
| 141 |
| 142 There should be a one-to-one correspondence between Builds, supported |
| 143 platforms, and try bots. If not all of the builds can be fetched, then |
| 144 continuing with rebaselining may yield incorrect results, when the new |
| 145 baselines are deduped, an old baseline may be kept for the platform |
| 146 that's missing results. |
| 147 |
| 148 Returns: |
| 149 A dict mapping Build to LayoutTestResults, or None if any results |
| 150 were not available. |
| 151 """ |
| 152 buildbot = self._tool.buildbot |
| 153 results = {} |
| 154 for build in builds: |
| 155 results_url = buildbot.results_url(build.builder_name, build.build_n
umber) |
| 156 layout_test_results = buildbot.fetch_results(build) |
| 157 if layout_test_results is None: |
| 158 _log.error( |
| 159 'Failed to fetch results from "%s".\n' |
| 160 'Try starting a new job for %s by running :\n' |
| 161 ' git cl try -b %s', |
| 162 results_url, build.builder_name, build.builder_name) |
| 163 return None |
| 164 results[build] = layout_test_results |
| 165 return results |
| 166 |
| 167 def _test_prefix_list(self, issue_number, builds_to_results, only_changed_te
sts): |
| 168 """Returns a collection of tests, builders and file extensions to get ne
w baselines for. |
| 129 | 169 |
| 130 Args: | 170 Args: |
| 131 issue_number: The CL number of the change which needs new baselines. | 171 issue_number: The CL number of the change which needs new baselines. |
| 172 builds_to_results: A dict mapping Builds to LayoutTestResults. |
| 132 only_changed_tests: Whether to only include baselines for tests that | 173 only_changed_tests: Whether to only include baselines for tests that |
| 133 are changed in this CL. If False, all new baselines for failing | 174 are changed in this CL. If False, all new baselines for failing |
| 134 tests will be downloaded, even for tests that were not modified. | 175 tests will be downloaded, even for tests that were not modified. |
| 135 | 176 |
| 136 Returns: | 177 Returns: |
| 137 A dict containing information about which new baselines to download. | 178 A dict containing information about which new baselines to download. |
| 138 """ | 179 """ |
| 139 builds_to_tests = self._builds_to_tests(issue_number) | 180 builds_to_tests = {} |
| 181 for build, results in builds_to_results.iteritems(): |
| 182 builds_to_tests[build] = self._tests_to_rebaseline(build, results) |
| 140 if only_changed_tests: | 183 if only_changed_tests: |
| 141 files_in_cl = self.rietveld.changed_files(issue_number) | 184 files_in_cl = self.rietveld.changed_files(issue_number) |
| 142 # Note, in the changed files list from Rietveld, paths always | 185 # Note, in the changed files list from Rietveld, paths always |
| 143 # use / as the separator, and they're always relative to repo root. | 186 # use / as the separator, and they're always relative to repo root. |
| 144 # TODO(qyearsley): Do this without using a hard-coded constant. | 187 # TODO(qyearsley): Do this without using a hard-coded constant. |
| 145 test_base = 'third_party/WebKit/LayoutTests/' | 188 test_base = 'third_party/WebKit/LayoutTests/' |
| 146 tests_in_cl = [f[len(test_base):] for f in files_in_cl if f.startswi
th(test_base)] | 189 tests_in_cl = [f[len(test_base):] for f in files_in_cl if f.startswi
th(test_base)] |
| 147 result = {} | 190 result = {} |
| 148 for build, tests in builds_to_tests.iteritems(): | 191 for build, tests in builds_to_tests.iteritems(): |
| 149 for test in tests: | 192 for test in tests: |
| 150 if only_changed_tests and test not in tests_in_cl: | 193 if only_changed_tests and test not in tests_in_cl: |
| 151 continue | 194 continue |
| 152 if test not in result: | 195 if test not in result: |
| 153 result[test] = {} | 196 result[test] = {} |
| 154 result[test][build] = BASELINE_SUFFIX_LIST | 197 result[test][build] = BASELINE_SUFFIX_LIST |
| 155 return result | 198 return result |
| 156 | 199 |
| 157 def _builds_to_tests(self, issue_number): | 200 def _tests_to_rebaseline(self, build, layout_test_results): |
| 158 """Fetches a list of try bots, and for each, fetches tests with new base
lines.""" | 201 """Fetches a list of tests that should be rebaselined for some build .""
" |
| 159 _log.debug('Getting results for Rietveld issue %d.', issue_number) | |
| 160 builds = self.rietveld.latest_try_jobs(issue_number, self._try_bots()) | |
| 161 if not builds: | |
| 162 _log.debug('No try job results for builders in: %r.', self._try_bots
()) | |
| 163 return {build: self._tests_to_rebaseline(build) for build in builds} | |
| 164 | |
| 165 def _try_bots(self): | |
| 166 """Returns a collection of try bot builders to fetch results for.""" | |
| 167 return self._tool.builders.all_try_builder_names() | |
| 168 | |
| 169 def _tests_to_rebaseline(self, build): | |
| 170 """Fetches a list of tests that should be rebaselined.""" | |
| 171 buildbot = self._tool.buildbot | |
| 172 results_url = buildbot.results_url(build.builder_name, build.build_numbe
r) | |
| 173 | |
| 174 layout_test_results = buildbot.fetch_layout_test_results(results_url) | |
| 175 if layout_test_results is None: | |
| 176 _log.warning('Failed to request layout test results from "%s".', res
ults_url) | |
| 177 return [] | |
| 178 | |
| 179 unexpected_results = layout_test_results.didnt_run_as_expected_results() | 202 unexpected_results = layout_test_results.didnt_run_as_expected_results() |
| 180 tests = sorted(r.test_name() for r in unexpected_results | 203 tests = sorted(r.test_name() for r in unexpected_results |
| 181 if r.is_missing_baseline() or r.has_mismatch_result()) | 204 if r.is_missing_baseline() or r.has_mismatch_result()) |
| 182 | 205 |
| 183 new_failures = self._fetch_tests_with_new_failures(build) | 206 new_failures = self._fetch_tests_with_new_failures(build) |
| 184 if new_failures is None: | 207 if new_failures is None: |
| 185 _log.warning('No retry summary available for build %s.', build) | 208 _log.warning('No retry summary available for build %s.', build) |
| 186 else: | 209 else: |
| 187 tests = [t for t in tests if t in new_failures] | 210 tests = [t for t in tests if t in new_failures] |
| 188 return tests | 211 return tests |
| 189 | 212 |
| 190 def _fetch_tests_with_new_failures(self, build): | 213 def _fetch_tests_with_new_failures(self, build): |
| 191 """Fetches a list of tests that failed with a patch in a given try job b
ut not without.""" | 214 """Fetches a list of tests that failed with a patch in a given try job b
ut not without.""" |
| 192 buildbot = self._tool.buildbot | 215 buildbot = self._tool.buildbot |
| 193 content = buildbot.fetch_retry_summary_json(build) | 216 content = buildbot.fetch_retry_summary_json(build) |
| 194 if content is None: | 217 if content is None: |
| 195 return None | 218 return None |
| 196 try: | 219 try: |
| 197 retry_summary = json.loads(content) | 220 retry_summary = json.loads(content) |
| 198 return retry_summary['failures'] | 221 return retry_summary['failures'] |
| 199 except (ValueError, KeyError): | 222 except (ValueError, KeyError): |
| 200 _log.warning('Unexepected retry summary content:\n%s', content) | 223 _log.warning('Unexpected retry summary content:\n%s', content) |
| 201 return None | 224 return None |
| 202 | 225 |
| 203 @staticmethod | 226 @staticmethod |
| 204 def _log_test_prefix_list(test_prefix_list): | 227 def _log_test_prefix_list(test_prefix_list): |
| 205 """Logs the tests to download new baselines for.""" | 228 """Logs the tests to download new baselines for.""" |
| 206 if not test_prefix_list: | 229 if not test_prefix_list: |
| 207 _log.info('No tests to rebaseline; exiting.') | 230 _log.info('No tests to rebaseline; exiting.') |
| 208 return | 231 return |
| 209 _log.debug('Tests to rebaseline:') | 232 _log.debug('Tests to rebaseline:') |
| 210 for test, builds in test_prefix_list.iteritems(): | 233 for test, builds in test_prefix_list.iteritems(): |
| 211 _log.debug(' %s:', test) | 234 _log.debug(' %s:', test) |
| 212 for build in sorted(builds): | 235 for build in sorted(builds): |
| 213 _log.debug(' %s', build) | 236 _log.debug(' %s', build) |
| OLD | NEW |