Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(18)

Side by Side Diff: third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/auto_rebaseline.py

Issue 2191423003: Extract AutoRebaseline out of rebaseline.py. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebased Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/auto_rebaseline_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 # found in the LICENSE file.
4
5 """A command to download new baselines for NeedsRebaseline tests.
6
7 This command checks the list of tests with NeedsRebaseline expectations,
8 and downloads the latest baselines for those tests from the results archived
9 by the continuous builders.
10 """
11
12 import logging
13 import optparse
14 import re
15 import sys
16 import time
17 import traceback
18 import urllib2
19
20 from webkitpy.common.net.buildbot import Build
21 from webkitpy.layout_tests.models.test_expectations import TestExpectations, BAS ELINE_SUFFIX_LIST
22 from webkitpy.tool.commands.rebaseline import AbstractParallelRebaselineCommand
23
24
25 _log = logging.getLogger(__name__)
26
27
28 class AutoRebaseline(AbstractParallelRebaselineCommand):
29 name = "auto-rebaseline"
30 help_text = "Rebaselines any NeedsRebaseline lines in TestExpectations that have cycled through all the bots."
31 AUTO_REBASELINE_BRANCH_NAME = "auto-rebaseline-temporary-branch"
32 AUTO_REBASELINE_ALT_BRANCH_NAME = "auto-rebaseline-alt-temporary-branch"
33
34 # Rietveld uploader stinks. Limit the number of rebaselines in a given patch to keep upload from failing.
35 # FIXME: http://crbug.com/263676 Obviously we should fix the uploader here.
36 MAX_LINES_TO_REBASELINE = 200
37
38 SECONDS_BEFORE_GIVING_UP = 300
39
40 def __init__(self):
41 super(AutoRebaseline, self).__init__(options=[
42 # FIXME: Remove this option.
43 self.no_optimize_option,
44 # FIXME: Remove this option.
45 self.results_directory_option,
46 optparse.make_option("--auth-refresh-token-json", help="Rietveld aut h refresh JSON token."),
47 optparse.make_option("--dry-run", action='store_true', default=False ,
48 help='Run without creating a temporary branch, committing locally, or uploading/landing '
49 'changes to the remote repository.')
50 ])
51 self._blame_regex = re.compile(r'''
52 ^(\S*) # Commit hash
53 [^(]* \( # Whitespace and open parenthesis
54 < # Email address is surrounded by <>
55 (
56 [^@]+ # Username preceding @
57 @
58 [^@>]+ # Domain terminated by @ or >, some lines have an ad ditional @ fragment after the email.
59 )
60 .*?([^ ]*) # Test file name
61 \ \[ # Single space followed by opening [ for expectation specifier
62 [^[]*$ # Prevents matching previous [ for version specifier s instead of expectation specifiers
63 ''', re.VERBOSE)
64
65 def bot_revision_data(self, scm):
66 revisions = []
67 for result in self.build_data().values():
68 if result.run_was_interrupted():
69 _log.error("Can't rebaseline because the latest run on %s exited early.", result.builder_name())
70 return []
71 revisions.append({
72 "builder": result.builder_name(),
73 "revision": result.chromium_revision(scm),
74 })
75 return revisions
76
77 @staticmethod
78 def _strip_comments(line):
79 comment_index = line.find("#")
80 if comment_index == -1:
81 comment_index = len(line)
82 return re.sub(r"\s+", " ", line[:comment_index].strip())
83
84 def tests_to_rebaseline(self, tool, min_revision, print_revisions):
85 port = tool.port_factory.get()
86 expectations_file_path = port.path_to_generic_test_expectations_file()
87
88 tests = set()
89 revision = None
90 commit = None
91 author = None
92 bugs = set()
93 has_any_needs_rebaseline_lines = False
94
95 for line in tool.scm().blame(expectations_file_path).split("\n"):
96 line = self._strip_comments(line)
97 if "NeedsRebaseline" not in line:
98 continue
99
100 has_any_needs_rebaseline_lines = True
101
102 parsed_line = self._blame_regex.match(line)
103 if not parsed_line:
104 # Deal gracefully with inability to parse blame info for a line in TestExpectations.
105 # Parsing could fail if for example during local debugging the d eveloper modifies
106 # TestExpectations and does not commit.
107 _log.info("Couldn't find blame info for expectations line, skipp ing [line=%s].", line)
108 continue
109
110 commit_hash = parsed_line.group(1)
111 commit_position = tool.scm().commit_position_from_git_commit(commit_ hash)
112
113 test = parsed_line.group(3)
114 if print_revisions:
115 _log.info("%s is waiting for r%s", test, commit_position)
116
117 if not commit_position or commit_position > min_revision:
118 continue
119
120 if revision and commit_position != revision:
121 continue
122
123 if not revision:
124 revision = commit_position
125 commit = commit_hash
126 author = parsed_line.group(2)
127
128 bugs.update(re.findall(r"crbug\.com\/(\d+)", line))
129 tests.add(test)
130
131 if len(tests) >= self.MAX_LINES_TO_REBASELINE:
132 _log.info("Too many tests to rebaseline in one patch. Doing the first %d.", self.MAX_LINES_TO_REBASELINE)
133 break
134
135 return tests, revision, commit, author, bugs, has_any_needs_rebaseline_l ines
136
137 @staticmethod
138 def link_to_patch(commit):
139 return "https://chromium.googlesource.com/chromium/src/+/" + commit
140
141 def commit_message(self, author, revision, commit, bugs):
142 bug_string = ""
143 if bugs:
144 bug_string = "BUG=%s\n" % ",".join(bugs)
145
146 return """Auto-rebaseline for r%s
147
148 %s
149
150 %sTBR=%s
151 """ % (revision, self.link_to_patch(commit), bug_string, author)
152
153 def get_test_prefix_list(self, tests):
154 test_prefix_list = {}
155 lines_to_remove = {}
156
157 for builder_name in self._release_builders():
158 port_name = self._tool.builders.port_name_for_builder_name(builder_n ame)
159 port = self._tool.port_factory.get(port_name)
160 expectations = TestExpectations(port, include_overrides=True)
161 for test in expectations.get_needs_rebaseline_failures():
162 if test not in tests:
163 continue
164
165 if test not in test_prefix_list:
166 lines_to_remove[test] = []
167 test_prefix_list[test] = {}
168 lines_to_remove[test].append(builder_name)
169 test_prefix_list[test][Build(builder_name)] = BASELINE_SUFFIX_LI ST
170
171 return test_prefix_list, lines_to_remove
172
173 def _run_git_cl_command(self, options, command):
174 subprocess_command = ['git', 'cl'] + command
175 if options.verbose:
176 subprocess_command.append('--verbose')
177 if options.auth_refresh_token_json:
178 subprocess_command.append('--auth-refresh-token-json')
179 subprocess_command.append(options.auth_refresh_token_json)
180
181 process = self._tool.executive.popen(subprocess_command, stdout=self._to ol.executive.PIPE,
182 stderr=self._tool.executive.STDOUT)
183 last_output_time = time.time()
184
185 # git cl sometimes completely hangs. Bail if we haven't gotten any outpu t to stdout/stderr in a while.
186 while process.poll() is None and time.time() < last_output_time + self.S ECONDS_BEFORE_GIVING_UP:
187 # FIXME: This doesn't make any sense. readline blocks, so all this c ode to
188 # try and bail is useless. Instead, we should do the readline calls on a
189 # subthread. Then the rest of this code would make sense.
190 out = process.stdout.readline().rstrip('\n')
191 if out:
192 last_output_time = time.time()
193 _log.info(out)
194
195 if process.poll() is None:
196 _log.error('Command hung: %s', subprocess_command)
197 return False
198 return True
199
200 # FIXME: Move this somewhere more general.
201 @staticmethod
202 def tree_status():
203 blink_tree_status_url = "http://chromium-status.appspot.com/status"
204 status = urllib2.urlopen(blink_tree_status_url).read().lower()
205 if 'closed' in status or status == "0":
206 return 'closed'
207 elif 'open' in status or status == "1":
208 return 'open'
209 return 'unknown'
210
211 def execute(self, options, args, tool):
212 if tool.scm().executable_name == "svn":
213 _log.error("Auto rebaseline only works with a git checkout.")
214 return
215
216 if not options.dry_run and tool.scm().has_working_directory_changes():
217 _log.error("Cannot proceed with working directory changes. Clean wor king directory first.")
218 return
219
220 revision_data = self.bot_revision_data(tool.scm())
221 if not revision_data:
222 return
223
224 min_revision = int(min([item["revision"] for item in revision_data]))
225 tests, revision, commit, author, bugs, _ = self.tests_to_rebaseline(
226 tool, min_revision, print_revisions=options.verbose)
227
228 if options.verbose:
229 _log.info("Min revision across all bots is %s.", min_revision)
230 for item in revision_data:
231 _log.info("%s: r%s", item["builder"], item["revision"])
232
233 if not tests:
234 _log.debug('No tests to rebaseline.')
235 return
236
237 if self.tree_status() == 'closed':
238 _log.info('Cannot proceed. Tree is closed.')
239 return
240
241 _log.info('Rebaselining %s for r%s by %s.', list(tests), revision, autho r)
242
243 test_prefix_list, _ = self.get_test_prefix_list(tests)
244
245 did_switch_branches = False
246 did_finish = False
247 old_branch_name_or_ref = ''
248 rebaseline_branch_name = self.AUTO_REBASELINE_BRANCH_NAME
249 try:
250 # Save the current branch name and check out a clean branch for the patch.
251 old_branch_name_or_ref = tool.scm().current_branch_or_ref()
252 if old_branch_name_or_ref == self.AUTO_REBASELINE_BRANCH_NAME:
253 rebaseline_branch_name = self.AUTO_REBASELINE_ALT_BRANCH_NAME
254 if not options.dry_run:
255 tool.scm().delete_branch(rebaseline_branch_name)
256 tool.scm().create_clean_branch(rebaseline_branch_name)
257 did_switch_branches = True
258
259 if test_prefix_list:
260 self._rebaseline(options, test_prefix_list)
261
262 if options.dry_run:
263 return
264
265 tool.scm().commit_locally_with_message(
266 self.commit_message(author, revision, commit, bugs))
267
268 # FIXME: It would be nice if we could dcommit the patch without uplo ading, but still
269 # go through all the precommit hooks. For rebaselines with lots of f iles, uploading
270 # takes a long time and sometimes fails, but we don't want to commit if, e.g. the
271 # tree is closed.
272 did_finish = self._run_git_cl_command(options, ['upload', '-f'])
273
274 if did_finish:
275 # Uploading can take a very long time. Do another pull to make s ure TestExpectations is up to date,
276 # so the dcommit can go through.
277 # FIXME: Log the pull and dcommit stdout/stderr to the log-serve r.
278 tool.executive.run_command(['git', 'pull'])
279
280 self._run_git_cl_command(options, ['land', '-f', '-v'])
281 except OSError:
282 traceback.print_exc(file=sys.stderr)
283 finally:
284 if did_switch_branches:
285 if did_finish:
286 # Close the issue if dcommit failed.
287 issue_already_closed = tool.executive.run_command(
288 ['git', 'config', 'branch.%s.rietveldissue' % rebaseline _branch_name],
289 return_exit_code=True)
290 if not issue_already_closed:
291 self._run_git_cl_command(options, ['set_close'])
292
293 tool.scm().ensure_cleanly_tracking_remote_master()
294 if old_branch_name_or_ref:
295 tool.scm().checkout_branch(old_branch_name_or_ref)
296 tool.scm().delete_branch(rebaseline_branch_name)
OLDNEW
« no previous file with comments | « no previous file | third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/auto_rebaseline_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698