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 """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 try: | |
| 69 _log.debug('Getting results for Rietveld issue %d.', issue_number) | |
|
wkorman
2016/12/05 05:58:35
Q for whomever may know: when are we moving to Ger
qyearsley
2016/12/05 17:55:21
There are ramifications for this tool, but not so
wkorman
2016/12/05 18:56:25
Sounds reasonable, perhaps worth checking in with
qyearsley
2016/12/07 00:42:27
Aye - just filed a bug for this: http://crbug.com/
| |
| 70 builds_to_results = self._fetch_results(builds) | |
| 71 except _MissingResults as error: | |
| 72 _log.error(error.message) | |
| 73 return | |
| 74 | |
| 75 test_prefix_list = {} | |
| 67 if args: | 76 if args: |
| 68 test_prefix_list = {} | |
| 69 for test in args: | 77 for test in args: |
| 70 test_prefix_list[test] = {b: BASELINE_SUFFIX_LIST for b in build s} | 78 test_prefix_list[test] = {b: BASELINE_SUFFIX_LIST for b in build s} |
| 71 else: | 79 else: |
| 72 test_prefix_list = self._test_prefix_list( | 80 test_prefix_list = self._test_prefix_list( |
| 73 issue_number, only_changed_tests=options.only_changed_tests) | 81 issue_number, |
| 82 builds_to_results, | |
| 83 only_changed_tests=options.only_changed_tests) | |
| 74 | 84 |
| 75 self._log_test_prefix_list(test_prefix_list) | 85 self._log_test_prefix_list(test_prefix_list) |
| 76 | 86 |
| 77 if options.dry_run: | 87 if options.dry_run: |
| 78 return | 88 return |
| 79 self.rebaseline(options, test_prefix_list) | 89 self.rebaseline(options, test_prefix_list) |
| 80 | 90 |
| 81 def _get_issue_number(self, options): | 91 def _get_issue_number(self, options): |
| 82 """Gets the Rietveld CL number from either |options| or from the current local branch.""" | 92 """Gets the Rietveld CL number from either |options| or from the current local branch.""" |
| 83 if options.issue: | 93 if options.issue: |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 117 if builders_without_builds: | 127 if builders_without_builds: |
| 118 _log.info('Triggering try jobs for:') | 128 _log.info('Triggering try jobs for:') |
| 119 command = ['try'] | 129 command = ['try'] |
| 120 for builder in sorted(builders_without_builds): | 130 for builder in sorted(builders_without_builds): |
| 121 _log.info(' %s', builder) | 131 _log.info(' %s', builder) |
| 122 command.extend(['-b', builder]) | 132 command.extend(['-b', builder]) |
| 123 self.git_cl().run(command) | 133 self.git_cl().run(command) |
| 124 | 134 |
| 125 return bool(builders_with_pending_builds or builders_without_builds) | 135 return bool(builders_with_pending_builds or builders_without_builds) |
| 126 | 136 |
| 127 def _test_prefix_list(self, issue_number, only_changed_tests): | 137 def _try_bots(self): |
| 128 """Returns a collection of test, builder and file extensions to get new baselines for. | 138 """Returns a collection of try bot builders to fetch results for.""" |
| 139 return self._tool.builders.all_try_builder_names() | |
| 140 | |
| 141 def _fetch_results(self, builds): | |
| 142 """Fetch results for each build. | |
| 143 | |
| 144 There should be a one-to-one correspondence between Builds, supported | |
| 145 platforms, and try bots. If not all of the builds can be fetched, then | |
| 146 continuing with rebaselining may yield incorrect results, when the new | |
| 147 baselines are deduped, an old baseline may be kept for the platform | |
| 148 that's missing results. | |
| 149 | |
| 150 Returns: | |
| 151 A dict mapping Build to LayoutTestResults. | |
| 152 """ | |
| 153 buildbot = self._tool.buildbot | |
| 154 results = {} | |
| 155 for build in builds: | |
| 156 results_url = buildbot.results_url(build.builder_name, build.build_n umber) | |
| 157 layout_test_results = buildbot.fetch_results(build) | |
| 158 if layout_test_results is None: | |
| 159 raise _MissingResults( | |
|
wkorman
2016/12/05 05:58:35
Worth documenting exceptions a method may raise in
qyearsley
2016/12/05 17:55:21
Added a "Raises:" part to the docstring.
| |
| 160 'Failed to fetch results from "%s".\n' | |
| 161 'Try starting a new job for %s by running :\n' | |
| 162 ' git cl try -b %s' % | |
| 163 (results_url, build.builder_name, build.builder_name)) | |
| 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) |
| 237 | |
| 238 | |
| 239 class _MissingResults(Exception): | |
|
wkorman
2016/12/05 05:58:35
Probably worth a doc comment or something in name
qyearsley
2016/12/05 17:55:21
Changed name to _MissingLayoutTestResults.
My rat
wkorman
2016/12/05 18:56:25
Yes, that sounds preferable.
qyearsley
2016/12/07 00:42:27
Now done :-)
| |
| 240 pass | |
| OLD | NEW |