| Index: git_cl.py
|
| diff --git a/git_cl.py b/git_cl.py
|
| index 5831b121ee638d0fe779203991b1692bd60b8db3..1bf4601f7666f95f7de2c8171e34dc4172f6b1ba 100755
|
| --- a/git_cl.py
|
| +++ b/git_cl.py
|
| @@ -398,7 +398,7 @@ def trigger_try_jobs(auth_config, changelist, options, masters, category):
|
| def fetch_try_jobs(auth_config, changelist, options):
|
| """Fetches try jobs from buildbucket.
|
|
|
| - Returns a map from build id to build info as json dictionary.
|
| + Returns a map from build id to build info as a dictionary.
|
| """
|
| rietveld_url = settings.GetDefaultServerUrl()
|
| rietveld_host = urlparse.urlparse(rietveld_url).hostname
|
| @@ -434,17 +434,24 @@ def fetch_try_jobs(auth_config, changelist, options):
|
| return builds
|
|
|
|
|
| -def print_try_jobs(options, builds):
|
| - """Prints nicely result of fetch_try_jobs."""
|
| - if not builds:
|
| - print('No try jobs scheduled')
|
| - return
|
| +def try_job_buckets(builds):
|
| + """Groups a set of try jobs into buckets for output.
|
| +
|
| + Args:
|
| + builds: A list of dicts, one for each try job. This is the output
|
| + of fetch_try_jobs.
|
|
|
| + Returns:
|
| + A dict mapping categories of try jobs (e.g. successes, failures,
|
| + infra failures, etc.) to lists of dicts, one for each try job.
|
| + The information here is a subset of the information in the original
|
| + input build dicts.
|
| + """
|
| # Make a copy, because we'll be modifying builds dictionary.
|
| builds = builds.copy()
|
| builder_names_cache = {}
|
|
|
| - def get_builder(b):
|
| + def get_builder_name(b):
|
| try:
|
| return builder_names_cache[b['id']]
|
| except KeyError:
|
| @@ -452,85 +459,128 @@ def print_try_jobs(options, builds):
|
| parameters = json.loads(b['parameters_json'])
|
| name = parameters['builder_name']
|
| except (ValueError, KeyError) as error:
|
| - print('WARNING: failed to get builder name for build %s: %s' % (
|
| - b['id'], error))
|
| + print('WARNING: failed to get builder name for build %s: %s' %
|
| + (b['id'], error))
|
| name = None
|
| builder_names_cache[b['id']] = name
|
| return name
|
|
|
| - def get_bucket(b):
|
| + def get_master_name(b):
|
| bucket = b['bucket']
|
| if bucket.startswith('master.'):
|
| return bucket[len('master.'):]
|
| return bucket
|
|
|
| - if options.print_master:
|
| - name_fmt = '%%-%ds %%-%ds' % (
|
| - max(len(str(get_bucket(b))) for b in builds.itervalues()),
|
| - max(len(str(get_builder(b))) for b in builds.itervalues()))
|
| - def get_name(b):
|
| - return name_fmt % (get_bucket(b), get_builder(b))
|
| - else:
|
| - name_fmt = '%%-%ds' % (
|
| - max(len(str(get_builder(b))) for b in builds.itervalues()))
|
| - def get_name(b):
|
| - return name_fmt % get_builder(b)
|
| -
|
| def sort_key(b):
|
| - return b['status'], b.get('result'), get_name(b), b.get('url')
|
| + return b['status'], b.get('result'), get_builder_name(b), b.get('url')
|
|
|
| - def pop(title, f, color=None, **kwargs):
|
| - """Pop matching builds from `builds` dict and print them."""
|
| -
|
| - if not options.color or color is None:
|
| - colorize = str
|
| - else:
|
| - colorize = lambda x: '%s%s%s' % (color, x, Fore.RESET)
|
| + buckets = collections.OrderedDict()
|
|
|
| + def pop(title, **kwargs):
|
| + """Pops matching builds from `builds` and adds it to `buckets`."""
|
| result = []
|
| for b in builds.values():
|
| if all(b.get(k) == v for k, v in kwargs.iteritems()):
|
| builds.pop(b['id'])
|
| result.append(b)
|
| if result:
|
| - print(colorize(title))
|
| + buckets[title] = []
|
| for b in sorted(result, key=sort_key):
|
| - print(' ', colorize('\t'.join(map(str, f(b)))))
|
| -
|
| - total = len(builds)
|
| - pop(status='COMPLETED', result='SUCCESS',
|
| - title='Successes:', color=Fore.GREEN,
|
| - f=lambda b: (get_name(b), b.get('url')))
|
| - pop(status='COMPLETED', result='FAILURE', failure_reason='INFRA_FAILURE',
|
| - title='Infra Failures:', color=Fore.MAGENTA,
|
| - f=lambda b: (get_name(b), b.get('url')))
|
| - pop(status='COMPLETED', result='FAILURE', failure_reason='BUILD_FAILURE',
|
| - title='Failures:', color=Fore.RED,
|
| - f=lambda b: (get_name(b), b.get('url')))
|
| - pop(status='COMPLETED', result='CANCELED',
|
| - title='Canceled:', color=Fore.MAGENTA,
|
| - f=lambda b: (get_name(b),))
|
| - pop(status='COMPLETED', result='FAILURE',
|
| - failure_reason='INVALID_BUILD_DEFINITION',
|
| - title='Wrong master/builder name:', color=Fore.MAGENTA,
|
| - f=lambda b: (get_name(b),))
|
| - pop(status='COMPLETED', result='FAILURE',
|
| - title='Other failures:',
|
| - f=lambda b: (get_name(b), b.get('failure_reason'), b.get('url')))
|
| - pop(status='COMPLETED',
|
| - title='Other finished:',
|
| - f=lambda b: (get_name(b), b.get('result'), b.get('url')))
|
| - pop(status='STARTED',
|
| - title='Started:', color=Fore.YELLOW,
|
| - f=lambda b: (get_name(b), b.get('url')))
|
| - pop(status='SCHEDULED',
|
| - title='Scheduled:',
|
| - f=lambda b: (get_name(b), 'id=%s' % b['id']))
|
| + buckets[title].append({
|
| + 'builder_name': get_builder_name(b),
|
| + 'master_name': get_master_name(b),
|
| + 'url': b.get('url'),
|
| + 'buildbucket_id': b.get('id'),
|
| + 'status': b.get('status'),
|
| + 'result': b.get('result'),
|
| + 'failure_reason': b.get('failure_reason'),
|
| + })
|
| +
|
| + pop('Successes', status='COMPLETED', result='SUCCESS')
|
| + pop('Infra Failures', status='COMPLETED', result='FAILURE',
|
| + failure_reason='INFRA_FAILURE')
|
| + pop('Failures', status='COMPLETED', result='FAILURE',
|
| + failure_reason='BUILD_FAILURE')
|
| + pop('Canceled', status='COMPLETED', result='CANCELED')
|
| + pop('Wrong master/builder name', status='COMPLETED', result='FAILURE',
|
| + failure_reason='INVALID_BUILD_DEFINITION')
|
| + pop('Other failures', status='COMPLETED', result='FAILURE')
|
| + pop('Other finished', status='COMPLETED')
|
| + pop('Started', status='STARTED')
|
| + pop('Scheduled', status='SCHEDULED')
|
| # The last section is just in case buildbucket API changes OR there is a bug.
|
| - pop(title='Other:',
|
| - f=lambda b: (get_name(b), 'id=%s' % b['id']))
|
| + pop('Other')
|
| assert len(builds) == 0
|
| - print('Total: %d try jobs' % total)
|
| + return buckets
|
| +
|
| +
|
| +def print_try_jobs(options, builds):
|
| + """Nicely prints the result of fetch_try_jobs."""
|
| + if not builds:
|
| + print('No try jobs scheduled')
|
| + return
|
| +
|
| + buckets = try_job_buckets(builds)
|
| +
|
| + def color_for_bucket(title):
|
| + return {
|
| + 'Successes': Fore.GREEN,
|
| + 'Infra Failures': Fore.MAGENTA,
|
| + 'Failures': Fore.RED,
|
| + 'Canceled': Fore.MAGENTA,
|
| + 'Wrong master/builder name': Fore.MAGENTA,
|
| + 'Started:': Fore.YELLOW,
|
| + }.get(title)
|
| +
|
| + def extra_columns_for_bucket(title):
|
| + return {
|
| + 'Successes': ['url'],
|
| + 'Infra Failures': ['url'],
|
| + 'Failures': ['url'],
|
| + 'Canceled': [],
|
| + 'Wrong master/builder name': ['builder_name'],
|
| + 'Other failures': ['failure_reason', 'url'],
|
| + 'Other finished': ['result', 'url'],
|
| + 'Started': ['url'],
|
| + 'Scheduled': ['buildbucket_id'],
|
| + 'Other': ['buildbucket_id'],
|
| + }.get(title)
|
| +
|
| + for title, builds in buckets.iteritems():
|
| + if not builds:
|
| + continue
|
| + color = color_for_bucket(title)
|
| + if not options.color or color is None:
|
| + colorize = str
|
| + else:
|
| + colorize = lambda x: '%s%s%s' % (color, x, Fore.RESET)
|
| + print(colorize(title + ':'))
|
| + for build in builds:
|
| + row = [build['builder_name']]
|
| + # TODO: Make sure the spacing is right when master name is printed.
|
| + if options.print_master:
|
| + row.append([build['master_name']])
|
| + for column in extra_columns_for_bucket(title):
|
| + if column == 'id':
|
| + row.append('id=%s' % build['id'])
|
| + else:
|
| + row.append(build[column])
|
| + print(' ', colorize('\t'.join(row)))
|
| + print('Total: %d try jobs' % sum(len(b) for b in buckets))
|
| +
|
| +
|
| +def write_try_results_json(output_file, builds):
|
| + """This outputs results from fetch_try_jobs as JSON.
|
| +
|
| + Args:
|
| + builds: Results fetched from fetch_try_jobs. This is a list of dicts.
|
| +
|
| + Returns:
|
| + A dict mapping "status buckets" to lists of builds.
|
| + """
|
| + write_json(output_file + '.full', builds)
|
| + buckets = try_job_buckets(builds)
|
| + write_json(output_file, buckets)
|
|
|
|
|
| def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
|
| @@ -4757,6 +4807,8 @@ def CMDtry_results(parser, args):
|
| group.add_option(
|
| "--buildbucket-host", default='cr-buildbucket.appspot.com',
|
| help="Host of buildbucket. The default host is %default.")
|
| + group.add_option(
|
| + '--json', help='Path of JSON output file to write try job results to.')
|
| parser.add_option_group(group)
|
| auth.add_auth_options(parser)
|
| options, args = parser.parse_args(args)
|
| @@ -4785,7 +4837,10 @@ def CMDtry_results(parser, args):
|
| print('ERROR: Exception when trying to fetch try jobs: %s\n%s' %
|
| (e, stacktrace))
|
| return 1
|
| - print_try_jobs(options, jobs)
|
| + if options.json:
|
| + write_try_results_json(options.json, jobs)
|
| + else:
|
| + print_try_jobs(options, jobs)
|
| return 0
|
|
|
|
|
|
|