OLD | NEW |
1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 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 """Utility functions to communicate with Rietveld.""" | 5 """Utility functions to communicate with Rietveld.""" |
6 | 6 |
7 import json | 7 import json |
8 import logging | 8 import logging |
9 import urllib2 | 9 import urllib2 |
10 | 10 |
11 from webkitpy.common.net.buildbot import Build | 11 from webkitpy.common.net.buildbot import Build |
12 | 12 |
13 _log = logging.getLogger(__name__) | 13 _log = logging.getLogger(__name__) |
14 | 14 |
15 BASE_CODEREVIEW_URL = 'https://codereview.chromium.org/api' | 15 BASE_CODEREVIEW_URL = 'https://codereview.chromium.org/api' |
16 | 16 |
17 | 17 |
18 class Rietveld(object): | 18 class Rietveld(object): |
19 | 19 |
20 def __init__(self, web): | 20 def __init__(self, web): |
21 self.web = web | 21 self.web = web |
22 | 22 |
23 def latest_try_job_results(self, issue_number, builder_names=None, patchset_
number=None): | 23 def latest_try_jobs(self, issue_number, builder_names=None, patchset_number=
None): |
24 """Returns a list of Build objects for builds on the latest patchset. | 24 """Returns a list of Build objects for builds on the latest patchset. |
25 | 25 |
26 Args: | 26 Args: |
27 issue_number: A Rietveld issue number. | 27 issue_number: A Rietveld issue number. |
28 builder_names: A collection of builder names. If specified, only res
ults | 28 builder_names: A collection of builder names. If specified, only res
ults |
29 from the given list of builders will be kept. | 29 from the given list of builders will be kept. |
30 patchset_number: If given, a specific patchset will be used instead
of the latest one. | 30 patchset_number: If given, a specific patchset will be used instead
of the latest one. |
31 | 31 |
32 Returns: | 32 Returns: |
33 A dict mapping Build objects to result dicts for the latest build | 33 A list of Build objects, where Build objects for completed jobs have
a build number, |
34 for each builder on the latest patchset. | 34 and Build objects for pending jobs have no build number. |
35 """ | 35 """ |
36 try: | 36 try: |
37 if patchset_number: | 37 if patchset_number: |
38 url = self._patchset_url(issue_number, patchset_number) | 38 url = self._patchset_url(issue_number, patchset_number) |
39 else: | 39 else: |
40 url = self._latest_patchset_url(issue_number) | 40 url = self._latest_patchset_url(issue_number) |
41 patchset_data = self._get_json(url) | 41 patchset_data = self._get_json(url) |
42 except (urllib2.URLError, ValueError): | 42 except (urllib2.URLError, ValueError): |
43 return {} | 43 return [] |
44 | 44 |
45 def build(job): | 45 builds = [] |
46 return Build(builder_name=job['builder'], build_number=job['buildnum
ber']) | 46 for result_dict in patchset_data['try_job_results']: |
47 | 47 build = Build(result_dict['builder'], result_dict['buildnumber']) |
48 results = {build(job): job for job in patchset_data['try_job_results']} | 48 # Normally, a value of -1 or 6 in the "result" field indicates the j
ob is |
| 49 # started or pending, and the "buildnumber" field is null. |
| 50 if build.build_number and result_dict['result'] in (-1, 6): |
| 51 _log.warning('Build %s has result %d, but unexpectedly has a bui
ld number.', build, result_dict['result']) |
| 52 build.build_number = None |
| 53 builds.append(build) |
49 | 54 |
50 if builder_names is not None: | 55 if builder_names is not None: |
51 results = {b: result for b, result in results.iteritems() if b.build
er_name in builder_names} | 56 builds = [b for b in builds if b.builder_name in builder_names] |
52 | 57 |
53 latest_builds = self._filter_latest_builds(list(results)) | 58 return self._filter_latest_builds(builds) |
54 return {b: result for b, result in results.iteritems() if b in latest_bu
ilds} | |
55 | 59 |
56 def _filter_latest_builds(self, builds): | 60 def _filter_latest_builds(self, builds): |
57 """Filters out a collection of Build objects to include only the latest
for each builder. | 61 """Filters out a collection of Build objects to include only the latest
for each builder. |
58 | 62 |
59 Args: | 63 Args: |
60 jobs: A list of Build objects. | 64 jobs: A list of Build objects. |
61 | 65 |
62 Returns: | 66 Returns: |
63 A list of Build objects that contains only the latest build for each
builder. | 67 A list of Build objects; only one Build object per builder name. If
there are only |
| 68 Builds with no build number, then one is kept; if there are Builds w
ith build numbers, |
| 69 then the one with the highest build number is kept. |
64 """ | 70 """ |
65 builder_to_highest_number = {} | 71 builder_to_latest_build = {} |
66 for build in builds: | 72 for build in builds: |
67 if build.build_number > builder_to_highest_number.get(build.builder_
name, 0): | 73 if build.builder_name not in builder_to_latest_build: |
68 builder_to_highest_number[build.builder_name] = build.build_numb
er | 74 builder_to_latest_build[build.builder_name] = build |
69 | 75 elif build.build_number > builder_to_latest_build[build.builder_name
].build_number: |
70 def is_latest_build(build): | 76 builder_to_latest_build[build.builder_name] = build |
71 if build.builder_name not in builder_to_highest_number: | 77 return sorted(builder_to_latest_build.values()) |
72 return False | |
73 return builder_to_highest_number[build.builder_name] == build.build_
number | |
74 | |
75 return [b for b in builds if is_latest_build(b)] | |
76 | 78 |
77 def changed_files(self, issue_number): | 79 def changed_files(self, issue_number): |
78 """Lists the files included in a CL, or None if this can't be determined
. | 80 """Lists the files included in a CL, or None if this can't be determined
. |
79 | 81 |
80 File paths are sorted and relative to the repository root. | 82 File paths are sorted and relative to the repository root. |
81 """ | 83 """ |
82 try: | 84 try: |
83 url = self._latest_patchset_url(issue_number) | 85 url = self._latest_patchset_url(issue_number) |
84 issue_data = self._get_json(url) | 86 issue_data = self._get_json(url) |
85 return sorted(issue_data['files']) | 87 return sorted(issue_data['files']) |
(...skipping 22 matching lines...) Expand all Loading... |
108 return json.loads(contents) | 110 return json.loads(contents) |
109 except ValueError: | 111 except ValueError: |
110 _log.error('Invalid JSON: %s', contents) | 112 _log.error('Invalid JSON: %s', contents) |
111 raise | 113 raise |
112 | 114 |
113 def _issue_url(self, issue_number): | 115 def _issue_url(self, issue_number): |
114 return '%s/%s' % (BASE_CODEREVIEW_URL, issue_number) | 116 return '%s/%s' % (BASE_CODEREVIEW_URL, issue_number) |
115 | 117 |
116 def _patchset_url(self, issue_number, patchset_number): | 118 def _patchset_url(self, issue_number, patchset_number): |
117 return '%s/%s' % (self._issue_url(issue_number), patchset_number) | 119 return '%s/%s' % (self._issue_url(issue_number), patchset_number) |
OLD | NEW |