| 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 |
| 11 import logging | 11 import logging |
| 12 import optparse | 12 import optparse |
| 13 | 13 |
| 14 from webkitpy.common.checkout.scm.git import Git | 14 from webkitpy.common.checkout.scm.git import Git |
| 15 from webkitpy.common.net.rietveld import latest_try_jobs | 15 from webkitpy.common.net.buildbot import Build |
| 16 from webkitpy.common.net.rietveld import latest_try_jobs, changed_files |
| 16 from webkitpy.common.net.web import Web | 17 from webkitpy.common.net.web import Web |
| 18 from webkitpy.common.webkit_finder import WebKitFinder |
| 17 from webkitpy.layout_tests.models.test_expectations import BASELINE_SUFFIX_LIST | 19 from webkitpy.layout_tests.models.test_expectations import BASELINE_SUFFIX_LIST |
| 18 from webkitpy.tool.commands.rebaseline import AbstractParallelRebaselineCommand | 20 from webkitpy.tool.commands.rebaseline import AbstractParallelRebaselineCommand |
| 19 from webkitpy.tool.commands.rebaseline import Build | |
| 20 | 21 |
| 21 | 22 |
| 22 _log = logging.getLogger(__name__) | 23 _log = logging.getLogger(__name__) |
| 23 | 24 |
| 24 | 25 |
| 25 class RebaselineCL(AbstractParallelRebaselineCommand): | 26 class RebaselineCL(AbstractParallelRebaselineCommand): |
| 26 name = "rebaseline-cl" | 27 name = "rebaseline-cl" |
| 27 help_text = "Fetches new baselines for one CL, from layout test runs on try
bots." | 28 help_text = "Fetches new baselines for a CL from test runs on try bots." |
| 29 long_help = ("By default, this command will check the latest try results " |
| 30 "and download new baselines for any tests that have been " |
| 31 "changed in the given CL that have failed and have new " |
| 32 "baselines. After downloading, the baselines for different " |
| 33 "platforms should be optimized (conslidated).") |
| 28 show_in_main_help = True | 34 show_in_main_help = True |
| 29 | 35 |
| 30 def __init__(self): | 36 def __init__(self): |
| 31 super(RebaselineCL, self).__init__(options=[ | 37 super(RebaselineCL, self).__init__(options=[ |
| 32 optparse.make_option( | 38 optparse.make_option( |
| 33 '--issue', type='int', default=None, | 39 '--issue', type='int', default=None, |
| 34 help='Rietveld issue number; if none given, this will be obtaine
d via `git cl issue`.'), | 40 help='Rietveld issue number; if none given, this will be obtaine
d via `git cl issue`.'), |
| 35 optparse.make_option( | 41 optparse.make_option( |
| 36 '--dry-run', action='store_true', default=False, | 42 '--dry-run', action='store_true', default=False, |
| 37 help='Dry run mode; list actions that would be performed but do
not do anything.'), | 43 help='Dry run mode; list actions that would be performed but do
not do anything.'), |
| 44 optparse.make_option( |
| 45 '--only-changed-tests', action='store_true', default=False, |
| 46 help='Only download new baselines for tests that are changed in
the CL.'), |
| 38 self.no_optimize_option, | 47 self.no_optimize_option, |
| 39 self.results_directory_option, | 48 self.results_directory_option, |
| 40 ]) | 49 ]) |
| 41 self.web = Web() | 50 self.web = Web() |
| 42 | 51 |
| 43 def execute(self, options, args, tool): | 52 def execute(self, options, args, tool): |
| 44 issue_number = self._get_issue_number(options) | 53 issue_number = self._get_issue_number(options) |
| 45 if not issue_number: | 54 if not issue_number: |
| 46 return | 55 return |
| 47 if args: | 56 if args: |
| 48 test_prefix_list = {} | 57 test_prefix_list = {} |
| 49 try_jobs = latest_try_jobs(issue_number, self._try_bots(), self.web) | 58 try_jobs = latest_try_jobs(issue_number, self._try_bots(), self.web) |
| 50 builds = [Build(j.builder_name, j.build_number) for j in try_jobs] | 59 builds = [Build(j.builder_name, j.build_number) for j in try_jobs] |
| 51 for test in args: | 60 for test in args: |
| 52 test_prefix_list[test] = {b: BASELINE_SUFFIX_LIST for b in build
s} | 61 test_prefix_list[test] = {b: BASELINE_SUFFIX_LIST for b in build
s} |
| 53 else: | 62 else: |
| 54 test_prefix_list = self._test_prefix_list(issue_number) | 63 test_prefix_list = self._test_prefix_list( |
| 64 issue_number, only_changed_tests=options.only_changed_tests) |
| 55 self._log_test_prefix_list(test_prefix_list) | 65 self._log_test_prefix_list(test_prefix_list) |
| 56 | 66 |
| 57 if options.dry_run: | 67 if options.dry_run: |
| 58 return | 68 return |
| 59 self._rebaseline(options, test_prefix_list) | 69 self._rebaseline(options, test_prefix_list) |
| 60 | 70 |
| 61 def _get_issue_number(self, options): | 71 def _get_issue_number(self, options): |
| 62 """Gets the Rietveld CL number from either |options| or from the current
local branch.""" | 72 """Gets the Rietveld CL number from either |options| or from the current
local branch.""" |
| 63 if options.issue: | 73 if options.issue: |
| 64 return options.issue | 74 return options.issue |
| 65 issue_number = self.git().get_issue_number() | 75 issue_number = self.git().get_issue_number() |
| 66 _log.debug('Issue number for current branch: %s', issue_number) | 76 _log.debug('Issue number for current branch: %s', issue_number) |
| 67 if not issue_number.isdigit(): | 77 if not issue_number.isdigit(): |
| 68 _log.error('No issue number given and no issue for current branch.') | 78 _log.error('No issue number given and no issue for current branch.') |
| 69 return None | 79 return None |
| 70 return int(issue_number) | 80 return int(issue_number) |
| 71 | 81 |
| 72 def git(self): | 82 def git(self): |
| 73 """Returns a Git instance; can be overridden for tests.""" | 83 """Returns a Git instance; can be overridden for tests.""" |
| 74 # Pass in a current working directory inside of the repo so | 84 # Pass in a current working directory inside of the repo so |
| 75 # that this command can be called from outside of the repo. | 85 # that this command can be called from outside of the repo. |
| 76 return Git(cwd=self._tool.filesystem.dirname(self._tool.path())) | 86 return Git(cwd=self._tool.filesystem.dirname(self._tool.path())) |
| 77 | 87 |
| 78 def _test_prefix_list(self, issue_number): | 88 def _test_prefix_list(self, issue_number, only_changed_tests): |
| 79 """Returns a collection of test, builder and file extensions to get new
baselines for.""" | 89 """Returns a collection of test, builder and file extensions to get new
baselines for. |
| 90 |
| 91 Args: |
| 92 issue_number: The CL number of the change which needs new baselines. |
| 93 only_changed_tests: Whether to only include baselines for tests that |
| 94 are changed in this CL. If False, all new baselines for failing |
| 95 tests will be downloaded, even for tests that were not modified. |
| 96 |
| 97 Returns: |
| 98 A dict containing information about which new baselines to download. |
| 99 """ |
| 80 builds_to_tests = self._builds_to_tests(issue_number) | 100 builds_to_tests = self._builds_to_tests(issue_number) |
| 101 if only_changed_tests: |
| 102 files_in_cl = changed_files(issue_number, self.web) |
| 103 finder = WebKitFinder(self._tool.filesystem) |
| 104 tests_in_cl = [finder.layout_test_name(f) for f in files_in_cl] |
| 81 result = {} | 105 result = {} |
| 82 for build, tests in builds_to_tests.iteritems(): | 106 for build, tests in builds_to_tests.iteritems(): |
| 83 for test in tests: | 107 for test in tests: |
| 108 if only_changed_tests and test not in tests_in_cl: |
| 109 continue |
| 84 if test not in result: | 110 if test not in result: |
| 85 result[test] = {} | 111 result[test] = {} |
| 86 result[test][build] = BASELINE_SUFFIX_LIST | 112 result[test][build] = BASELINE_SUFFIX_LIST |
| 87 return result | 113 return result |
| 88 | 114 |
| 89 def _builds_to_tests(self, issue_number): | 115 def _builds_to_tests(self, issue_number): |
| 90 """Fetches a list of try bots, and for each, fetches tests with new base
lines.""" | 116 """Fetches a list of try bots, and for each, fetches tests with new base
lines.""" |
| 91 _log.debug('Getting results for Rietveld issue %d.', issue_number) | 117 _log.debug('Getting results for Rietveld issue %d.', issue_number) |
| 92 try_jobs = latest_try_jobs(issue_number, self._try_bots(), self.web) | 118 try_jobs = latest_try_jobs(issue_number, self._try_bots(), self.web) |
| 93 if not try_jobs: | 119 if not try_jobs: |
| 94 _log.debug('No try job results for builders in: %r.', self._try_bots
()) | 120 _log.debug('No try job results for builders in: %r.', self._try_bots
()) |
| 95 builds_to_tests = {} | 121 builds_to_tests = {} |
| 96 for job in try_jobs: | 122 for job in try_jobs: |
| 97 test_results = self._unexpected_mismatch_results(job) | 123 test_results = self._unexpected_mismatch_results(job) |
| 98 build = Build(job.builder_name, job.build_number) | 124 build = Build(job.builder_name, job.build_number) |
| 99 builds_to_tests[build] = sorted(r.test_name() for r in test_results) | 125 builds_to_tests[build] = sorted(r.test_name() for r in test_results) |
| 100 return builds_to_tests | 126 return builds_to_tests |
| 101 | 127 |
| 102 def _try_bots(self): | 128 def _try_bots(self): |
| 103 """Retuns a collection of try bot builders to fetch results for.""" | 129 """Returns a collection of try bot builders to fetch results for.""" |
| 104 return self._tool.builders.all_try_builder_names() | 130 return self._tool.builders.all_try_builder_names() |
| 105 | 131 |
| 106 def _unexpected_mismatch_results(self, try_job): | 132 def _unexpected_mismatch_results(self, try_job): |
| 107 """Fetches a list of LayoutTestResult objects for unexpected results wit
h new baselines.""" | 133 """Fetches a list of LayoutTestResult objects for unexpected results wit
h new baselines.""" |
| 108 buildbot = self._tool.buildbot | 134 buildbot = self._tool.buildbot |
| 109 results_url = buildbot.results_url(try_job.builder_name, try_job.build_n
umber) | 135 results_url = buildbot.results_url(try_job.builder_name, try_job.build_n
umber) |
| 110 layout_test_results = buildbot.fetch_layout_test_results(results_url) | 136 layout_test_results = buildbot.fetch_layout_test_results(results_url) |
| 111 if layout_test_results is None: | 137 if layout_test_results is None: |
| 112 _log.warning('Failed to request layout test results from "%s".', res
ults_url) | 138 _log.warning('Failed to request layout test results from "%s".', res
ults_url) |
| 113 return [] | 139 return [] |
| 114 return layout_test_results.unexpected_mismatch_results() | 140 return layout_test_results.unexpected_mismatch_results() |
| 115 | 141 |
| 116 @staticmethod | 142 @staticmethod |
| 117 def _log_test_prefix_list(test_prefix_list): | 143 def _log_test_prefix_list(test_prefix_list): |
| 118 """Logs the tests to download new baselines for.""" | 144 """Logs the tests to download new baselines for.""" |
| 119 if not test_prefix_list: | 145 if not test_prefix_list: |
| 120 _log.info('No tests to rebaseline.') | 146 _log.info('No tests to rebaseline.') |
| 121 return | 147 return |
| 122 _log.info('Tests to rebaseline:') | 148 _log.info('Tests to rebaseline:') |
| 123 for test, builds in test_prefix_list.iteritems(): | 149 for test, builds in test_prefix_list.iteritems(): |
| 124 builds_str = ', '.join(sorted('%s (%s)' % (b.builder_name, b.build_n
umber) for b in builds)) | 150 builds_str = ', '.join(sorted('%s (%s)' % (b.builder_name, b.build_n
umber) for b in builds)) |
| 125 _log.info(' %s: %s', test, builds_str) | 151 _log.info(' %s: %s', test, builds_str) |
| OLD | NEW |