| 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 | 12 |
| 13 import hashlib | 13 import hashlib |
| 14 import json | 14 import json |
| 15 import math | 15 import math |
| 16 import os | 16 import os |
| 17 import tempfile | 17 import tempfile |
| 18 import re | 18 import re |
| 19 import uuid | 19 import uuid |
| 20 | 20 |
| 21 from . import depot_config | 21 from . import depot_config |
| 22 | 22 |
| 23 # These relate to how to increase the number of repetitions during re-test | 23 # These relate to how to increase the number of repetitions during re-test |
| 24 MINIMUM_SAMPLE_SIZE = 5 | 24 MINIMUM_SAMPLE_SIZE = 5 |
| 25 INCREASE_FACTOR = 1.5 | 25 INCREASE_FACTOR = 1.5 |
| 26 # Buildbot job result codes. | |
| 27 # See http://docs.buildbot.net/current/developer/results.html | |
| 28 SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION = range(5) | |
| 29 | 26 |
| 30 class RevisionState(object): | 27 class RevisionState(object): |
| 31 """Abstracts the state of a single revision on a bisect job.""" | 28 """Abstracts the state of a single revision on a bisect job.""" |
| 32 | 29 |
| 33 # Possible values for the status attribute of RevisionState: | 30 # Possible values for the status attribute of RevisionState: |
| 34 ( | 31 ( |
| 35 NEW, # A revision_state object that has just been initialized. | 32 NEW, # A revision_state object that has just been initialized. |
| 36 BUILDING, # Requested a build for this revision, waiting for it. | 33 BUILDING, # Requested a build for this revision, waiting for it. |
| 37 TESTING, # A test job for this revision was triggered, waiting for it. | 34 TESTING, # A test job for this revision was triggered, waiting for it. |
| 38 TESTED, # The test job completed with non-failing results. | 35 TESTED, # The test job completed with non-failing results. |
| (...skipping 30 matching lines...) Expand all Loading... |
| 69 self.previous_revision = None | 66 self.previous_revision = None |
| 70 self.job_name = None | 67 self.job_name = None |
| 71 self.patch_file = None | 68 self.patch_file = None |
| 72 self.deps_revision = None | 69 self.deps_revision = None |
| 73 self.depot_name = depot_name or self.bisector.base_depot | 70 self.depot_name = depot_name or self.bisector.base_depot |
| 74 self.depot = depot_config.DEPOT_DEPS_NAME[self.depot_name] | 71 self.depot = depot_config.DEPOT_DEPS_NAME[self.depot_name] |
| 75 self.commit_hash = str(commit_hash) | 72 self.commit_hash = str(commit_hash) |
| 76 self._rev_str = None | 73 self._rev_str = None |
| 77 self.base_revision = base_revision | 74 self.base_revision = base_revision |
| 78 self.revision_overrides = {} | 75 self.revision_overrides = {} |
| 76 self.build_id = None |
| 79 if self.base_revision: | 77 if self.base_revision: |
| 80 assert self.base_revision.deps_file_contents | 78 assert self.base_revision.deps_file_contents |
| 81 self.needs_patch = True | 79 self.needs_patch = True |
| 82 self.revision_overrides[self.depot['src']] = self.commit_hash | 80 self.revision_overrides[self.depot['src']] = self.commit_hash |
| 83 self.deps_patch, self.deps_file_contents = self.bisector.make_deps_patch( | 81 self.deps_patch, self.deps_file_contents = self.bisector.make_deps_patch( |
| 84 self.base_revision, self.base_revision.deps_file_contents, | 82 self.base_revision, self.base_revision.deps_file_contents, |
| 85 self.depot, self.commit_hash) | 83 self.depot, self.commit_hash) |
| 86 self.deps_sha = hashlib.sha1(self.deps_patch).hexdigest() | 84 self.deps_sha = hashlib.sha1(self.deps_patch).hexdigest() |
| 87 self.deps_sha_patch = self.bisector.make_deps_sha_file(self.deps_sha) | 85 self.deps_sha_patch = self.bisector.make_deps_sha_file(self.deps_sha) |
| 88 self.deps = dict(base_revision.deps) | 86 self.deps = dict(base_revision.deps) |
| 89 self.deps[self.depot_name] = self.commit_hash | 87 self.deps[self.depot_name] = self.commit_hash |
| 90 else: | 88 else: |
| 91 self.needs_patch = False | 89 self.needs_patch = False |
| 92 self.build_url = self.bisector.get_platform_gs_prefix() + self._gs_suffix() | 90 self.build_url = self.bisector.get_platform_gs_prefix() + self._gs_suffix() |
| 93 self.values = [] | 91 self.values = [] |
| 94 self.mean_value = None | 92 self.mean_value = None |
| 95 self.overall_return_code = None | 93 self.overall_return_code = None |
| 96 self.std_dev = None | 94 self.std_dev = None |
| 97 self._test_config = None | 95 self._test_config = None |
| 98 self.build_number = None | |
| 99 | 96 |
| 100 if self.bisector.test_type == 'perf': | 97 if self.bisector.test_type == 'perf': |
| 101 self.repeat_count = MINIMUM_SAMPLE_SIZE | 98 self.repeat_count = MINIMUM_SAMPLE_SIZE |
| 102 else: | 99 else: |
| 103 self.repeat_count = self.bisector.bisect_config.get( | 100 self.repeat_count = self.bisector.bisect_config.get( |
| 104 'repeat_count', MINIMUM_SAMPLE_SIZE) | 101 'repeat_count', MINIMUM_SAMPLE_SIZE) |
| 105 | 102 |
| 106 @property | 103 @property |
| 107 def tested(self): | 104 def tested(self): |
| 108 return self.status in (RevisionState.TESTED,) | 105 return self.status in (RevisionState.TESTED,) |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 276 """Checks if the revision is already built and archived.""" | 273 """Checks if the revision is already built and archived.""" |
| 277 if not self.build_archived: | 274 if not self.build_archived: |
| 278 api = self.bisector.api | 275 api = self.bisector.api |
| 279 self.build_archived = api.gsutil_file_exists(self.build_url) | 276 self.build_archived = api.gsutil_file_exists(self.build_url) |
| 280 | 277 |
| 281 if self.bisector.dummy_builds: | 278 if self.bisector.dummy_builds: |
| 282 self.build_archived = self.in_progress | 279 self.build_archived = self.in_progress |
| 283 | 280 |
| 284 return self.build_archived | 281 return self.build_archived |
| 285 | 282 |
| 286 def _fetch_build_info(self, base_url, build_number): | |
| 287 api = self.bisector.api | |
| 288 build_url = '%s/builds/%s?as_text=1' % (base_url, build_number) | |
| 289 fetch_result = api.m.url.fetch( build_url, step_name='fetch build details') | |
| 290 return json.loads(fetch_result or '{}') | |
| 291 | |
| 292 def _is_build_failed(self): | 283 def _is_build_failed(self): |
| 293 api = self.bisector.api | 284 result = self.bisector.api.m.buildbucket.get_build( |
| 294 current_build = None | 285 self.build_id, |
| 295 path = 'json/builders/' + self.bisector.get_builder_bot_for_this_platform() | 286 step_test_data=lambda: self.bisector.api.m.json.test_api.output_stream( |
| 296 base_url = api.m.properties.get('buildbotURL', 'http://localhost:8041/') | 287 {'results': [ |
| 297 base_url += path | 288 {'build': {'result': 'SUCCESS', 'status': 'COMPLETED'}}]} |
| 298 if self.build_number is None: | 289 )) |
| 299 try: | 290 return (result.stdout['results'][0]['build']['status'] == 'COMPLETED' and |
| 300 # Get all the current builds. | 291 result.stdout['results'][0]['build'].get('result') != 'SUCCESS') |
| 301 builder_state_url = base_url + '?as_text=1' | |
| 302 builder_state = api.m.url.fetch( | |
| 303 builder_state_url, step_name='fetch builder state') | |
| 304 builder_state = json.loads(builder_state or '{}') | |
| 305 for build_number in builder_state.get('cachedBuilds', []): | |
| 306 build = self._fetch_build_info(base_url, build_number) | |
| 307 # Properties is a list of triples (key, value, source) | |
| 308 build_properties = dict([t[:2] for t in build.get('properties', [])]) | |
| 309 if build_properties.get('build_archive_url') == self.build_url: | |
| 310 self.build_number = build_number | |
| 311 current_build = build | |
| 312 break | |
| 313 except (api.m.step.StepFailure, ValueError): # pragma: no cover | |
| 314 # If we cannot get json from buildbot, we cannot determine if a build is | |
| 315 # failed, hence we consider it in progress until it times out. | |
| 316 return False | |
| 317 if self.build_number is None: | |
| 318 # The build hasn't started yet, therefore it's not failed. | |
| 319 return False | |
| 320 if not current_build: | |
| 321 current_build = self._fetch_build_info(base_url, self.build_number) | |
| 322 return current_build.get('results') in [FAILURE, SKIPPED, EXCEPTION] | |
| 323 | 292 |
| 324 def _results_available(self): | 293 def _results_available(self): |
| 325 """Checks if the results for the test job have been uploaded.""" | 294 """Checks if the results for the test job have been uploaded.""" |
| 326 api = self.bisector.api | 295 api = self.bisector.api |
| 327 result = api.gsutil_file_exists(self.test_results_url) | 296 result = api.gsutil_file_exists(self.test_results_url) |
| 328 if self.bisector.dummy_builds: | 297 if self.bisector.dummy_builds: |
| 329 return self.in_progress | 298 return self.in_progress |
| 330 return result # pragma: no cover | 299 return result # pragma: no cover |
| 331 | 300 |
| 332 def _gs_suffix(self): | 301 def _gs_suffix(self): |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 420 'build_archive_url': self.build_url, | 389 'build_archive_url': self.build_url, |
| 421 }, | 390 }, |
| 422 }, | 391 }, |
| 423 'client_operation_id': operation_id | 392 'client_operation_id': operation_id |
| 424 } | 393 } |
| 425 if self.revision_overrides: | 394 if self.revision_overrides: |
| 426 build_details['parameters']['properties']['deps_revision_overrides'] = \ | 395 build_details['parameters']['properties']['deps_revision_overrides'] = \ |
| 427 self.revision_overrides | 396 self.revision_overrides |
| 428 | 397 |
| 429 try: | 398 try: |
| 430 api.m.buildbucket.put([build_details], | 399 result = api.m.buildbucket.put( |
| 431 api.m.service_account.get_json_path( | 400 [build_details], |
| 432 api.SERVICE_ACCOUNT)) | 401 api.m.service_account.get_json_path(api.SERVICE_ACCOUNT), |
| 402 step_test_data=lambda: api.m.json.test_api.output_stream( |
| 403 {'results':[{'build':{'id':'1201331270'}}]})) |
| 433 except api.m.step.StepFailure: # pragma: no cover | 404 except api.m.step.StepFailure: # pragma: no cover |
| 434 self.bisector.surface_result('BUILD_FAILURE') | 405 self.bisector.surface_result('BUILD_FAILURE') |
| 435 raise | 406 raise |
| 407 self.build_id = result.stdout['results'][0]['build']['id'] |
| 436 | 408 |
| 437 def _get_bisect_config_for_tester(self): | 409 def _get_bisect_config_for_tester(self): |
| 438 """Copies the key-value pairs required by a tester bot to a new dict.""" | 410 """Copies the key-value pairs required by a tester bot to a new dict.""" |
| 439 result = {} | 411 result = {} |
| 440 required_test_properties = { | 412 required_test_properties = { |
| 441 'truncate_percent', | 413 'truncate_percent', |
| 442 'metric', | 414 'metric', |
| 443 'command', | 415 'command', |
| 444 'test_type' | 416 'test_type' |
| 445 } | 417 } |
| (...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 599 else: | 571 else: |
| 600 next_revision_to_test.retest() | 572 next_revision_to_test.retest() |
| 601 | 573 |
| 602 def __repr__(self): | 574 def __repr__(self): |
| 603 if self.overall_return_code is not None: | 575 if self.overall_return_code is not None: |
| 604 return ('RevisionState(rev=%s, values=%r, overall_return_code=%r, ' | 576 return ('RevisionState(rev=%s, values=%r, overall_return_code=%r, ' |
| 605 'std_dev=%r)') % (self.revision_string(), self.values, | 577 'std_dev=%r)') % (self.revision_string(), self.values, |
| 606 self.overall_return_code, self.std_dev) | 578 self.overall_return_code, self.std_dev) |
| 607 return ('RevisionState(rev=%s, values=%r, mean_value=%r, std_dev=%r)' % ( | 579 return ('RevisionState(rev=%s, values=%r, mean_value=%r, std_dev=%r)' % ( |
| 608 self.revision_string(), self.values, self.mean_value, self.std_dev)) | 580 self.revision_string(), self.values, self.mean_value, self.std_dev)) |
| OLD | NEW |