You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
5.8 KiB
Python
168 lines
5.8 KiB
Python
# Copyright 2014 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import collections
|
|
import contextlib
|
|
import hashlib
|
|
|
|
from recipe_engine import recipe_api
|
|
|
|
|
|
class TryserverApi(recipe_api.RecipeApi):
|
|
def __init__(self, *args, **kwargs):
|
|
super(TryserverApi, self).__init__(*args, **kwargs)
|
|
self._failure_reasons = []
|
|
|
|
@property
|
|
def is_tryserver(self):
|
|
"""Returns true iff we have a change to check out."""
|
|
return (self.is_patch_in_git or self.is_gerrit_issue)
|
|
|
|
@property
|
|
def is_gerrit_issue(self):
|
|
"""Returns true iff the properties exist to match a Gerrit issue."""
|
|
if self.m.properties.get('patch_storage') == 'gerrit':
|
|
return True
|
|
# TODO(tandrii): remove this, once nobody is using buildbot Gerrit Poller.
|
|
return ('event.patchSet.ref' in self.m.properties and
|
|
'event.change.url' in self.m.properties and
|
|
'event.change.id' in self.m.properties)
|
|
|
|
@property
|
|
def is_patch_in_git(self):
|
|
return (self.m.properties.get('patch_storage') == 'git' and
|
|
self.m.properties.get('patch_repo_url') and
|
|
self.m.properties.get('patch_ref'))
|
|
|
|
def get_files_affected_by_patch(self, patch_root, **kwargs):
|
|
"""Returns list of paths to files affected by the patch.
|
|
|
|
Argument:
|
|
patch_root: path relative to api.path['root'], usually obtained from
|
|
api.gclient.calculate_patch_root(patch_project)
|
|
|
|
Returned paths will be relative to to patch_root.
|
|
"""
|
|
cwd = self.m.context.cwd or self.m.path['start_dir'].join(patch_root)
|
|
with self.m.context(cwd=cwd):
|
|
step_result = self.m.git(
|
|
'-c', 'core.quotePath=false', 'diff', '--cached', '--name-only',
|
|
name='git diff to analyze patch',
|
|
stdout=self.m.raw_io.output(),
|
|
step_test_data=lambda:
|
|
self.m.raw_io.test_api.stream_output('foo.cc'),
|
|
**kwargs)
|
|
paths = [self.m.path.join(patch_root, p) for p in
|
|
step_result.stdout.split()]
|
|
if self.m.platform.is_win:
|
|
# Looks like "analyze" wants POSIX slashes even on Windows (since git
|
|
# uses that format even on Windows).
|
|
paths = [path.replace('\\', '/') for path in paths]
|
|
step_result.presentation.logs['files'] = paths
|
|
return paths
|
|
|
|
def set_subproject_tag(self, subproject_tag):
|
|
"""Adds a subproject tag to the build.
|
|
|
|
This can be used to distinguish between builds that execute different steps
|
|
depending on what was patched, e.g. blink vs. pure chromium patches.
|
|
"""
|
|
assert self.is_tryserver
|
|
|
|
step_result = self.m.step.active_result
|
|
step_result.presentation.properties['subproject_tag'] = subproject_tag
|
|
|
|
def _set_failure_type(self, failure_type):
|
|
if not self.is_tryserver:
|
|
return
|
|
|
|
step_result = self.m.step.active_result
|
|
step_result.presentation.properties['failure_type'] = failure_type
|
|
|
|
def set_patch_failure_tryjob_result(self):
|
|
"""Mark the tryjob result as failure to apply the patch."""
|
|
self._set_failure_type('PATCH_FAILURE')
|
|
|
|
def set_compile_failure_tryjob_result(self):
|
|
"""Mark the tryjob result as a compile failure."""
|
|
self._set_failure_type('COMPILE_FAILURE')
|
|
|
|
def set_test_failure_tryjob_result(self):
|
|
"""Mark the tryjob result as a test failure.
|
|
|
|
This means we started running actual tests (not prerequisite steps
|
|
like checkout or compile), and some of these tests have failed.
|
|
"""
|
|
self._set_failure_type('TEST_FAILURE')
|
|
|
|
def set_invalid_test_results_tryjob_result(self):
|
|
"""Mark the tryjob result as having invalid test results.
|
|
|
|
This means we run some tests, but the results were not valid
|
|
(e.g. no list of specific test cases that failed, or too many
|
|
tests failing, etc).
|
|
"""
|
|
self._set_failure_type('INVALID_TEST_RESULTS')
|
|
|
|
def add_failure_reason(self, reason):
|
|
"""
|
|
Records a more detailed reason why build is failing.
|
|
|
|
The reason can be any JSON-serializable object.
|
|
"""
|
|
assert self.m.json.is_serializable(reason)
|
|
self._failure_reasons.append(reason)
|
|
|
|
@contextlib.contextmanager
|
|
def set_failure_hash(self):
|
|
"""
|
|
Context manager that sets a failure_hash build property on StepFailure.
|
|
|
|
This can be used to easily compare whether two builds have failed
|
|
for the same reason. For example, if a patch is bad (breaks something),
|
|
we'd expect it to always break in the same way. Different failures
|
|
for the same patch are usually a sign of flakiness.
|
|
"""
|
|
try:
|
|
yield
|
|
except self.m.step.StepFailure as e:
|
|
self.add_failure_reason(e.reason)
|
|
|
|
try:
|
|
step_result = self.m.step.active_result
|
|
except ValueError:
|
|
step_result = None
|
|
if step_result:
|
|
failure_hash = hashlib.sha1()
|
|
failure_hash.update(self.m.json.dumps(self._failure_reasons))
|
|
step_result.presentation.properties['failure_hash'] = (
|
|
failure_hash.hexdigest())
|
|
|
|
raise e
|
|
|
|
def get_footers(self, patch_text=None):
|
|
"""Retrieves footers from the patch description.
|
|
|
|
footers are machine readable tags embedded in commit messages. See
|
|
git-footers documentation for more information.
|
|
"""
|
|
if patch_text is None:
|
|
patch_text = self.m.gerrit.get_change_description(
|
|
self.m.properties['patch_gerrit_url'],
|
|
self.m.properties['patch_issue'],
|
|
self.m.properties['patch_set'])
|
|
|
|
result = self.m.python(
|
|
'parse description', self.package_repo_resource('git_footers.py'),
|
|
args=['--json', self.m.json.output()],
|
|
stdin=self.m.raw_io.input(data=patch_text))
|
|
return result.json.output
|
|
|
|
def get_footer(self, tag, patch_text=None):
|
|
"""Gets a specific tag from a CL description"""
|
|
return self.get_footers(patch_text).get(tag, [])
|
|
|
|
def normalize_footer_name(self, footer):
|
|
return '-'.join([ word.title() for word in footer.strip().split('-') ])
|