Merge lp:~cjwatson/launchpad/api-build-builder into lp:launchpad

Proposed by Colin Watson on 2012-04-06
Status: Merged
Approved by: Ian Booth on 2012-04-10
Approved revision: no longer in the source branch.
Merged at revision: 15072
Proposed branch: lp:~cjwatson/launchpad/api-build-builder
Merge into: lp:launchpad
Diff against target: 259 lines (+48/-46)
3 files modified
lib/lp/buildmaster/interfaces/buildfarmjob.py (+5/-4)
lib/lp/soyuz/stories/webservice/xx-builds.txt (+2/-0)
lib/lp/soyuz/tests/test_binarypackagebuild.py (+41/-42)
To merge this branch: bzr merge lp:~cjwatson/launchpad/api-build-builder
Reviewer Review Type Date Requested Status
Benji York (community) code 2012-04-06 Approve on 2012-04-09
Review via email: mp+101162@code.launchpad.net

Commit Message

Export the builder property on builds.

Description of the Change

== Summary ==

As I noted in bug 975579, it's annoying that the API doesn't expose which builder was used for a given build.

== Proposed fix ==

Export it.

== Implementation details ==

I tidied up some tests a bit to get my LoC delta to zero. Along the way I happened to notice that one of the tests in TestBinaryPackageBuild.test_queueBuild was useless (failUnless when it should have been failUnlessEqual, or better assertEqual), so I fixed that.

== Tests ==

bin/test -vvct test_binarypackagebuild

== Demo and Q/A ==

Load the object corresponding to any successful build in the API, and check that its builder property is correct. Likewise for a build in needs-build state (builder should be None).

== lint ==

None.

To post a comment you must log in.
Benji York (benji) wrote :

This branch looks good. The removal of the "fail*" assertions is a nice touch.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
2--- lib/lp/buildmaster/interfaces/buildfarmjob.py 2012-01-02 11:21:14 +0000
3+++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2012-04-10 10:35:22 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 # pylint: disable-msg=E0211,E0213
10@@ -209,9 +209,10 @@
11 "is dispatched the first time and not changed in "
12 "subsequent build attempts.")))
13
14- builder = Reference(
15- title=_("Builder"), schema=IBuilder, required=False, readonly=True,
16- description=_("The builder assigned to this job."))
17+ builder = exported(
18+ Reference(
19+ title=_("Builder"), schema=IBuilder, required=False, readonly=True,
20+ description=_("The builder assigned to this job.")))
21
22 buildqueue_record = Reference(
23 # Really IBuildQueue, set in _schema_circular_imports to avoid
24
25=== modified file 'lib/lp/soyuz/stories/webservice/xx-builds.txt'
26--- lib/lp/soyuz/stories/webservice/xx-builds.txt 2012-01-31 01:19:45 +0000
27+++ lib/lp/soyuz/stories/webservice/xx-builds.txt 2012-04-10 10:35:22 +0000
28@@ -42,6 +42,7 @@
29 >>> pprint_entry(builds['entries'][0])
30 arch_tag: u'i386'
31 archive_link: u'http://.../beta/~cprov/+archive/ppa'
32+ builder_link: u'http://.../beta/builders/bob'
33 can_be_cancelled: False
34 can_be_rescored: False
35 can_be_retried: True
36@@ -74,6 +75,7 @@
37 archive_link: u'http://.../~cprov/+archive/ppa'
38 build_log_url:
39 u'http://.../~cprov/+archive/ppa/+build/26/+files/netapplet-1.0.0.tar.gz'
40+ builder_link: u'http://.../builders/bob'
41 buildstate: u'Failed to build'
42 can_be_cancelled: False
43 can_be_rescored: False
44
45=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
46--- lib/lp/soyuz/tests/test_binarypackagebuild.py 2012-02-09 23:09:36 +0000
47+++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2012-04-10 10:35:22 +0000
48@@ -1,4 +1,4 @@
49-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
50+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
51 # GNU Affero General Public License version 3 (see the file LICENSE).
52
53 """Test Build features."""
54@@ -83,9 +83,9 @@
55 bq = self.build.queueBuild()
56 self.assertProvides(bq, IBuildQueue)
57 self.assertProvides(bq.specific_job, IBuildPackageJob)
58- self.failUnlessEqual(self.build.is_virtualized, bq.virtualized)
59- self.failIfEqual(None, bq.processor)
60- self.failUnless(bq, self.build.buildqueue_record)
61+ self.assertEqual(self.build.is_virtualized, bq.virtualized)
62+ self.assertIsNotNone(bq.processor)
63+ self.assertEqual(bq, self.build.buildqueue_record)
64
65 def test_getBuildCookie(self):
66 # A build cookie is made up of the job type and record id.
67@@ -93,7 +93,7 @@
68 Store.of(self.build).flush()
69 cookie = self.build.getBuildCookie()
70 expected_cookie = "PACKAGEBUILD-%d" % self.build.id
71- self.assertEquals(expected_cookie, cookie)
72+ self.assertEqual(expected_cookie, cookie)
73
74 def test_estimateDuration(self):
75 # Without previous builds, a negligable package size estimate is 60s
76@@ -124,7 +124,7 @@
77 # the distribution source package release when the context
78 # is not a PPA or a copy archive.
79 self.addFakeBuildLog()
80- self.failUnlessEqual(
81+ self.assertEqual(
82 'http://launchpad.dev/ubuntutest/+source/'
83 'gedit/666/+build/%d/+files/mybuildlog.txt' % (
84 self.build.package_build.build_farm_job.id),
85@@ -137,7 +137,7 @@
86 ppa_owner = self.factory.makePerson(name="joe")
87 removeSecurityProxy(self.build).archive = self.factory.makeArchive(
88 owner=ppa_owner, name="myppa")
89- self.failUnlessEqual(
90+ self.assertEqual(
91 'http://launchpad.dev/~joe/'
92 '+archive/myppa/+build/%d/+files/mybuildlog.txt' % (
93 self.build.build_farm_job.id),
94@@ -150,7 +150,7 @@
95 store = Store.of(build_farm_job)
96 store.flush()
97
98- self.failUnlessEqual(self.build, build_farm_job.getSpecificJob())
99+ self.assertEqual(self.build, build_farm_job.getSpecificJob())
100
101 def test_adapt_from_build_farm_job_prefetching(self):
102 # The package_build is prefetched for efficiency.
103@@ -173,8 +173,7 @@
104 # If getSpecificJob is called on the binary build it is a noop.
105 store = Store.of(self.build)
106 store.flush()
107- self.assertStatementCount(
108- 0, self.build.getSpecificJob)
109+ self.assertStatementCount(0, self.build.getSpecificJob)
110
111 def test_getUploader(self):
112 # For ACL purposes the uploader is the changes file signer.
113@@ -182,7 +181,7 @@
114 class MockChanges:
115 signer = "Somebody <somebody@ubuntu.com>"
116
117- self.assertEquals("Somebody <somebody@ubuntu.com>",
118+ self.assertEqual("Somebody <somebody@ubuntu.com>",
119 self.build.getUploader(MockChanges()))
120
121 def test_can_be_cancelled(self):
122@@ -301,7 +300,12 @@
123 depwait_build = self._setupSimpleDepwaitContext()
124 self.layer.txn.commit()
125 depwait_build.updateDependencies()
126- self.assertEquals(depwait_build.dependencies, '')
127+ self.assertEqual(depwait_build.dependencies, '')
128+
129+ def assertRaisesUnparsableDependencies(self, depwait_build, dependencies):
130+ depwait_build.dependencies = dependencies
131+ self.assertRaises(
132+ UnparsableDependencies, depwait_build.updateDependencies)
133
134 def testInvalidDependencies(self):
135 # Calling `IBinaryPackageBuild.updateDependencies` on a build with
136@@ -310,24 +314,16 @@
137 depwait_build = self._setupSimpleDepwaitContext()
138
139 # None is not a valid dependency values.
140- depwait_build.dependencies = None
141- self.assertRaises(
142- UnparsableDependencies, depwait_build.updateDependencies)
143+ self.assertRaisesUnparsableDependencies(depwait_build, None)
144
145 # Missing 'name'.
146- depwait_build.dependencies = u'(>> version)'
147- self.assertRaises(
148- UnparsableDependencies, depwait_build.updateDependencies)
149+ self.assertRaisesUnparsableDependencies(depwait_build, u'(>> version)')
150
151 # Missing 'version'.
152- depwait_build.dependencies = u'name (>>)'
153- self.assertRaises(
154- UnparsableDependencies, depwait_build.updateDependencies)
155+ self.assertRaisesUnparsableDependencies(depwait_build, u'name (>>)')
156
157- # Missing comman between dependencies.
158- depwait_build.dependencies = u'name1 name2'
159- self.assertRaises(
160- UnparsableDependencies, depwait_build.updateDependencies)
161+ # Missing comma between dependencies.
162+ self.assertRaisesUnparsableDependencies(depwait_build, u'name1 name2')
163
164 def testBug378828(self):
165 # `IBinaryPackageBuild.updateDependencies` copes with the
166@@ -344,7 +340,7 @@
167
168 self.layer.txn.commit()
169 depwait_build.updateDependencies()
170- self.assertEquals(depwait_build.dependencies, '')
171+ self.assertEqual(depwait_build.dependencies, '')
172
173 def testVersionedDependencies(self):
174 # `IBinaryPackageBuild.updateDependencies` supports versioned
175@@ -357,10 +353,10 @@
176
177 depwait_build.dependencies = u'dep-bin (>> 666)'
178 depwait_build.updateDependencies()
179- self.assertEquals(depwait_build.dependencies, u'dep-bin (>> 666)')
180+ self.assertEqual(depwait_build.dependencies, u'dep-bin (>> 666)')
181 depwait_build.dependencies = u'dep-bin (>= 666)'
182 depwait_build.updateDependencies()
183- self.assertEquals(depwait_build.dependencies, u'')
184+ self.assertEqual(depwait_build.dependencies, u'')
185
186 def testVersionedDependencyOnOldPublication(self):
187 # `IBinaryPackageBuild.updateDependencies` doesn't just consider
188@@ -376,10 +372,10 @@
189
190 depwait_build.dependencies = u'dep-bin (= 666)'
191 depwait_build.updateDependencies()
192- self.assertEquals(depwait_build.dependencies, u'')
193+ self.assertEqual(depwait_build.dependencies, u'')
194 depwait_build.dependencies = u'dep-bin (= 999)'
195 depwait_build.updateDependencies()
196- self.assertEquals(depwait_build.dependencies, u'')
197+ self.assertEqual(depwait_build.dependencies, u'')
198
199
200 class BaseTestCaseWithThreeBuilds(TestCaseWithFactory):
201@@ -426,8 +422,7 @@
202
203 def test_getByBuildFarmJob_returns_none_when_missing(self):
204 sprb = self.factory.makeSourcePackageRecipeBuild()
205- self.assertIs(
206- None,
207+ self.assertIsNone(
208 getUtility(IBinaryPackageBuildSet).getByBuildFarmJob(
209 sprb.build_farm_job))
210
211@@ -598,8 +593,7 @@
212 expected = self.build.can_be_cancelled
213 entry_url = api_url(self.build)
214 logout()
215- entry = self.webservice.get(
216- entry_url, api_version='devel').jsonBody()
217+ entry = self.webservice.get(entry_url, api_version='devel').jsonBody()
218 self.assertEqual(expected, entry['can_be_cancelled'])
219
220 def test_cancel_is_exported(self):
221@@ -607,25 +601,30 @@
222 build_url = api_url(self.build)
223 self.build.queueBuild()
224 logout()
225- entry = self.webservice.get(
226- build_url, api_version='devel').jsonBody()
227+ entry = self.webservice.get(build_url, api_version='devel').jsonBody()
228 response = self.webservice.named_post(
229 entry['self_link'], 'cancel', api_version='devel')
230 self.assertEqual(200, response.status)
231- entry = self.webservice.get(
232- build_url, api_version='devel').jsonBody()
233+ entry = self.webservice.get(build_url, api_version='devel').jsonBody()
234 self.assertEqual(BuildStatus.CANCELLED.title, entry['buildstate'])
235
236 def test_cancel_security(self):
237 # Check that unauthorised users cannot call cancel()
238 build_url = api_url(self.build)
239- person = self.factory.makePerson()
240 webservice = webservice_for_person(
241- person, permission=OAuthPermission.WRITE_PUBLIC)
242+ self.factory.makePerson(), permission=OAuthPermission.WRITE_PUBLIC)
243 logout()
244
245- entry = webservice.get(
246- build_url, api_version='devel').jsonBody()
247+ entry = webservice.get(build_url, api_version='devel').jsonBody()
248 response = webservice.named_post(
249 entry['self_link'], 'cancel', api_version='devel')
250 self.assertEqual(401, response.status)
251+
252+ def test_builder_is_exported(self):
253+ # The builder property is exported.
254+ removeSecurityProxy(self.build).builder = self.factory.makeBuilder()
255+ build_url = api_url(self.build)
256+ builder_url = api_url(self.build.builder)
257+ logout()
258+ entry = self.webservice.get(build_url, api_version='devel').jsonBody()
259+ self.assertEndsWith(entry['builder_link'], builder_url)