Merge lp:~michael.nelson/launchpad/594492-present-bfjs-in-builder-history into lp:launchpad

Proposed by Michael Nelson
Status: Merged
Approved by: Michael Nelson
Approved revision: no longer in the source branch.
Merged at revision: 11041
Proposed branch: lp:~michael.nelson/launchpad/594492-present-bfjs-in-builder-history
Merge into: lp:launchpad
Prerequisite: lp:~michael.nelson/launchpad/536700-present-packagebuilds-in-ppa-context-2
Diff against target: 698 lines (+323/-51)
15 files modified
lib/lp/buildmaster/configure.zcml (+6/-0)
lib/lp/buildmaster/interfaces/buildfarmjob.py (+15/-0)
lib/lp/buildmaster/model/builder.py (+13/-8)
lib/lp/buildmaster/model/buildfarmjob.py (+71/-2)
lib/lp/buildmaster/tests/test_buildfarmjob.py (+132/-12)
lib/lp/registry/model/distribution.py (+3/-1)
lib/lp/soyuz/browser/build.py (+7/-1)
lib/lp/soyuz/browser/builder.py (+1/-0)
lib/lp/soyuz/interfaces/archive.py (+3/-9)
lib/lp/soyuz/interfaces/buildrecords.py (+8/-2)
lib/lp/soyuz/model/archive.py (+4/-3)
lib/lp/soyuz/model/distroarchseries.py (+3/-1)
lib/lp/soyuz/tests/test_binarypackagebuild.py (+2/-2)
lib/lp/soyuz/tests/test_hasbuildrecords.py (+42/-3)
lib/lp/testing/factory.py (+13/-7)
To merge this branch: bzr merge lp:~michael.nelson/launchpad/594492-present-bfjs-in-builder-history
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Canonical Launchpad Engineering Pending
Review via email: mp+27717@code.launchpad.net

Commit message

Builder history presents general build farm jobs.

Description of the change

Please review my branch:

   bzr+ssh://bazaar.launchpad.net/~michael.nelson/launchpad/594492-present-bfjs-in-builder-history

revision 11026.

Test command:
bin/test -vvm test_buildfarmjob -m test_hasbuildrecords
Pre-implementation call with: Lots of discussion on bug 491330.

This is a fix for bug 491330 that doesn't yet have any visible effect. It
updates the Builder.getBuildRecords() query to accept binary_only=False, in
which case it will return BuildFarmJob records for that builder using the new
IBuildFarmJobSet.getBuildsForBuilder() method.

This is now used by the BuilderHistory view, so that when other BuildFarmJob
types are added, they will appear automatically in the builder history.

The main issue in this branch was *not* including BuildFarmJobs that are private. Currently this is only the case if the BuildFarmJob is also a PackageBuild, and the package build is associated with a private archive. I tried both using ResultSet.difference and a left join, and went with the left join in the end.

To post a comment you must log in.
Revision history for this message
Michael Nelson (michael.nelson) wrote :

Carried out an irc review with jtv yesterday:

http://irclogs.ubuntu.com/2010/06/16/%23launchpad-reviews.html#t14:53

The changes are pushed and attached from that discussion. Thanks jtv!

=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
--- lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-06-15 16:17:17 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-06-16 14:12:20 +0000
@@ -294,13 +294,14 @@
294294
295295
296class IBuildFarmJobSet(Interface):296class IBuildFarmJobSet(Interface):
297 """A utility representing a set of package builds."""297 """A utility representing a set of build farm jobs."""
298298
299 def getBuildsForBuilder(builder_id, status=None, user=None):299 def getBuildsForBuilder(builder_id, status=None, user=None):
300 """Return `IBuildFarmJob` records touched by a builder.300 """Return `IBuildFarmJob` records touched by a builder.
301301
302 :param builder_id: The id of the builder for which to find builds.302 :param builder_id: The id of the builder for which to find builds.
303 :param status: If status is provided, only builds with that status303 :param status: If given, limit the search to builds with this status.
304 will be returned.304 :param user: If given, this will be used to determine private builds
305 that should be included.
305 :return: a `ResultSet` representing the requested builds.306 :return: a `ResultSet` representing the requested builds.
306 """307 """
307308
=== modified file 'lib/lp/buildmaster/model/buildfarmjob.py'
--- lib/lp/buildmaster/model/buildfarmjob.py 2010-06-16 11:25:18 +0000
+++ lib/lp/buildmaster/model/buildfarmjob.py 2010-06-16 16:01:08 +0000
@@ -16,7 +16,7 @@
1616
17import pytz17import pytz
1818
19from storm.expr import And, Desc, Join, LeftJoin, Or, Select19from storm.expr import And, Coalesce, Desc, Join, LeftJoin, Select
20from storm.locals import Bool, DateTime, Int, Reference, Storm20from storm.locals import Bool, DateTime, Int, Reference, Storm
21from storm.store import Store21from storm.store import Store
2222
@@ -28,7 +28,7 @@
28from canonical.database.constants import UTC_NOW28from canonical.database.constants import UTC_NOW
29from canonical.database.enumcol import DBEnum29from canonical.database.enumcol import DBEnum
30from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities30from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
31from canonical.launchpad.interfaces.lpstorm import IMasterStore31from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore
32from canonical.launchpad.webapp.interfaces import (32from canonical.launchpad.webapp.interfaces import (
33 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE)33 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE)
3434
@@ -353,16 +353,19 @@
353353
354 def getBuildsForBuilder(self, builder_id, status=None, user=None):354 def getBuildsForBuilder(self, builder_id, status=None, user=None):
355 """See `IBuildFarmJobSet`."""355 """See `IBuildFarmJobSet`."""
356 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
357 # Imported here to avoid circular imports.356 # Imported here to avoid circular imports.
358 from lp.buildmaster.model.packagebuild import PackageBuild357 from lp.buildmaster.model.packagebuild import PackageBuild
359 from lp.soyuz.model.archive import Archive358 from lp.soyuz.model.archive import Archive
360359
360 extra_clauses = [BuildFarmJob.builder == builder_id]
361 if status is not None:
362 extra_clauses.append(BuildFarmJob.status == status)
363
361 # We need to ensure that we don't include any private builds.364 # We need to ensure that we don't include any private builds.
362 # Currently only package builds can be private (via their365 # Currently only package builds can be private (via their
363 # related archive), but not all build farm jobs will have a366 # related archive), but not all build farm jobs will have a
364 # related package build - hence the left join.367 # related package build - hence the left join.
365 bfjs_with_optional_package_builds = LeftJoin(368 left_join_pkg_builds = LeftJoin(
366 BuildFarmJob,369 BuildFarmJob,
367 Join(370 Join(
368 PackageBuild,371 PackageBuild,
@@ -370,35 +373,41 @@
370 And(PackageBuild.archive == Archive.id)),373 And(PackageBuild.archive == Archive.id)),
371 PackageBuild.build_farm_job == BuildFarmJob.id)374 PackageBuild.build_farm_job == BuildFarmJob.id)
372375
373 filtered_builds = store.using(bfjs_with_optional_package_builds).find(376 filtered_builds = IStore(BuildFarmJob).using(
374 BuildFarmJob,377 left_join_pkg_builds).find(BuildFarmJob, *extra_clauses)
375 BuildFarmJob.builder == builder_id)378
376
377 if status is not None:
378 filtered_builds = filtered_builds.find(
379 BuildFarmJob.status == status)
380
381 # Anonymous users can only see public builds.
382 if user is None:379 if user is None:
383 filtered_builds = filtered_builds.find(380 # Anonymous requests don't get to see private builds at all.
384 Or(Archive.private == None, Archive.private == False))381 filtered_builds = filtered_builds.find(
385382 Coalesce(Archive.private, False) == False)
386 # All other non-admins can additionally see private builds for383
387 # which they are a member of the team owning the archive.384 elif user.inTeam(getUtility(ILaunchpadCelebrities).admin):
388 elif not user.inTeam(getUtility(ILaunchpadCelebrities).admin):385 # Admins get to see everything.
386 pass
387 else:
388 # Everyone else sees a union of all public builds and the
389 # specific private builds to which they have access.
390 filtered_builds = filtered_builds.find(
391 Coalesce(Archive.private, False) == False)
392
389 user_teams_subselect = Select(393 user_teams_subselect = Select(
390 TeamParticipation.teamID,394 TeamParticipation.teamID,
391 where=And(395 where=And(
392 TeamParticipation.personID == user.id,396 TeamParticipation.personID == user.id,
393 TeamParticipation.teamID == Archive.ownerID))397 TeamParticipation.teamID == Archive.ownerID))
394 filtered_builds = filtered_builds.find(398 private_builds_for_user = IStore(BuildFarmJob).find(
395 Or(399 BuildFarmJob,
396 Archive.private == None,400 PackageBuild.build_farm_job == BuildFarmJob.id,
397 Or(401 PackageBuild.archive == Archive.id,
398 Archive.private == False,402 Archive.private == True,
399 Archive.ownerID.is_in(user_teams_subselect))))403 Archive.ownerID.is_in(user_teams_subselect),
400404 *extra_clauses)
401 filtered_builds.order_by(Desc(BuildFarmJob.date_finished), BuildFarmJob.id)405
406 filtered_builds = filtered_builds.union(
407 private_builds_for_user)
408
409 filtered_builds.order_by(
410 Desc(BuildFarmJob.date_finished), BuildFarmJob.id)
402411
403 return filtered_builds412 return filtered_builds
404413
405414
=== modified file 'lib/lp/buildmaster/tests/test_buildfarmjob.py'
--- lib/lp/buildmaster/tests/test_buildfarmjob.py 2010-06-16 11:25:18 +0000
+++ lib/lp/buildmaster/tests/test_buildfarmjob.py 2010-06-17 08:26:34 +0000
@@ -24,28 +24,37 @@
24 BuildFarmJobType, IBuildFarmJob, IBuildFarmJobSet, IBuildFarmJobSource,24 BuildFarmJobType, IBuildFarmJob, IBuildFarmJobSet, IBuildFarmJobSource,
25 InconsistentBuildFarmJobError)25 InconsistentBuildFarmJobError)
26from lp.buildmaster.model.buildfarmjob import BuildFarmJob26from lp.buildmaster.model.buildfarmjob import BuildFarmJob
27from lp.testing import login, TestCaseWithFactory27from lp.testing import ANONYMOUS, login, login_person, TestCaseWithFactory
2828
2929
30class TestBuildFarmJobBase(TestCaseWithFactory):30class TestBuildFarmJobMixin:
3131
32 layer = DatabaseFunctionalLayer32 layer = DatabaseFunctionalLayer
3333
34 def setUp(self):34 def setUp(self):
35 """Create a build farm job with which to test."""35 """Create a build farm job with which to test."""
36 super(TestBuildFarmJobBase, self).setUp()36 super(TestBuildFarmJobMixin, self).setUp()
37 self.build_farm_job = self.makeBuildFarmJob()37 self.build_farm_job = self.makeBuildFarmJob()
3838
39 def makeBuildFarmJob(self, builder=None,39 def makeBuildFarmJob(self, builder=None,
40 job_type=BuildFarmJobType.PACKAGEBUILD,40 job_type=BuildFarmJobType.PACKAGEBUILD,
41 status=BuildStatus.FULLYBUILT):41 status=BuildStatus.NEEDSBUILD,
42 date_finished=None):
43 """A factory method for creating PackageBuilds.
44
45 This is not included in the launchpad test factory because
46 a build farm job should never be instantiated outside the
47 context of a derived class (such as a BinaryPackageBuild
48 or eventually a SPRecipeBuild).
49 """
42 build_farm_job = getUtility(IBuildFarmJobSource).new(50 build_farm_job = getUtility(IBuildFarmJobSource).new(
43 job_type=job_type, status=status)51 job_type=job_type, status=status)
44 removeSecurityProxy(build_farm_job).builder = builder52 removeSecurityProxy(build_farm_job).builder = builder
53 removeSecurityProxy(build_farm_job).date_finished = date_finished
45 return build_farm_job54 return build_farm_job
4655
4756
48class TestBuildFarmJob(TestBuildFarmJobBase):57class TestBuildFarmJob(TestBuildFarmJobMixin, TestCaseWithFactory):
49 """Tests for the build farm job object."""58 """Tests for the build farm job object."""
5059
51 def test_providesInterface(self):60 def test_providesInterface(self):
@@ -62,7 +71,6 @@
62 self.assertEqual(self.build_farm_job, retrieved_job)71 self.assertEqual(self.build_farm_job, retrieved_job)
6372
64 def test_default_values(self):73 def test_default_values(self):
65 # A build farm job defaults to the NEEDSBUILD status.
66 # We flush the database updates to ensure sql defaults74 # We flush the database updates to ensure sql defaults
67 # are set for various attributes.75 # are set for various attributes.
68 flush_database_updates()76 flush_database_updates()
@@ -172,7 +180,7 @@
172 InconsistentBuildFarmJobError, self.build_farm_job.getSpecificJob)180 InconsistentBuildFarmJobError, self.build_farm_job.getSpecificJob)
173181
174182
175class TestBuildFarmJobSecurity(TestBuildFarmJobBase):183class TestBuildFarmJobSecurity(TestBuildFarmJobMixin, TestCaseWithFactory):
176184
177 def test_view_build_farm_job(self):185 def test_view_build_farm_job(self):
178 # Anonymous access can read public builds, but not edit.186 # Anonymous access can read public builds, but not edit.
@@ -190,100 +198,102 @@
190 BuildStatus.FULLYBUILT, self.build_farm_job.status)198 BuildStatus.FULLYBUILT, self.build_farm_job.status)
191199
192200
193class TestBuildFarmJobSet(TestBuildFarmJobBase):201class TestBuildFarmJobSet(TestBuildFarmJobMixin, TestCaseWithFactory):
194202
195 layer = LaunchpadFunctionalLayer203 layer = LaunchpadFunctionalLayer
196204
197 def setUp(self):205 def setUp(self):
198 super(TestBuildFarmJobSet, self).setUp()206 super(TestBuildFarmJobSet, self).setUp()
199 self.builder = self.factory.makeBuilder()207 self.builder = self.factory.makeBuilder()
200 self.build_farm_jobs = []
201 self.build_farm_jobs.append(
202 self.makeBuildFarmJob(builder=self.builder))
203 self.build_farm_jobs.append(self.makeBuildFarmJob(
204 builder=self.builder,
205 job_type=BuildFarmJobType.RECIPEBRANCHBUILD))
206 self.build_farm_jobs.append(self.makeBuildFarmJob(
207 builder=self.builder, status=BuildStatus.BUILDING))
208
209 # For good measure, create a different type of build that will
210 # also have an associated PackageBuild.
211 owning_team = self.factory.makeTeam()
212 archive = self.factory.makeArchive(owner=owning_team)
213 self.binary_package_build = self.factory.makeBinaryPackageBuild(
214 archive=archive, builder=self.builder)
215 self.build_farm_jobs.append(self.binary_package_build.build_farm_job)
216
217 self.build_farm_job_set = getUtility(IBuildFarmJobSet)208 self.build_farm_job_set = getUtility(IBuildFarmJobSet)
218209
219 def test_getBuildsForBuilder_all(self):210 def test_getBuildsForBuilder_all(self):
220 # The default call without arguments returns all builds for the211 # The default call without arguments returns all builds for the
221 # builder, and not those for other builders.212 # builder, and not those for other builders.
213 build1 = self.makeBuildFarmJob(builder=self.builder)
214 build2 = self.makeBuildFarmJob(builder=self.builder)
222 self.makeBuildFarmJob(builder=self.factory.makeBuilder())215 self.makeBuildFarmJob(builder=self.factory.makeBuilder())
223 self.assertContentEqual(216 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)
224 self.build_farm_jobs, self.build_farm_job_set.getBuildsForBuilder(217
225 self.builder))218 self.assertContentEqual([build1, build2], result)
226219
227 def test_getBuildsForBuilder_by_status(self):220 def test_getBuildsForBuilder_by_status(self):
228 # If the status arg is used, the results will be filtered by221 # If the status arg is used, the results will be filtered by
229 # status.222 # status.
230 self.assertContentEqual(223 successful_builds = [
231 self.build_farm_jobs[:2],224 self.makeBuildFarmJob(
232 self.build_farm_job_set.getBuildsForBuilder(225 builder=self.builder, status=BuildStatus.FULLYBUILT),
233 self.builder, status=BuildStatus.FULLYBUILT))226 self.makeBuildFarmJob(
234227 builder=self.builder, status=BuildStatus.FULLYBUILT),
235 def makeBuildPrivate(self, build):228 ]
236 """Helper to privatise a package build."""229 self.makeBuildFarmJob(builder=self.builder)
237 removeSecurityProxy(build.archive).buildd_secret = "blah"230
238 removeSecurityProxy(build.archive).private = True231 query_by_status = self.build_farm_job_set.getBuildsForBuilder(
232 self.builder, status=BuildStatus.FULLYBUILT)
233
234 self.assertContentEqual(successful_builds, query_by_status)
235
236 def _makePrivateAndNonPrivateBuilds(self, owning_team=None):
237 """Return a tuple of a private and non-private build farm job."""
238 if owning_team is None:
239 owning_team = self.factory.makeTeam()
240 archive = self.factory.makeArchive(owner=owning_team, private=True)
241 login_person(owning_team.teamowner)
242 private_build = self.factory.makeBinaryPackageBuild(
243 archive=archive, builder=self.builder)
244 private_build = private_build.build_farm_job
245 login(ANONYMOUS)
246 other_build = self.makeBuildFarmJob(builder=self.builder)
247 return (private_build, other_build)
239248
240 def test_getBuildsForBuilder_hides_private_from_anon(self):249 def test_getBuildsForBuilder_hides_private_from_anon(self):
241 # If no user is passed, all private builds are filtered out.250 # If no user is passed, all private builds are filtered out.
242251 private_build, other_build = self._makePrivateAndNonPrivateBuilds()
243 # When private it is not included for anon requests.
244 self.makeBuildPrivate(self.binary_package_build)
245 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)252 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)
246 self.assertTrue(253 self.assertContentEqual([other_build], result)
247 self.binary_package_build.build_farm_job not in result)
248254
249 def test_getBuildsForBuilder_hides_private_other_users(self):255 def test_getBuildsForBuilder_hides_private_other_users(self):
250 # Private builds are not returned for users without permission256 # Private builds are not returned for users without permission
251 # to view them.257 # to view them.
252 self.makeBuildPrivate(self.binary_package_build)258 private_build, other_build = self._makePrivateAndNonPrivateBuilds()
253 result = self.build_farm_job_set.getBuildsForBuilder(259 result = self.build_farm_job_set.getBuildsForBuilder(
254 self.builder, user=self.factory.makePerson())260 self.builder, user=self.factory.makePerson())
255 self.assertTrue(261 self.assertContentEqual([other_build], result)
256 self.binary_package_build.build_farm_job not in result)
257262
258 def test_getBuildsForBuilder_shows_private_to_admin(self):263 def test_getBuildsForBuilder_shows_private_to_admin(self):
259 # Admin users can see private builds.264 # Admin users can see private builds.
260 admin_team = getUtility(ILaunchpadCelebrities).admin265 admin_team = getUtility(ILaunchpadCelebrities).admin
261 self.makeBuildPrivate(self.binary_package_build)266 private_build, other_build = self._makePrivateAndNonPrivateBuilds()
262 result = self.build_farm_job_set.getBuildsForBuilder(267 result = self.build_farm_job_set.getBuildsForBuilder(
263 self.builder, user=admin_team.teamowner)268 self.builder, user=admin_team.teamowner)
264 self.assertTrue(self.binary_package_build.build_farm_job in result)269 self.assertContentEqual([private_build, other_build], result)
265270
266 def test_getBuildsForBuilder_shows_private_to_authorised(self):271 def test_getBuildsForBuilder_shows_private_to_authorised(self):
267 # Similarly, if the user is in the owning team they can see it.272 # Similarly, if the user is in the owning team they can see it.
268 self.makeBuildPrivate(self.binary_package_build)273 owning_team = self.factory.makeTeam()
274 private_build, other_build = self._makePrivateAndNonPrivateBuilds(
275 owning_team=owning_team)
269 result = self.build_farm_job_set.getBuildsForBuilder(276 result = self.build_farm_job_set.getBuildsForBuilder(
270 self.builder,277 self.builder,
271 user=self.binary_package_build.archive.owner.teamowner)278 user=owning_team.teamowner)
272 self.assertTrue(self.binary_package_build.build_farm_job in result)279 self.assertContentEqual([private_build, other_build], result)
273280
274 def test_getBuildsForBuilder_ordered_by_date_finished(self):281 def test_getBuildsForBuilder_ordered_by_date_finished(self):
275 # Results are returned with the oldest build last.282 # Results are returned with the oldest build last.
276 naked_build_0 = removeSecurityProxy(self.build_farm_jobs[0])283 build_1 = self.makeBuildFarmJob(
277 naked_build_1 = removeSecurityProxy(self.build_farm_jobs[1])284 builder=self.builder,
278285 date_finished=datetime(2008, 10, 10, tzinfo=pytz.UTC))
279 naked_build_0.date_finished = datetime(2008, 10, 10, tzinfo=pytz.UTC)286 build_2 = self.makeBuildFarmJob(
280 naked_build_1.date_finished = datetime(2008, 11, 10, tzinfo=pytz.UTC)287 builder=self.builder,
281 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)288 date_finished=datetime(2008, 11, 10, tzinfo=pytz.UTC))
282 self.assertEqual(self.build_farm_jobs[0], result[3])289
283290 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)
284 naked_build_0.date_finished = datetime(2008, 12, 10, tzinfo=pytz.UTC)291 self.assertEqual([build_2, build_1], list(result))
285 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)292
286 self.assertEqual(self.build_farm_jobs[1], result[3])293 removeSecurityProxy(build_2).date_finished = (
294 datetime(2008, 8, 10, tzinfo=pytz.UTC))
295 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)
296 self.assertEqual([build_1, build_2], list(result))
287297
288298
289def test_suite():299def test_suite():
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

A few more changes discussed on IRC:

 * The tests are much better, but hiding a login() in a helper is a bit obscure. Replaced with a removeSecurityProxy.
 * In test_binary_only_false, instead of just testing that builds.count() == 3 for a binary-only query, better to test separately that (1) all its elements are binary builds, (2) the result is nonempty so the test isn't trivial, and (3) the result is smaller than with a non-binary-only query so the test isn't meaningless.

Great shape now. Land that baby!

Quod subigo farinam,

Jeroen

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/buildmaster/configure.zcml'
--- lib/lp/buildmaster/configure.zcml 2010-06-21 07:32:35 +0000
+++ lib/lp/buildmaster/configure.zcml 2010-06-21 07:32:36 +0000
@@ -58,6 +58,12 @@
58 <allow58 <allow
59 interface="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJobSource" />59 interface="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJobSource" />
60 </securedutility>60 </securedutility>
61 <securedutility
62 class="lp.buildmaster.model.buildfarmjob.BuildFarmJobSet"
63 provides="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJobSet">
64 <allow
65 interface="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJobSet" />
66 </securedutility>
6167
62 <!-- PackageBuild -->68 <!-- PackageBuild -->
63 <class69 <class
6470
=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
--- lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-06-21 07:32:35 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-06-21 07:32:36 +0000
@@ -10,6 +10,7 @@
10__all__ = [10__all__ = [
11 'IBuildFarmJob',11 'IBuildFarmJob',
12 'IBuildFarmJobOld',12 'IBuildFarmJobOld',
13 'IBuildFarmJobSet',
13 'IBuildFarmJobSource',14 'IBuildFarmJobSource',
14 'InconsistentBuildFarmJobError',15 'InconsistentBuildFarmJobError',
15 'ISpecificBuildFarmJob',16 'ISpecificBuildFarmJob',
@@ -290,3 +291,17 @@
290 :param virtualized: An optional boolean indicating whether291 :param virtualized: An optional boolean indicating whether
291 this job should be run virtualized.292 this job should be run virtualized.
292 """293 """
294
295
296class IBuildFarmJobSet(Interface):
297 """A utility representing a set of build farm jobs."""
298
299 def getBuildsForBuilder(builder_id, status=None, user=None):
300 """Return `IBuildFarmJob` records touched by a builder.
301
302 :param builder_id: The id of the builder for which to find builds.
303 :param status: If given, limit the search to builds with this status.
304 :param user: If given, this will be used to determine private builds
305 that should be included.
306 :return: a `ResultSet` representing the requested builds.
307 """
293308
=== modified file 'lib/lp/buildmaster/model/builder.py'
--- lib/lp/buildmaster/model/builder.py 2010-06-21 07:32:35 +0000
+++ lib/lp/buildmaster/model/builder.py 2010-06-21 07:32:36 +0000
@@ -45,6 +45,7 @@
45 BuildDaemonError, BuildSlaveFailure, CannotBuild, CannotFetchFile,45 BuildDaemonError, BuildSlaveFailure, CannotBuild, CannotFetchFile,
46 CannotResumeHost, CorruptBuildCookie, IBuilder, IBuilderSet,46 CannotResumeHost, CorruptBuildCookie, IBuilder, IBuilderSet,
47 ProtocolVersionMismatch)47 ProtocolVersionMismatch)
48from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSet
48from lp.buildmaster.interfaces.buildfarmjobbehavior import (49from lp.buildmaster.interfaces.buildfarmjobbehavior import (
49 BuildBehaviorMismatch)50 BuildBehaviorMismatch)
50from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet51from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
@@ -59,7 +60,8 @@
59# These dependencies on soyuz will be removed when getBuildRecords()60# These dependencies on soyuz will be removed when getBuildRecords()
60# is moved.61# is moved.
61from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet62from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
62from lp.soyuz.interfaces.buildrecords import IHasBuildRecords63from lp.soyuz.interfaces.buildrecords import (
64 IHasBuildRecords, IncompatibleArguments)
63from lp.soyuz.model.buildpackagejob import BuildPackageJob65from lp.soyuz.model.buildpackagejob import BuildPackageJob
6466
6567
@@ -423,16 +425,19 @@
423 self.builderok = False425 self.builderok = False
424 self.failnotes = reason426 self.failnotes = reason
425427
426 # XXX Michael Nelson 20091202 bug=491330. The current UI assumes
427 # that the builder history will display binary build records, as
428 # returned by getBuildRecords() below. See the bug for a discussion
429 # of the options.
430
431 def getBuildRecords(self, build_state=None, name=None, arch_tag=None,428 def getBuildRecords(self, build_state=None, name=None, arch_tag=None,
432 user=None, binary_only=True):429 user=None, binary_only=True):
433 """See IHasBuildRecords."""430 """See IHasBuildRecords."""
434 return getUtility(IBinaryPackageBuildSet).getBuildsForBuilder(431 if binary_only:
435 self.id, build_state, name, arch_tag, user)432 return getUtility(IBinaryPackageBuildSet).getBuildsForBuilder(
433 self.id, build_state, name, arch_tag, user)
434 else:
435 if arch_tag is not None or name is not None:
436 raise IncompatibleArguments(
437 "The 'arch_tag' and 'name' parameters can be used only "
438 "with binary_only=True.")
439 return getUtility(IBuildFarmJobSet).getBuildsForBuilder(
440 self, status=build_state, user=user)
436441
437 def slaveStatus(self):442 def slaveStatus(self):
438 """See IBuilder."""443 """See IBuilder."""
439444
=== modified file 'lib/lp/buildmaster/model/buildfarmjob.py'
--- lib/lp/buildmaster/model/buildfarmjob.py 2010-06-21 07:32:35 +0000
+++ lib/lp/buildmaster/model/buildfarmjob.py 2010-06-21 07:32:36 +0000
@@ -16,6 +16,7 @@
1616
17import pytz17import pytz
1818
19from storm.expr import And, Coalesce, Desc, Join, LeftJoin, Select
19from storm.locals import Bool, DateTime, Int, Reference, Storm20from storm.locals import Bool, DateTime, Int, Reference, Storm
20from storm.store import Store21from storm.store import Store
2122
@@ -26,15 +27,18 @@
2627
27from canonical.database.constants import UTC_NOW28from canonical.database.constants import UTC_NOW
28from canonical.database.enumcol import DBEnum29from canonical.database.enumcol import DBEnum
29from canonical.launchpad.interfaces.lpstorm import IMasterStore30from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
31from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore
30from canonical.launchpad.webapp.interfaces import (32from canonical.launchpad.webapp.interfaces import (
31 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE)33 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE)
3234
33from lp.buildmaster.interfaces.buildbase import BuildStatus35from lp.buildmaster.interfaces.buildbase import BuildStatus
34from lp.buildmaster.interfaces.buildfarmjob import (36from lp.buildmaster.interfaces.buildfarmjob import (
35 BuildFarmJobType, IBuildFarmJob, IBuildFarmJobOld,37 BuildFarmJobType, IBuildFarmJob, IBuildFarmJobOld,
36 IBuildFarmJobSource, InconsistentBuildFarmJobError, ISpecificBuildFarmJob)38 IBuildFarmJobSet, IBuildFarmJobSource,
39 InconsistentBuildFarmJobError, ISpecificBuildFarmJob)
37from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet40from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
41from lp.registry.model.teammembership import TeamParticipation
3842
3943
40class BuildFarmJobOld:44class BuildFarmJobOld:
@@ -342,3 +346,68 @@
342class BuildFarmJobDerived:346class BuildFarmJobDerived:
343 implements(IBuildFarmJob)347 implements(IBuildFarmJob)
344 delegates(IBuildFarmJob, context='build_farm_job')348 delegates(IBuildFarmJob, context='build_farm_job')
349
350
351class BuildFarmJobSet:
352 implements(IBuildFarmJobSet)
353
354 def getBuildsForBuilder(self, builder_id, status=None, user=None):
355 """See `IBuildFarmJobSet`."""
356 # Imported here to avoid circular imports.
357 from lp.buildmaster.model.packagebuild import PackageBuild
358 from lp.soyuz.model.archive import Archive
359
360 extra_clauses = [BuildFarmJob.builder == builder_id]
361 if status is not None:
362 extra_clauses.append(BuildFarmJob.status == status)
363
364 # We need to ensure that we don't include any private builds.
365 # Currently only package builds can be private (via their
366 # related archive), but not all build farm jobs will have a
367 # related package build - hence the left join.
368 left_join_pkg_builds = LeftJoin(
369 BuildFarmJob,
370 Join(
371 PackageBuild,
372 Archive,
373 And(PackageBuild.archive == Archive.id)),
374 PackageBuild.build_farm_job == BuildFarmJob.id)
375
376 filtered_builds = IStore(BuildFarmJob).using(
377 left_join_pkg_builds).find(BuildFarmJob, *extra_clauses)
378
379 if user is None:
380 # Anonymous requests don't get to see private builds at all.
381 filtered_builds = filtered_builds.find(
382 Coalesce(Archive.private, False) == False)
383
384 elif user.inTeam(getUtility(ILaunchpadCelebrities).admin):
385 # Admins get to see everything.
386 pass
387 else:
388 # Everyone else sees a union of all public builds and the
389 # specific private builds to which they have access.
390 filtered_builds = filtered_builds.find(
391 Coalesce(Archive.private, False) == False)
392
393 user_teams_subselect = Select(
394 TeamParticipation.teamID,
395 where=And(
396 TeamParticipation.personID == user.id,
397 TeamParticipation.teamID == Archive.ownerID))
398 private_builds_for_user = IStore(BuildFarmJob).find(
399 BuildFarmJob,
400 PackageBuild.build_farm_job == BuildFarmJob.id,
401 PackageBuild.archive == Archive.id,
402 Archive.private == True,
403 Archive.ownerID.is_in(user_teams_subselect),
404 *extra_clauses)
405
406 filtered_builds = filtered_builds.union(
407 private_builds_for_user)
408
409 filtered_builds.order_by(
410 Desc(BuildFarmJob.date_finished), BuildFarmJob.id)
411
412 return filtered_builds
413
345414
=== modified file 'lib/lp/buildmaster/tests/test_buildfarmjob.py'
--- lib/lp/buildmaster/tests/test_buildfarmjob.py 2010-06-21 07:32:35 +0000
+++ lib/lp/buildmaster/tests/test_buildfarmjob.py 2010-06-21 07:32:36 +0000
@@ -15,31 +15,46 @@
15from zope.security.proxy import removeSecurityProxy15from zope.security.proxy import removeSecurityProxy
1616
17from canonical.database.sqlbase import flush_database_updates17from canonical.database.sqlbase import flush_database_updates
18from canonical.testing.layers import DatabaseFunctionalLayer18from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
19from canonical.testing.layers import (
20 DatabaseFunctionalLayer, LaunchpadFunctionalLayer)
1921
20from lp.buildmaster.interfaces.buildbase import BuildStatus22from lp.buildmaster.interfaces.buildbase import BuildStatus
21from lp.buildmaster.interfaces.buildfarmjob import (23from lp.buildmaster.interfaces.buildfarmjob import (
22 BuildFarmJobType, IBuildFarmJob, IBuildFarmJobSource,24 BuildFarmJobType, IBuildFarmJob, IBuildFarmJobSet, IBuildFarmJobSource,
23 InconsistentBuildFarmJobError)25 InconsistentBuildFarmJobError)
24from lp.buildmaster.model.buildfarmjob import BuildFarmJob26from lp.buildmaster.model.buildfarmjob import BuildFarmJob
25from lp.testing import login, TestCaseWithFactory27from lp.testing import login, TestCaseWithFactory
2628
2729
28class TestBuildFarmJobBase(TestCaseWithFactory):30class TestBuildFarmJobMixin:
2931
30 layer = DatabaseFunctionalLayer32 layer = DatabaseFunctionalLayer
3133
32 def setUp(self):34 def setUp(self):
33 """Create a build farm job with which to test."""35 """Create a build farm job with which to test."""
34 super(TestBuildFarmJobBase, self).setUp()36 super(TestBuildFarmJobMixin, self).setUp()
35 self.build_farm_job = self.makeBuildFarmJob()37 self.build_farm_job = self.makeBuildFarmJob()
3638
37 def makeBuildFarmJob(self):39 def makeBuildFarmJob(self, builder=None,
38 return getUtility(IBuildFarmJobSource).new(40 job_type=BuildFarmJobType.PACKAGEBUILD,
39 job_type=BuildFarmJobType.PACKAGEBUILD)41 status=BuildStatus.NEEDSBUILD,
4042 date_finished=None):
4143 """A factory method for creating PackageBuilds.
42class TestBuildFarmJob(TestBuildFarmJobBase):44
45 This is not included in the launchpad test factory because
46 a build farm job should never be instantiated outside the
47 context of a derived class (such as a BinaryPackageBuild
48 or eventually a SPRecipeBuild).
49 """
50 build_farm_job = getUtility(IBuildFarmJobSource).new(
51 job_type=job_type, status=status)
52 removeSecurityProxy(build_farm_job).builder = builder
53 removeSecurityProxy(build_farm_job).date_finished = date_finished
54 return build_farm_job
55
56
57class TestBuildFarmJob(TestBuildFarmJobMixin, TestCaseWithFactory):
43 """Tests for the build farm job object."""58 """Tests for the build farm job object."""
4459
45 def test_providesInterface(self):60 def test_providesInterface(self):
@@ -56,7 +71,6 @@
56 self.assertEqual(self.build_farm_job, retrieved_job)71 self.assertEqual(self.build_farm_job, retrieved_job)
5772
58 def test_default_values(self):73 def test_default_values(self):
59 # A build farm job defaults to the NEEDSBUILD status.
60 # We flush the database updates to ensure sql defaults74 # We flush the database updates to ensure sql defaults
61 # are set for various attributes.75 # are set for various attributes.
62 flush_database_updates()76 flush_database_updates()
@@ -166,7 +180,7 @@
166 InconsistentBuildFarmJobError, self.build_farm_job.getSpecificJob)180 InconsistentBuildFarmJobError, self.build_farm_job.getSpecificJob)
167181
168182
169class TestBuildFarmJobSecurity(TestBuildFarmJobBase):183class TestBuildFarmJobSecurity(TestBuildFarmJobMixin, TestCaseWithFactory):
170184
171 def test_view_build_farm_job(self):185 def test_view_build_farm_job(self):
172 # Anonymous access can read public builds, but not edit.186 # Anonymous access can read public builds, but not edit.
@@ -184,5 +198,111 @@
184 BuildStatus.FULLYBUILT, self.build_farm_job.status)198 BuildStatus.FULLYBUILT, self.build_farm_job.status)
185199
186200
201class TestBuildFarmJobSet(TestBuildFarmJobMixin, TestCaseWithFactory):
202
203 layer = LaunchpadFunctionalLayer
204
205 def setUp(self):
206 super(TestBuildFarmJobSet, self).setUp()
207 self.builder = self.factory.makeBuilder()
208 self.build_farm_job_set = getUtility(IBuildFarmJobSet)
209
210 def test_getBuildsForBuilder_all(self):
211 # The default call without arguments returns all builds for the
212 # builder, and not those for other builders.
213 build1 = self.makeBuildFarmJob(builder=self.builder)
214 build2 = self.makeBuildFarmJob(builder=self.builder)
215 self.makeBuildFarmJob(builder=self.factory.makeBuilder())
216
217 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)
218
219 self.assertContentEqual([build1, build2], result)
220
221 def test_getBuildsForBuilder_by_status(self):
222 # If the status arg is used, the results will be filtered by
223 # status.
224 successful_builds = [
225 self.makeBuildFarmJob(
226 builder=self.builder, status=BuildStatus.FULLYBUILT),
227 self.makeBuildFarmJob(
228 builder=self.builder, status=BuildStatus.FULLYBUILT),
229 ]
230 self.makeBuildFarmJob(builder=self.builder)
231
232 query_by_status = self.build_farm_job_set.getBuildsForBuilder(
233 self.builder, status=BuildStatus.FULLYBUILT)
234
235 self.assertContentEqual(successful_builds, query_by_status)
236
237 def _makePrivateAndNonPrivateBuilds(self, owning_team=None):
238 """Return a tuple of a private and non-private build farm job."""
239 if owning_team is None:
240 owning_team = self.factory.makeTeam()
241 archive = self.factory.makeArchive(owner=owning_team, private=True)
242 private_build = self.factory.makeBinaryPackageBuild(
243 archive=archive, builder=self.builder)
244 private_build = removeSecurityProxy(private_build).build_farm_job
245 other_build = self.makeBuildFarmJob(builder=self.builder)
246 return (private_build, other_build)
247
248 def test_getBuildsForBuilder_hides_private_from_anon(self):
249 # If no user is passed, all private builds are filtered out.
250 private_build, other_build = self._makePrivateAndNonPrivateBuilds()
251
252 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)
253
254 self.assertContentEqual([other_build], result)
255
256 def test_getBuildsForBuilder_hides_private_other_users(self):
257 # Private builds are not returned for users without permission
258 # to view them.
259 private_build, other_build = self._makePrivateAndNonPrivateBuilds()
260
261 result = self.build_farm_job_set.getBuildsForBuilder(
262 self.builder, user=self.factory.makePerson())
263
264 self.assertContentEqual([other_build], result)
265
266 def test_getBuildsForBuilder_shows_private_to_admin(self):
267 # Admin users can see private builds.
268 admin_team = getUtility(ILaunchpadCelebrities).admin
269 private_build, other_build = self._makePrivateAndNonPrivateBuilds()
270
271 result = self.build_farm_job_set.getBuildsForBuilder(
272 self.builder, user=admin_team.teamowner)
273
274 self.assertContentEqual([private_build, other_build], result)
275
276 def test_getBuildsForBuilder_shows_private_to_authorised(self):
277 # Similarly, if the user is in the owning team they can see it.
278 owning_team = self.factory.makeTeam()
279 private_build, other_build = self._makePrivateAndNonPrivateBuilds(
280 owning_team=owning_team)
281
282 result = self.build_farm_job_set.getBuildsForBuilder(
283 self.builder,
284 user=owning_team.teamowner)
285
286 self.assertContentEqual([private_build, other_build], result)
287
288 def test_getBuildsForBuilder_ordered_by_date_finished(self):
289 # Results are returned with the oldest build last.
290 build_1 = self.makeBuildFarmJob(
291 builder=self.builder,
292 date_finished=datetime(2008, 10, 10, tzinfo=pytz.UTC))
293 build_2 = self.makeBuildFarmJob(
294 builder=self.builder,
295 date_finished=datetime(2008, 11, 10, tzinfo=pytz.UTC))
296
297 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)
298 self.assertEqual([build_2, build_1], list(result))
299
300 removeSecurityProxy(build_2).date_finished = (
301 datetime(2008, 8, 10, tzinfo=pytz.UTC))
302 result = self.build_farm_job_set.getBuildsForBuilder(self.builder)
303
304 self.assertEqual([build_1, build_2], list(result))
305
306
187def test_suite():307def test_suite():
188 return unittest.TestLoader().loadTestsFromName(__name__)308 return unittest.TestLoader().loadTestsFromName(__name__)
189309
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2010-06-09 08:26:26 +0000
+++ lib/lp/registry/model/distribution.py 2010-06-21 07:32:36 +0000
@@ -797,11 +797,13 @@
797 raise NotFoundError(filename)797 raise NotFoundError(filename)
798798
799 def getBuildRecords(self, build_state=None, name=None, pocket=None,799 def getBuildRecords(self, build_state=None, name=None, pocket=None,
800 arch_tag=None, user=None):800 arch_tag=None, user=None, binary_only=True):
801 """See `IHasBuildRecords`"""801 """See `IHasBuildRecords`"""
802 # Ignore "user", since it would not make any difference to the802 # Ignore "user", since it would not make any difference to the
803 # records returned here (private builds are only in PPA right803 # records returned here (private builds are only in PPA right
804 # now).804 # now).
805 # The "binary_only" option is not yet supported for
806 # IDistribution.
805807
806 # Find out the distroarchseries in question.808 # Find out the distroarchseries in question.
807 arch_ids = DistroArchSeriesSet().getIdsForArchitectures(809 arch_ids = DistroArchSeriesSet().getIdsForArchitectures(
808810
=== modified file 'lib/lp/soyuz/browser/build.py'
--- lib/lp/soyuz/browser/build.py 2010-06-21 07:32:35 +0000
+++ lib/lp/soyuz/browser/build.py 2010-06-21 07:32:36 +0000
@@ -376,10 +376,16 @@
376 # build self.state & self.available_states structures376 # build self.state & self.available_states structures
377 self._setupMappedStates(state_tag)377 self._setupMappedStates(state_tag)
378378
379 # By default, we use the binary_only class attribute, but we
380 # ensure it is true if we are passed an arch tag or a name.
381 binary_only = self.binary_only
382 if self.text is not None or self.arch_tag is not None:
383 binary_only = True
384
379 # request context build records according the selected state385 # request context build records according the selected state
380 builds = self.context.getBuildRecords(386 builds = self.context.getBuildRecords(
381 build_state=self.state, name=self.text, arch_tag=self.arch_tag,387 build_state=self.state, name=self.text, arch_tag=self.arch_tag,
382 user=self.user, binary_only=self.binary_only)388 user=self.user, binary_only=binary_only)
383 self.batchnav = BatchNavigator(builds, self.request)389 self.batchnav = BatchNavigator(builds, self.request)
384 # We perform this extra step because we don't what to issue one390 # We perform this extra step because we don't what to issue one
385 # extra query to retrieve the BuildQueue for each Build (batch item)391 # extra query to retrieve the BuildQueue for each Build (batch item)
386392
=== modified file 'lib/lp/soyuz/browser/builder.py'
--- lib/lp/soyuz/browser/builder.py 2010-06-04 15:19:55 +0000
+++ lib/lp/soyuz/browser/builder.py 2010-06-21 07:32:36 +0000
@@ -265,6 +265,7 @@
265 __used_for__ = IBuilder265 __used_for__ = IBuilder
266266
267 page_title = 'Build history'267 page_title = 'Build history'
268 binary_only = False
268269
269 @property270 @property
270 def label(self):271 def label(self):
271272
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2010-06-21 07:32:35 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2010-06-21 07:32:36 +0000
@@ -29,7 +29,6 @@
29 'IArchivePublic',29 'IArchivePublic',
30 'IArchiveSet',30 'IArchiveSet',
31 'IDistributionArchive',31 'IDistributionArchive',
32 'IncompatibleArguments',
33 'InsufficientUploadRights',32 'InsufficientUploadRights',
34 'InvalidComponent',33 'InvalidComponent',
35 'InvalidPocketForPartnerArchive',34 'InvalidPocketForPartnerArchive',
@@ -90,11 +89,6 @@
90 webservice_error(400) #Bad request.89 webservice_error(400) #Bad request.
9190
9291
93class IncompatibleArguments(Exception):
94 """Raised when incompatible arguments are passed to a method."""
95 webservice_error(400) # Bad request.
96
97
98class CannotSwitchPrivacy(Exception):92class CannotSwitchPrivacy(Exception):
99 """Raised when switching the privacy of an archive that has93 """Raised when switching the privacy of an archive that has
100 publishing records."""94 publishing records."""
@@ -168,7 +162,7 @@
168 """Returned when a pocket is closed for uploads."""162 """Returned when a pocket is closed for uploads."""
169163
170 def __init__(self, distroseries, pocket):164 def __init__(self, distroseries, pocket):
171 Exception.__init__(self, 165 Exception.__init__(self,
172 "Not permitted to upload to the %s pocket in a series in the "166 "Not permitted to upload to the %s pocket in a series in the "
173 "'%s' state." % (pocket.name, distroseries.status.name))167 "'%s' state." % (pocket.name, distroseries.status.name))
174168
@@ -520,11 +514,11 @@
520 )514 )
521 @export_operation_as("checkUpload")515 @export_operation_as("checkUpload")
522 @export_read_operation()516 @export_read_operation()
523 def _checkUpload(person, distroseries, sourcepackagename, component, 517 def _checkUpload(person, distroseries, sourcepackagename, component,
524 pocket, strict_component=True):518 pocket, strict_component=True):
525 """Wrapper around checkUpload for the web service API."""519 """Wrapper around checkUpload for the web service API."""
526520
527 def checkUpload(person, distroseries, sourcepackagename, component, 521 def checkUpload(person, distroseries, sourcepackagename, component,
528 pocket, strict_component=True):522 pocket, strict_component=True):
529 """Check if 'person' upload 'suitesourcepackage' to 'archive'.523 """Check if 'person' upload 'suitesourcepackage' to 'archive'.
530524
531525
=== modified file 'lib/lp/soyuz/interfaces/buildrecords.py'
--- lib/lp/soyuz/interfaces/buildrecords.py 2010-06-21 07:32:35 +0000
+++ lib/lp/soyuz/interfaces/buildrecords.py 2010-06-21 07:32:36 +0000
@@ -12,6 +12,7 @@
1212
13__all__ = [13__all__ = [
14 'IHasBuildRecords',14 'IHasBuildRecords',
15 'IncompatibleArguments',
15 ]16 ]
1617
17from zope.interface import Interface18from zope.interface import Interface
@@ -21,7 +22,12 @@
21from canonical.launchpad import _22from canonical.launchpad import _
22from lazr.restful.declarations import (23from lazr.restful.declarations import (
23 REQUEST_USER, call_with, export_read_operation, operation_parameters,24 REQUEST_USER, call_with, export_read_operation, operation_parameters,
24 operation_returns_collection_of, rename_parameters_as)25 operation_returns_collection_of, rename_parameters_as, webservice_error)
26
27
28class IncompatibleArguments(Exception):
29 """Raised when incompatible arguments are passed to a method."""
30 webservice_error(400) # Bad request.
2531
2632
27class IHasBuildRecords(Interface):33class IHasBuildRecords(Interface):
@@ -40,7 +46,7 @@
40 description=_("The pocket into which this entry is published"),46 description=_("The pocket into which this entry is published"),
41 # Really a PackagePublishingPocket see _schema_circular_imports.47 # Really a PackagePublishingPocket see _schema_circular_imports.
42 vocabulary=DBEnumeratedType))48 vocabulary=DBEnumeratedType))
43 @call_with(user=REQUEST_USER)49 @call_with(user=REQUEST_USER, binary_only=True)
44 # Really a IBuild see _schema_circular_imports.50 # Really a IBuild see _schema_circular_imports.
45 @operation_returns_collection_of(Interface)51 @operation_returns_collection_of(Interface)
46 @export_read_operation()52 @export_read_operation()
4753
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-06-21 07:32:35 +0000
+++ lib/lp/soyuz/model/archive.py 2010-06-21 07:32:36 +0000
@@ -66,7 +66,7 @@
66 ArchiveNotPrivate, ArchivePurpose, ArchiveStatus, CannotCopy,66 ArchiveNotPrivate, ArchivePurpose, ArchiveStatus, CannotCopy,
67 CannotSwitchPrivacy, CannotUploadToPPA, CannotUploadToPocket,67 CannotSwitchPrivacy, CannotUploadToPPA, CannotUploadToPocket,
68 DistroSeriesNotFound, IArchive, IArchiveSet, IDistributionArchive,68 DistroSeriesNotFound, IArchive, IArchiveSet, IDistributionArchive,
69 IncompatibleArguments, InsufficientUploadRights, InvalidPocketForPPA,69 InsufficientUploadRights, InvalidPocketForPPA,
70 InvalidPocketForPartnerArchive, InvalidComponent, IPPA,70 InvalidPocketForPartnerArchive, InvalidComponent, IPPA,
71 MAIN_ARCHIVE_PURPOSES, NoRightsForArchive, NoRightsForComponent,71 MAIN_ARCHIVE_PURPOSES, NoRightsForArchive, NoRightsForComponent,
72 NoSuchPPA, NoTokensForTeams, PocketNotFound, VersionRequiresName,72 NoSuchPPA, NoTokensForTeams, PocketNotFound, VersionRequiresName,
@@ -79,7 +79,8 @@
79 ArchiveSubscriberStatus, IArchiveSubscriberSet, ArchiveSubscriptionError)79 ArchiveSubscriberStatus, IArchiveSubscriberSet, ArchiveSubscriptionError)
80from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFileType80from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFileType
81from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet81from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
82from lp.soyuz.interfaces.buildrecords import IHasBuildRecords82from lp.soyuz.interfaces.buildrecords import (
83 IHasBuildRecords, IncompatibleArguments)
83from lp.soyuz.interfaces.component import IComponent, IComponentSet84from lp.soyuz.interfaces.component import IComponent, IComponentSet
84from lp.registry.interfaces.distroseries import IDistroSeriesSet85from lp.registry.interfaces.distroseries import IDistroSeriesSet
85from lp.registry.interfaces.person import PersonVisibility86from lp.registry.interfaces.person import PersonVisibility
@@ -1044,7 +1045,7 @@
1044 if isinstance(sourcepackagename, basestring):1045 if isinstance(sourcepackagename, basestring):
1045 sourcepackagename = getUtility(1046 sourcepackagename = getUtility(
1046 ISourcePackageNameSet)[sourcepackagename]1047 ISourcePackageNameSet)[sourcepackagename]
1047 reason = self.checkUpload(person, distroseries, sourcepackagename, 1048 reason = self.checkUpload(person, distroseries, sourcepackagename,
1048 component, pocket, strict_component)1049 component, pocket, strict_component)
1049 if reason is not None:1050 if reason is not None:
1050 raise reason1051 raise reason
10511052
=== modified file 'lib/lp/soyuz/model/distroarchseries.py'
--- lib/lp/soyuz/model/distroarchseries.py 2010-04-09 15:46:09 +0000
+++ lib/lp/soyuz/model/distroarchseries.py 2010-06-21 07:32:36 +0000
@@ -229,11 +229,13 @@
229 self, name)229 self, name)
230230
231 def getBuildRecords(self, build_state=None, name=None, pocket=None,231 def getBuildRecords(self, build_state=None, name=None, pocket=None,
232 arch_tag=None, user=None):232 arch_tag=None, user=None, binary_only=True):
233 """See IHasBuildRecords"""233 """See IHasBuildRecords"""
234 # Ignore "user", since it would not make any difference to the234 # Ignore "user", since it would not make any difference to the
235 # records returned here (private builds are only in PPA right235 # records returned here (private builds are only in PPA right
236 # now).236 # now).
237 # Ignore "binary_only" as for a distro arch series it is only
238 # the binaries that are relevant.
237239
238 # For consistency we return an empty resultset if arch_tag240 # For consistency we return an empty resultset if arch_tag
239 # is provided but doesn't match our architecture.241 # is provided but doesn't match our architecture.
240242
=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
--- lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-21 07:32:35 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-21 07:32:36 +0000
@@ -150,8 +150,8 @@
150 # If getSpecificJob is called on the binary build it is a noop.150 # If getSpecificJob is called on the binary build it is a noop.
151 store = Store.of(self.build)151 store = Store.of(self.build)
152 store.flush()152 store.flush()
153 build, statements = record_statements(self.build.getSpecificJob)153 self.assertStatementCount(
154 self.assertEqual(0, len(statements))154 0, self.build.getSpecificJob)
155155
156156
157class TestBuildUpdateDependencies(TestCaseWithFactory):157class TestBuildUpdateDependencies(TestCaseWithFactory):
158158
=== modified file 'lib/lp/soyuz/tests/test_hasbuildrecords.py'
--- lib/lp/soyuz/tests/test_hasbuildrecords.py 2010-06-21 07:32:35 +0000
+++ lib/lp/soyuz/tests/test_hasbuildrecords.py 2010-06-21 07:32:36 +0000
@@ -12,12 +12,14 @@
1212
13from lp.registry.model.sourcepackage import SourcePackage13from lp.registry.model.sourcepackage import SourcePackage
14from lp.buildmaster.interfaces.builder import IBuilderSet14from lp.buildmaster.interfaces.builder import IBuilderSet
15from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType15from lp.buildmaster.interfaces.buildfarmjob import (
16 BuildFarmJobType, IBuildFarmJob)
16from lp.buildmaster.interfaces.packagebuild import (17from lp.buildmaster.interfaces.packagebuild import (
17 IPackageBuildSource)18 IPackageBuildSource)
18from lp.registry.interfaces.pocket import PackagePublishingPocket19from lp.registry.interfaces.pocket import PackagePublishingPocket
19from lp.soyuz.interfaces.archive import IncompatibleArguments20from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild
20from lp.soyuz.interfaces.buildrecords import IHasBuildRecords21from lp.soyuz.interfaces.buildrecords import (
22 IHasBuildRecords, IncompatibleArguments)
21from lp.soyuz.model.processor import ProcessorFamilySet23from lp.soyuz.model.processor import ProcessorFamilySet
22from lp.soyuz.tests.test_binarypackagebuild import (24from lp.soyuz.tests.test_binarypackagebuild import (
23 BaseTestCaseWithThreeBuilds)25 BaseTestCaseWithThreeBuilds)
@@ -131,6 +133,43 @@
131 for build in self.builds:133 for build in self.builds:
132 build.builder = self.context134 build.builder = self.context
133135
136 def test_binary_only_false(self):
137 # A builder can optionally return the more general
138 # build farm job objects.
139
140 # Until we have different IBuildFarmJob types implemented, we
141 # can only test this by creating a lone IBuildFarmJob of a
142 # different type.
143 from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
144 from lp.buildmaster.interfaces.buildbase import BuildStatus
145 build_farm_job = getUtility(IBuildFarmJobSource).new(
146 job_type=BuildFarmJobType.RECIPEBRANCHBUILD, virtualized=True,
147 status=BuildStatus.BUILDING)
148 removeSecurityProxy(build_farm_job).builder = self.context
149
150 builds = self.context.getBuildRecords(binary_only=True)
151 binary_only_count = builds.count()
152
153 self.assertTrue(
154 all([IBinaryPackageBuild.providedBy(build) for build in builds]))
155
156 builds = self.context.getBuildRecords(binary_only=False)
157 all_count = builds.count()
158
159 self.assertFalse(
160 any([IBinaryPackageBuild.providedBy(build) for build in builds]))
161 self.assertTrue(
162 all([IBuildFarmJob.providedBy(build) for build in builds]))
163 self.assertBetween(0, binary_only_count, all_count)
164
165 def test_incompatible_arguments(self):
166 # binary_only=False is incompatible with arch_tag and name.
167 self.failUnlessRaises(
168 IncompatibleArguments, self.context.getBuildRecords,
169 binary_only=False, arch_tag="anything")
170 self.failUnlessRaises(
171 IncompatibleArguments, self.context.getBuildRecords,
172 binary_only=False, name="anything")
134173
135class TestSourcePackageHasBuildRecords(TestHasBuildRecordsInterface):174class TestSourcePackageHasBuildRecords(TestHasBuildRecordsInterface):
136 """Test the SourcePackage implementation of IHasBuildRecords."""175 """Test the SourcePackage implementation of IHasBuildRecords."""
137176
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-06-18 08:39:22 +0000
+++ lib/lp/testing/factory.py 2010-06-21 07:32:36 +0000
@@ -2183,18 +2183,22 @@
2183 source_package_recipe_build=source_package_recipe_build)2183 source_package_recipe_build=source_package_recipe_build)
21842184
2185 def makeBinaryPackageBuild(self, source_package_release=None,2185 def makeBinaryPackageBuild(self, source_package_release=None,
2186 distroarchseries=None):2186 distroarchseries=None, archive=None, builder=None):
2187 """Create a BinaryPackageBuild.2187 """Create a BinaryPackageBuild.
21882188
2189 If supplied, the source_package_release is used to determine archive.2189 If archive is not supplied, the source_package_release is used
2190 to determine archive.
2190 :param source_package_release: The SourcePackageRelease this binary2191 :param source_package_release: The SourcePackageRelease this binary
2191 build uses as its source.2192 build uses as its source.
2192 :param distroarchseries: The DistroArchSeries to use.2193 :param distroarchseries: The DistroArchSeries to use.
2194 :param archive: The Archive to use.
2195 :param builder: An optional builder to assign.
2193 """2196 """
2194 if source_package_release is None:2197 if archive is None:
2195 archive = self.makeArchive()2198 if source_package_release is None:
2196 else:2199 archive = self.makeArchive()
2197 archive = source_package_release.upload_archive2200 else:
2201 archive = source_package_release.upload_archive
2198 if source_package_release is None:2202 if source_package_release is None:
2199 multiverse = self.makeComponent(name='multiverse')2203 multiverse = self.makeComponent(name='multiverse')
2200 source_package_release = self.makeSourcePackageRelease(2204 source_package_release = self.makeSourcePackageRelease(
@@ -2212,7 +2216,9 @@
2212 archive=archive,2216 archive=archive,
2213 pocket=PackagePublishingPocket.RELEASE,2217 pocket=PackagePublishingPocket.RELEASE,
2214 date_created=self.getUniqueDate())2218 date_created=self.getUniqueDate())
2215 binary_package_build_job = binary_package_build.makeJob()2219 naked_build = removeSecurityProxy(binary_package_build)
2220 naked_build.builder = builder
2221 binary_package_build_job = naked_build.makeJob()
2216 BuildQueue(2222 BuildQueue(
2217 job=binary_package_build_job.job,2223 job=binary_package_build_job.job,
2218 job_type=BuildFarmJobType.PACKAGEBUILD)2224 job_type=BuildFarmJobType.PACKAGEBUILD)