| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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) |
| OLD | NEW |