blob: 83f5b8f8a27803cf1714da1eef9d7d450f8308bd [file] [log] [blame]
# Copyright 2016 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.
"""An interface to git-cl.
The git-cl tool is responsible for communicating with Rietveld, Gerrit,
and Buildbucket to manage changelists and try jobs associated with them.
"""
import json
import logging
import re
from webkitpy.common.net.buildbot import Build, filter_latest_builds
_log = logging.getLogger(__name__)
# A refresh token may be needed for some commands, such as git cl try,
# in order to authenticate with buildbucket.
_COMMANDS_THAT_TAKE_REFRESH_TOKEN = ('try',)
class GitCL(object):
def __init__(self, host, auth_refresh_token_json=None, cwd=None):
self._host = host
self._auth_refresh_token_json = auth_refresh_token_json
self._cwd = cwd
def run(self, args):
"""Runs git-cl with the given arguments and returns the output."""
command = ['git', 'cl'] + args
if self._auth_refresh_token_json and args[0] in _COMMANDS_THAT_TAKE_REFRESH_TOKEN:
command += ['--auth-refresh-token-json', self._auth_refresh_token_json]
return self._host.executive.run_command(command, cwd=self._cwd)
def trigger_try_jobs(self, builders=None):
builders = builders or self._host.builders.all_try_builder_names()
if 'android_blink_rel' in builders:
self.run(['try', '-b', 'android_blink_rel'])
builders.remove('android_blink_rel')
# TODO(qyearsley): Stop explicitly adding the master name when
# git cl try can get the master name; see http://crbug.com/700523.
command = ['try', '-m', 'tryserver.blink']
for builder in sorted(builders):
command.extend(['-b', builder])
self.run(command)
def get_issue_number(self):
return self.run(['issue']).split()[2]
def wait_for_try_jobs(self, poll_delay_seconds=10 * 60, timeout_seconds=120 * 60):
"""Waits until all try jobs are finished.
Args:
poll_delay_seconds: Time to wait between fetching results.
timeout_seconds: Time to wait before aborting.
Returns:
A list of try job result dicts, or None if a timeout occurred.
"""
start = self._host.time()
self._host.print_('Waiting for try jobs (timeout: %d seconds).' % timeout_seconds)
while self._host.time() - start < timeout_seconds:
self._host.sleep(poll_delay_seconds)
try_results = self.fetch_try_results()
_log.debug('Fetched try results: %s', try_results)
if self.all_jobs_finished(try_results):
self._host.print_('All jobs finished.')
return try_results
self._host.print_('Waiting. %d seconds passed.' % (self._host.time() - start))
self._host.sleep(poll_delay_seconds)
self._host.print_('Timed out waiting for try results.')
return None
def latest_try_jobs(self, builder_names=None):
"""Returns a list of Builds for the latest jobs for the given builders."""
try_results = self.fetch_try_results()
if builder_names:
try_results = [r for r in try_results if r['builder_name'] in builder_names]
return filter_latest_builds(self._try_result_to_build(r) for r in try_results)
def fetch_try_results(self):
"""Requests results f try jobs for the current CL."""
with self._host.filesystem.mkdtemp() as temp_directory:
results_path = self._host.filesystem.join(temp_directory, 'try-results.json')
self.run(['try-results', '--json', results_path])
contents = self._host.filesystem.read_text_file(results_path)
_log.debug('Fetched try results to file "%s".', results_path)
self._host.filesystem.remove(results_path)
return json.loads(contents)
@staticmethod
def _try_result_to_build(try_result):
"""Converts a parsed try result dict to a Build object."""
builder_name = try_result['builder_name']
url = try_result['url']
if url is None:
return Build(builder_name, None)
match = re.match(r'.*/builds/(\d+)?$', url)
build_number = match.group(1)
assert build_number and build_number.isdigit()
return Build(builder_name, int(build_number))
@staticmethod
def all_jobs_finished(try_results):
return all(r.get('status') == 'COMPLETED' for r in try_results)
@staticmethod
def has_failing_try_results(try_results):
return any(r.get('result') == 'FAILURE' for r in try_results)