Merge lp:~afrantzis/jenkins-launchpad-plugin/triggered-builds into lp:jenkins-launchpad-plugin

Proposed by Alexandros Frantzis on 2016-01-27
Status: Merged
Approved by: Michał Sawicz on 2016-02-05
Approved revision: 133
Merged at revision: 129
Proposed branch: lp:~afrantzis/jenkins-launchpad-plugin/triggered-builds
Merge into: lp:jenkins-launchpad-plugin
Diff against target: 244 lines (+130/-3)
3 files modified
jlp/jenkinsutils.py (+57/-1)
tests/__init__.py (+56/-1)
tests/test_jenkinsutils.py (+17/-1)
To merge this branch: bzr merge lp:~afrantzis/jenkins-launchpad-plugin/triggered-builds
Reviewer Review Type Date Requested Status
Michał Sawicz 2016-01-27 Approve on 2016-02-05
PS Jenkins bot continuous-integration Approve on 2016-02-05
Review via email: mp+284117@code.launchpad.net

Commit Message

Include triggered builds in reported downstream builds when creating MP vote message.

This is required because new versions of the triggered build plugin don't report triggered builds as downstream.

Description of the Change

Include triggered builds in reported downstream builds when creating MP vote message.

This is required because new versions of the triggered build plugin don't report triggered builds as downstream.

To test locally run the following script from within the project directory:

import jlp.jenkinsutils
import sys

print(jlp.jenkinsutils.get_executed_test_runs_message(sys.argv[1]))

To post a comment you must log in.
130. By Alexandros Frantzis on 2016-01-27

Fix PEP8 complaints

131. By Alexandros Frantzis on 2016-01-27

More PEP8 fixes for trusty

132. By Alexandros Frantzis on 2016-02-02

Fix variable initialization

Michał Sawicz (saviq) :
review: Needs Fixing
133. By Alexandros Frantzis on 2016-02-05

Added comment for upstream project check

Michał Sawicz (saviq) wrote :

Replying to self: set() could break ordering, so using lists is better here.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'jlp/jenkinsutils.py'
2--- jlp/jenkinsutils.py 2016-01-08 11:09:40 +0000
3+++ jlp/jenkinsutils.py 2016-02-05 12:29:45 +0000
4@@ -59,6 +59,12 @@
5 return all_params
6
7
8+def _deduplicate_builds(seq):
9+ """ Internal method to remove duplicate builds from a build list"""
10+ seen = set()
11+ return [x for x in seq if str(x) not in seen and not seen.add(str(x))]
12+
13+
14 def get_running_builds(job, job_params={}):
15 """ For a given jenkins job return a list of currently active builds
16 :param job: name of the jenkins job
17@@ -446,6 +452,49 @@
18 return builds
19
20
21+def get_triggered_builds(jenkins, jenkins_url, job_name, build_number):
22+ """For a given build find all the triggered builds and return them in
23+ a list where each element is of this form:
24+ {
25+ 'project':
26+ 'number':
27+ 'url':
28+ 'result':
29+ }
30+ """
31+ return_data = []
32+
33+ json_request = jenkins_url + job_name + '/' + str(build_number) +\
34+ '/api/json?depth=2&tree=actions[triggeredBuilds[' +\
35+ 'number,url,result,actions[causes[upstreamBuild,' +\
36+ 'upstreamProject]]]]'
37+ triggered_builds = jenkins.get_json_data(json_request, append_api=False)
38+
39+ for action in triggered_builds.get('actions', []):
40+ for triggered_build in action.get('triggeredBuilds', []):
41+ causes = {}
42+ for triggered_action in triggered_build.get('actions', []):
43+ if 'causes' in triggered_action:
44+ causes = triggered_action['causes'][0]
45+ break
46+
47+ # Ensure the build was triggered by the correct upstream project.
48+ # This avoids including older builds that are erroneously listed
49+ # by jenkins in some cases (e.g., for rebuilds).
50+ if (causes.get('upstreamProject', None) != job_name or
51+ causes.get('upstreamBuild', None) != build_number):
52+ continue
53+
54+ tb_jenkins_url, tb_job_name, tb_build_number = \
55+ parse_jenkins_url(triggered_build['url'])
56+ return_data.append({'project': tb_job_name,
57+ 'number': triggered_build['number'],
58+ 'url': normalize_url(triggered_build['url']),
59+ 'result': triggered_build['result']})
60+
61+ return return_data
62+
63+
64 def get_downstream_builds(jenkins, jenkins_url, job_name, build_number,
65 depth=0):
66 """For a given build find all the downstream builds and return them in
67@@ -467,6 +516,13 @@
68 job_url = jenkins_url + job_name
69 downstream_projects = get_downstream_projects(jenkins, job_url)
70 return_data = []
71+
72+ # Get the triggered builds which in newer versions of the relevant plugin
73+ # are not included as normal downstream builds
74+ return_data += \
75+ get_triggered_builds(jenkins, jenkins_url, job_name, build_number)
76+
77+ # Add normal downstream builds
78 json_request = '/api/json?depth=2&tree=builds[number,url,result,' + \
79 'actions[causes[upstreamBuild,upstreamProject]]]'
80 for project in downstream_projects:
81@@ -508,7 +564,7 @@
82 return_data = [p for p in return_data
83 if not p['project'] in jobs_blacklisted_from_messages]
84
85- return return_data
86+ return _deduplicate_builds(return_data)
87
88
89 def get_downstream_projects(jenkins, job_url):
90
91=== modified file 'tests/__init__.py'
92--- tests/__init__.py 2013-03-13 15:08:30 +0000
93+++ tests/__init__.py 2016-02-05 12:29:45 +0000
94@@ -3,6 +3,8 @@
95 job_data = {
96 'downstreamProjects': [{'name': 'job-multiconfig'},
97 {'name': 'job-freestyle'}]}
98+ job_build_data = {}
99+
100 job_multiconfig_build_data = {
101 u'builds': [{u'actions': [{u'causes':
102 [{u'upstreamBuild': 3,
103@@ -76,7 +78,20 @@
104 u'result': u'UNSTABLE',
105 u'url': u'http://10.0.0.1:8080/job/' +
106 'freestyle-downstream/12/'}],
107- u'artifacts': []}
108+ u'artifacts': [],
109+ u'actions': [
110+ {u'triggeredBuilds': [{
111+ u'actions': [{
112+ u'causes': [{
113+ u'upstreamBuild': 12,
114+ u'upstreamProject':
115+ u'freestyle-downstream'}]},
116+ {}],
117+ u'number': 66,
118+ 'artifacts': [],
119+ u'result': u'FAILURE',
120+ u'url': u'http://10.0.0.1:8080/job/' +
121+ 'freestyle-triggered/66/'}]}]}
122
123 job_freestyle_downstream_data = {
124 'downstreamProjects': []}
125@@ -85,11 +100,22 @@
126 'result': 'UNSTABLE',
127 'artifacts': []}
128
129+ job_freestyle_triggered_data = {
130+ 'downstreamProjects': []}
131+
132+ job_freestyle_triggered_run_data = {
133+ 'result': 'FAILURE',
134+ 'artifacts': []}
135+
136+ job_freestyle_triggered_build_data = {}
137+
138 def get_json_data(self, url, append_api=True):
139 url = url.rstrip('/')
140 print url
141 if url == 'http://10.0.0.1:8080/job/my-job':
142 return self.job_data
143+ elif url == 'http://10.0.0.1:8080/job/my-job/3':
144+ return self.job_run_data
145 elif url == 'http://10.0.0.1:8080/job/job-multiconfig':
146 return self.job_multiconfig_data
147 elif url == 'http://10.0.0.1:8080/job/job-multiconfig/3':
148@@ -100,6 +126,10 @@
149 return self.job_freestyle_downstream_data
150 elif url == 'http://10.0.0.1:8080/job/freestyle-downstream/12':
151 return self.job_freestyle_downstream_run_data
152+ elif url == 'http://10.0.0.1:8080/job/freestyle-triggered':
153+ return self.job_freestyle_triggered_data
154+ elif url == 'http://10.0.0.1:8080/job/freestyle-triggered/66':
155+ return self.job_freestyle_triggered_run_data
156 elif url == 'http://10.0.0.1:8080/job/job-freestyle/7':
157 return self.job_freestyle_build_data
158 elif url == 'http://10.0.0.1:8080/job/job-multiconfig/' + \
159@@ -120,3 +150,28 @@
160 'json?depth=2&tree=builds[number,url,result,' +\
161 'actions[causes[upstreamBuild,upstreamProject]]]':
162 return self.job_freestyle_downstream_build_data
163+ elif url == 'http://10.0.0.1:8080/job/my-job/3' +\
164+ '/api/json?depth=2&tree=actions[triggeredBuilds[' +\
165+ 'number,url,result,actions[causes[upstreamBuild,' +\
166+ 'upstreamProject]]]]':
167+ return self.job_build_data
168+ elif url == 'http://10.0.0.1:8080/job/job-multiconfig/3' +\
169+ '/api/json?depth=2&tree=actions[triggeredBuilds[' +\
170+ 'number,url,result,actions[causes[upstreamBuild,' +\
171+ 'upstreamProject]]]]':
172+ return self.job_multiconfig_build_data
173+ elif url == 'http://10.0.0.1:8080/job/job-freestyle/7' +\
174+ '/api/json?depth=2&tree=actions[triggeredBuilds[' +\
175+ 'number,url,result,actions[causes[upstreamBuild,' +\
176+ 'upstreamProject]]]]':
177+ return self.job_freestyle_build_data
178+ elif url == 'http://10.0.0.1:8080/job/freestyle-downstream/12' +\
179+ '/api/json?depth=2&tree=actions[triggeredBuilds[' +\
180+ 'number,url,result,actions[causes[upstreamBuild,' +\
181+ 'upstreamProject]]]]':
182+ return self.job_freestyle_downstream_build_data
183+ elif url == 'http://10.0.0.1:8080/job/freestyle-triggered/66' +\
184+ '/api/json?depth=2&tree=actions[triggeredBuilds[' +\
185+ 'number,url,result,actions[causes[upstreamBuild,' +\
186+ 'upstreamProject]]]]':
187+ return self.job_freestyle_triggered_build_data
188
189=== modified file 'tests/test_jenkinsutils.py'
190--- tests/test_jenkinsutils.py 2016-01-08 11:09:40 +0000
191+++ tests/test_jenkinsutils.py 2016-02-05 12:29:45 +0000
192@@ -542,6 +542,10 @@
193 'number': 12,
194 'url': 'http://10.0.0.1:8080/job/freestyle-downstream/12/',
195 'result': 'UNSTABLE', },
196+ {'project': 'freestyle-triggered',
197+ 'number': 66,
198+ 'url': 'http://10.0.0.1:8080/job/freestyle-triggered/66/',
199+ 'result': 'FAILURE', },
200 ]
201 self.assertEqual(ret, expected)
202
203@@ -567,6 +571,10 @@
204 'number': 12,
205 'url': 'http://10.0.0.1:8080/job/freestyle-downstream/12/',
206 'result': 'UNSTABLE', },
207+ {'project': 'freestyle-triggered',
208+ 'number': 66,
209+ 'url': 'http://10.0.0.1:8080/job/freestyle-triggered/66/',
210+ 'result': 'FAILURE', },
211 ]
212 self.assertEqual(ret, expected)
213
214@@ -585,6 +593,10 @@
215 'number': 7,
216 'url': 'http://10.0.0.1:8080/job/job-freestyle/7/',
217 'result': 'SUCCESS', },
218+ {'project': 'freestyle-triggered',
219+ 'number': 66,
220+ 'url': 'http://10.0.0.1:8080/job/freestyle-triggered/66/',
221+ 'result': 'FAILURE', },
222 ]
223 self.assertEqual(ret, expected)
224
225@@ -637,7 +649,10 @@
226 'result': u'SUCCESS', },
227 {'output': 'http://10.0.0.1:8080/job/freestyle-downstream/' +
228 '12/console',
229- 'result': 'UNSTABLE'}
230+ 'result': 'UNSTABLE'},
231+ {'output': 'http://10.0.0.1:8080/job/freestyle-triggered/' +
232+ '66/console',
233+ 'result': 'FAILURE'},
234 ]
235 self.assertEqual(ret, expected)
236
237@@ -1084,6 +1099,7 @@
238 deb: http://{hostname}/job/job-freestyle/7/artifact/work/""" +
239 """output/*zip*/output.zip
240 UNSTABLE: http://{hostname}/job/freestyle-downstream/12
241+ FAILURE: http://{hostname}/job/freestyle-triggered/66/console
242 Coverity artifacts:
243 http://{hostname}/job/job-multiconfig/./distribution=""" +
244 """raring,flavor=amd64/3/artifact/results/coverity/CID_10895.html

Subscribers

People subscribed via source and target branches