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

Side by Side Diff: scripts/slave/recipe_modules/auto_bisect/revision_state.py

Issue 940123005: Adding ability to bisect recipe to bisect into dependency repos. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@hax
Patch Set: Addressing feedback, adding TODOs Created 5 years, 9 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
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 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 """An interface for holding state and result of revisions in a bisect job. 5 """An interface for holding state and result of revisions in a bisect job.
6 6
7 When implementing support for tests other than perf, one should extend this 7 When implementing support for tests other than perf, one should extend this
8 class so that the bisect module and recipe can use it. 8 class so that the bisect module and recipe can use it.
9 9
10 See perf_revision_state for an example. 10 See perf_revision_state for an example.
11 """ 11 """
12 import hashlib
13 import os
14 import re
15
16 from . import depot_config
17
12 18
13 class RevisionState(object): 19 class RevisionState(object):
14 """Abstracts the state of a single revision on a bisect job.""" 20 """Abstracts the state of a single revision on a bisect job."""
15 21
16 def __init__(self, revision_string, bisector): 22 def __init__(self, revision_string, bisector, depot='chromium',
23 dependency_depot_name=None,base_revision=None,
24 deps_revision=None):
17 """Create a new instance to track the state of a revision. 25 """Create a new instance to track the state of a revision.
18 26
27 There are two use cases for this constructor:
28 - Creating a revision state for a chromium revision, OR
29 - Creating a revision state for a chromium revision plus an explicitly
30 specified revision of a dependency repository (a DEPS change).
31 In the first case a revision_string and a bisector are needed.
32 In the second case, revision_string must be None, and all of depot,
33 base_revision and deps_revision must be provided.
34
19 Args: 35 Args:
20 revision_string: should be in the following format: 36 revision_string (str): A git hash or a commit position in the chromium
21 [(chromium|src)@](<commit_pos>|<commit_hash)(,<repo>@<commit>)* 37 repository. If None, all kwargs must be given.
22 E.g.: 38 bisector (Bisector): The object performing the bisection.
23 'a0b1c2ffff89009909090' (full or abbrev. commit hash) 39 depot (dict): One of the entries in depot_config.DEPOT_DEPS_NAME that
24 '123456' 40 specifies which dependency to do the DEPS change on. It is expected to
25 'src@123456' 41 contain the 'chromium' string instead of None when not bisecting any
26 'chromium@123456' 42 dependencies.
27 'src@abc01234ffff,v8@00af5ceb888ff' 43 base_revision (RevisionState): The revision state to patch with the deps
28 bisector: an instance of Bisector, the object performing the bisection. 44 change.
45 depot_revision: The commit hash of the dependency repo to put in place of
46 the one set for the base_revision.
29 """ 47 """
48 # TODO(robertocn): Evaluate if the logic of this constructor should be
49 # split into separate methods.
30 super(RevisionState, self).__init__() 50 super(RevisionState, self).__init__()
31 self.bisector = bisector 51 self.bisector = bisector
32 self._good = None 52 self._good = None
53 self.deps = None
33 self.build_status_url = None 54 self.build_status_url = None
34 self.in_progress = False 55 self.in_progress = False
35 self.aborted = False 56 self.aborted = False
36 self.next_revision = None 57 self.next_revision = None
37 self.previous_revision = None 58 self.previous_revision = None
38 self.revision_string = revision_string 59 self.revision_string = revision_string
39 self.commit_hash, self.commit_pos = self._commit_from_rev_string()
40 self.build_job_name = None 60 self.build_job_name = None
41 self.test_job_name = None 61 self.test_job_name = None
42 self.built = False 62 self.built = False
63 self.patch_file = None
64 if not self.revision_string:
65 assert base_revision
66 assert base_revision.deps_file_contents
67 assert depot != 'chromium'
68 assert deps_revision
69 self.needs_patch = True
70 self.depot = depot
71 self.revision_string = (base_revision.revision_string + ',' +
72 dependency_depot_name)
73 self.revision_string += '@' + deps_revision
74 self.deps_patch, self.deps_file_contents = self.bisector.make_deps_patch(
75 base_revision, base_revision.deps_file_contents,
76 self.depot, deps_revision)
77 self.commit_hash = base_revision.commit_hash
78 self.commit_pos = base_revision.commit_pos
79 self.deps_sha = hashlib.sha1(self.deps_patch).hexdigest()
80 self.deps_sha_patch = self.bisector.make_deps_sha_file(self.deps_sha)
81 self.deps = dict(base_revision.deps)
82 self.deps[dependency_depot_name] = deps_revision
83 else:
84 self.needs_patch = False
85 self.depot = depot
86 self.commit_hash, self.commit_pos = self._commit_from_rev_string()
43 self.build_url = self.bisector.get_platform_gs_prefix() + self._gs_suffix() 87 self.build_url = self.bisector.get_platform_gs_prefix() + self._gs_suffix()
44 88
45 @property 89 @property
46 def good(self): 90 def good(self):
47 return self._good == True 91 return self._good == True
48 92
49 @property 93 @property
50 def bad(self): 94 def bad(self):
51 return self._good == False 95 return self._good == False
52 96
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
85 parallel. 129 parallel.
86 """ 130 """
87 assert self.in_progress 131 assert self.in_progress
88 self.in_progress = False 132 self.in_progress = False
89 self.aborted = True 133 self.aborted = True
90 # TODO: actually kill buildbot job if it's the test step. 134 # TODO: actually kill buildbot job if it's the test step.
91 135
92 def deps_change(self): 136 def deps_change(self):
93 """Uses `git show` to see if a given commit contains a DEPS change.""" 137 """Uses `git show` to see if a given commit contains a DEPS change."""
94 api = self.bisector.api 138 api = self.bisector.api
139 name = 'Checking DEPS for '+self.commit_hash
95 step_result = api.m.git('show', '--name-only', '--pretty=format:', 140 step_result = api.m.git('show', '--name-only', '--pretty=format:',
96 self.commit_hash, stdout=api.m.raw_io.output()) 141 self.commit_hash, stdout=api.m.raw_io.output(), name=name)
97 if self.bisector.dummy_builds: 142 if self.bisector.dummy_builds and not self.commit_hash.startswith('dcdc'):
98 return False 143 return False
99 if 'DEPS' in step_result.stdout.splitlines(): # pragma: no cover 144 if 'DEPS' in step_result.stdout.splitlines(): # pragma: no cover
100 return True 145 return True
101 return False # pragma: no cover 146 return False # pragma: no cover
102 147
148
149 def _gen_deps_local_scope(self):
150 """Defines the Var and From functions in a dict for calling exec.
151
152 This is needed for executing the DEPS file.
153 """
154 deps_data = {
155 'Var': lambda _: deps_data['vars'][_],
156 'From': lambda *args: None,
157 }
158 return deps_data
159
160 def read_deps(self):
161 """Sets the dependencies for this revision from the contents of DEPS."""
162 api = self.bisector.api
163 if self.deps:
164 return
165 step_result = api.m.git.cat_file_at_commit(depot_config.DEPS_FILENAME,
166 self.commit_hash,
167 stdout=api.m.raw_io.output())
168 self.deps_file_contents = step_result.stdout
169 try:
170 deps_data = self._gen_deps_local_scope()
171 exec(self.deps_file_contents or 'deps = {}', {}, deps_data)
172 deps_data = deps_data['deps']
173 except ImportError: # pragma: no cover
174 # TODO(robertocn): Implement manual parsing of DEPS when exec fails.
175 raise NotImplementedError('Path not implemented to manually parse DEPS')
176
177 revision_regex = re.compile('.git@(?P<revision>[a-fA-F0-9]+)')
178 results = {}
179 for depot_name, depot_data in depot_config.DEPOT_DEPS_NAME.iteritems():
180 if (depot_data.get('platform') and
181 depot_data.get('platform') != os.name):
182 # TODO(robertocn) we shouldn't be checking the os of the bot running the
183 # bisector, but the os the tester would be running on.
184 continue
185
186 if depot_data.get('recurse') and self.depot in depot_data.get('from'):
187 depot_data_src = depot_data.get('src') or depot_data.get('src_old')
188 src_dir = deps_data.get(depot_data_src)
189 if src_dir:
190 re_results = revision_regex.search(src_dir)
191 if re_results:
192 results[depot_name] = re_results.group('revision')
193 else:
194 warning_text = ('Could not parse revision for %s while bisecting '
195 '%s' % (depot_name, self.depot))
196 if not warning_text in self.bisector.warnings:
197 self.bisector.warnings.append(warning_text)
198 else:
199 results[depot_name] = None
200 self.deps = results
201 return
202
103 def update_status(self): 203 def update_status(self):
104 """Checks on the pending jobs and updates status accordingly. 204 """Checks on the pending jobs and updates status accordingly.
105 205
106 This method will check for the build to complete and then trigger the test, 206 This method will check for the build to complete and then trigger the test,
107 or will wait for the test as appropriate. 207 or will wait for the test as appropriate.
108 208
109 To wait for the test we try to get the buildbot job url from GS, and if 209 To wait for the test we try to get the buildbot job url from GS, and if
110 available, we query the status of such job. 210 available, we query the status of such job.
111 """ 211 """
112 if not self.in_progress: 212 if not self.in_progress:
(...skipping 17 matching lines...) Expand all
130 result = api.gsutil_file_exists(self.build_url) 230 result = api.gsutil_file_exists(self.build_url)
131 if self.bisector.dummy_builds: 231 if self.bisector.dummy_builds:
132 return self.in_progress 232 return self.in_progress
133 return result # pragma: no cover 233 return result # pragma: no cover
134 234
135 def _gs_suffix(self): 235 def _gs_suffix(self):
136 """Provides the expected right half of the build filename. 236 """Provides the expected right half of the build filename.
137 237
138 This takes into account whether the build has a deps patch. 238 This takes into account whether the build has a deps patch.
139 """ 239 """
140 # TODO: Implement the logic for deps patch changes. 240 name_parts = [self.commit_hash]
141 return self.commit_hash + '.zip' 241 if self.needs_patch:
242 name_parts.append(self.deps_sha)
243 return '%s.zip' % '_'.join(name_parts)
142 244
143 def _commit_from_rev_string(self): 245 def _commit_from_rev_string(self):
144 """Gets the chromium repo commit hash and position for this revision. 246 """Gets the chromium repo commit hash and position for this revision."""
145
146 If there are specified dependency revisions in the string, we don't compute
147 either the position or hash"""
148 pieces = self.revision_string.split(',') 247 pieces = self.revision_string.split(',')
149 if len(pieces) > 1:
150 return None, None # pragma: no cover
151 if (pieces[0].startswith('chromium@') or 248 if (pieces[0].startswith('chromium@') or
152 pieces[0].startswith('src@') or 249 pieces[0].startswith('src@') or
153 not '@' in pieces[0]): 250 not '@' in pieces[0]):
154 hash_or_pos = pieces[0].split('@')[-1] 251 hash_or_pos = pieces[0].split('@')[-1]
155 if self._check_if_hash(hash_or_pos): 252 if self._check_if_hash(hash_or_pos):
156 commit_pos = self._get_pos_from_hash(hash_or_pos) 253 commit_pos = self._get_pos_from_hash(hash_or_pos)
157 commit_hash = self._get_hash_from_pos(commit_pos) 254 commit_hash = self._get_hash_from_pos(commit_pos)
158 else: 255 else:
159 commit_hash = self._get_hash_from_pos(hash_or_pos) 256 commit_hash = self._get_hash_from_pos(hash_or_pos)
160 commit_pos = self._get_pos_from_hash(commit_hash) 257 commit_pos = self._get_pos_from_hash(commit_hash)
(...skipping 13 matching lines...) Expand all
174 int(s, 16) 271 int(s, 16)
175 return True 272 return True
176 273
177 def _get_pos_from_hash(self, sha): 274 def _get_pos_from_hash(self, sha):
178 api = self.bisector.api 275 api = self.bisector.api
179 return api.m.commit_position.chromium_commit_position_from_hash(sha) 276 return api.m.commit_position.chromium_commit_position_from_hash(sha)
180 277
181 def _get_hash_from_pos(self, pos): 278 def _get_hash_from_pos(self, pos):
182 api = self.bisector.api 279 api = self.bisector.api
183 return api.m.commit_position.chromium_hash_from_commit_position(pos) 280 return api.m.commit_position.chromium_hash_from_commit_position(pos)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698