Merge lp:~michael.nelson/launchpad/594492-present-bfjs-in-builder-history into lp:launchpad
- 594492-present-bfjs-in-builder-history
- Merge into devel
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 |
Related bugs: |
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+
revision 11026.
Test command:
bin/test -vvm test_buildfarmjob -m test_hasbuildre
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.
which case it will return BuildFarmJob records for that builder using the new
IBuildFarmJobSe
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.
Michael Nelson (michael.nelson) wrote : | # |
1 | === modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py' |
2 | --- lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-06-15 16:17:17 +0000 |
3 | +++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-06-16 14:12:20 +0000 |
4 | @@ -294,13 +294,14 @@ |
5 | |
6 | |
7 | class IBuildFarmJobSet(Interface): |
8 | - """A utility representing a set of package builds.""" |
9 | + """A utility representing a set of build farm jobs.""" |
10 | |
11 | def getBuildsForBuilder(builder_id, status=None, user=None): |
12 | """Return `IBuildFarmJob` records touched by a builder. |
13 | |
14 | :param builder_id: The id of the builder for which to find builds. |
15 | - :param status: If status is provided, only builds with that status |
16 | - will be returned. |
17 | + :param status: If given, limit the search to builds with this status. |
18 | + :param user: If given, this will be used to determine private builds |
19 | + that should be included. |
20 | :return: a `ResultSet` representing the requested builds. |
21 | """ |
22 | |
23 | === modified file 'lib/lp/buildmaster/model/buildfarmjob.py' |
24 | --- lib/lp/buildmaster/model/buildfarmjob.py 2010-06-16 11:25:18 +0000 |
25 | +++ lib/lp/buildmaster/model/buildfarmjob.py 2010-06-16 16:01:08 +0000 |
26 | @@ -16,7 +16,7 @@ |
27 | |
28 | import pytz |
29 | |
30 | -from storm.expr import And, Desc, Join, LeftJoin, Or, Select |
31 | +from storm.expr import And, Coalesce, Desc, Join, LeftJoin, Select |
32 | from storm.locals import Bool, DateTime, Int, Reference, Storm |
33 | from storm.store import Store |
34 | |
35 | @@ -28,7 +28,7 @@ |
36 | from canonical.database.constants import UTC_NOW |
37 | from canonical.database.enumcol import DBEnum |
38 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
39 | -from canonical.launchpad.interfaces.lpstorm import IMasterStore |
40 | +from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore |
41 | from canonical.launchpad.webapp.interfaces import ( |
42 | DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE) |
43 | |
44 | @@ -353,16 +353,19 @@ |
45 | |
46 | def getBuildsForBuilder(self, builder_id, status=None, user=None): |
47 | """See `IBuildFarmJobSet`.""" |
48 | - store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
49 | # Imported here to avoid circular imports. |
50 | from lp.buildmaster.model.packagebuild import PackageBuild |
51 | from lp.soyuz.model.archive import Archive |
52 | |
53 | + extra_clauses = [BuildFarmJob.builder == builder_id] |
54 | + if status is not None: |
55 | + extra_clauses.append(BuildFarmJob.status == status) |
56 | + |
57 | # We need to ensure that we don't include any private builds. |
58 | # Currently only package builds can be private (via their |
59 | # related archive), but not all build farm jobs will have a |
60 | # related package build - hence the left join. |
61 | - bfjs_with_optional_package_builds = LeftJoin( |
62 | + left_join_pkg_builds = LeftJoin( |
63 | BuildFarmJob, |
64 | Join( |
65 | PackageBuild, |
66 | @@ -370,35 +373,41 @@ |
67 | And(PackageBuild.archive == Archive.id)), |
68 | PackageBuild.build_farm_job == BuildFarmJob.id) |
69 | |
70 | - filtered_builds = store.using(bfjs_with_optional_package_builds).find( |
71 | - BuildFarmJob, |
72 | - BuildFarmJob.builder == builder_id) |
73 | - |
74 | - if status is not None: |
75 | - filtered_builds = filtered_builds.find( |
76 | - BuildFarmJob.status == status) |
77 | - |
78 | - # Anonymous users can only see public builds. |
79 | + filtered_builds = IStore(BuildFarmJob).using( |
80 | + left_join_pkg_builds).find(BuildFarmJob, *extra_clauses) |
81 | + |
82 | if user is None: |
83 | - filtered_builds = filtered_builds.find( |
84 | - Or(Archive.private == None, Archive.private == False)) |
85 | - |
86 | - # All other non-admins can additionally see private builds for |
87 | - # which they are a member of the team owning the archive. |
88 | - elif not user.inTeam(getUtility(ILaunchpadCelebrities).admin): |
89 | + # Anonymous requests don't get to see private builds at all. |
90 | + filtered_builds = filtered_builds.find( |
91 | + Coalesce(Archive.private, False) == False) |
92 | + |
93 | + elif user.inTeam(getUtility(ILaunchpadCelebrities).admin): |
94 | + # Admins get to see everything. |
95 | + pass |
96 | + else: |
97 | + # Everyone else sees a union of all public builds and the |
98 | + # specific private builds to which they have access. |
99 | + filtered_builds = filtered_builds.find( |
100 | + Coalesce(Archive.private, False) == False) |
101 | + |
102 | user_teams_subselect = Select( |
103 | TeamParticipation.teamID, |
104 | where=And( |
105 | TeamParticipation.personID == user.id, |
106 | TeamParticipation.teamID == Archive.ownerID)) |
107 | - filtered_builds = filtered_builds.find( |
108 | - Or( |
109 | - Archive.private == None, |
110 | - Or( |
111 | - Archive.private == False, |
112 | - Archive.ownerID.is_in(user_teams_subselect)))) |
113 | - |
114 | - filtered_builds.order_by(Desc(BuildFarmJob.date_finished), BuildFarmJob.id) |
115 | + private_builds_for_user = IStore(BuildFarmJob).find( |
116 | + BuildFarmJob, |
117 | + PackageBuild.build_farm_job == BuildFarmJob.id, |
118 | + PackageBuild.archive == Archive.id, |
119 | + Archive.private == True, |
120 | + Archive.ownerID.is_in(user_teams_subselect), |
121 | + *extra_clauses) |
122 | + |
123 | + filtered_builds = filtered_builds.union( |
124 | + private_builds_for_user) |
125 | + |
126 | + filtered_builds.order_by( |
127 | + Desc(BuildFarmJob.date_finished), BuildFarmJob.id) |
128 | |
129 | return filtered_builds |
130 | |
131 | |
132 | === modified file 'lib/lp/buildmaster/tests/test_buildfarmjob.py' |
133 | --- lib/lp/buildmaster/tests/test_buildfarmjob.py 2010-06-16 11:25:18 +0000 |
134 | +++ lib/lp/buildmaster/tests/test_buildfarmjob.py 2010-06-17 08:26:34 +0000 |
135 | @@ -24,28 +24,37 @@ |
136 | BuildFarmJobType, IBuildFarmJob, IBuildFarmJobSet, IBuildFarmJobSource, |
137 | InconsistentBuildFarmJobError) |
138 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
139 | -from lp.testing import login, TestCaseWithFactory |
140 | - |
141 | - |
142 | -class TestBuildFarmJobBase(TestCaseWithFactory): |
143 | +from lp.testing import ANONYMOUS, login, login_person, TestCaseWithFactory |
144 | + |
145 | + |
146 | +class TestBuildFarmJobMixin: |
147 | |
148 | layer = DatabaseFunctionalLayer |
149 | |
150 | def setUp(self): |
151 | """Create a build farm job with which to test.""" |
152 | - super(TestBuildFarmJobBase, self).setUp() |
153 | + super(TestBuildFarmJobMixin, self).setUp() |
154 | self.build_farm_job = self.makeBuildFarmJob() |
155 | |
156 | def makeBuildFarmJob(self, builder=None, |
157 | job_type=BuildFarmJobType.PACKAGEBUILD, |
158 | - status=BuildStatus.FULLYBUILT): |
159 | + status=BuildStatus.NEEDSBUILD, |
160 | + date_finished=None): |
161 | + """A factory method for creating PackageBuilds. |
162 | + |
163 | + This is not included in the launchpad test factory because |
164 | + a build farm job should never be instantiated outside the |
165 | + context of a derived class (such as a BinaryPackageBuild |
166 | + or eventually a SPRecipeBuild). |
167 | + """ |
168 | build_farm_job = getUtility(IBuildFarmJobSource).new( |
169 | job_type=job_type, status=status) |
170 | removeSecurityProxy(build_farm_job).builder = builder |
171 | + removeSecurityProxy(build_farm_job).date_finished = date_finished |
172 | return build_farm_job |
173 | |
174 | |
175 | -class TestBuildFarmJob(TestBuildFarmJobBase): |
176 | +class TestBuildFarmJob(TestBuildFarmJobMixin, TestCaseWithFactory): |
177 | """Tests for the build farm job object.""" |
178 | |
179 | def test_providesInterface(self): |
180 | @@ -62,7 +71,6 @@ |
181 | self.assertEqual(self.build_farm_job, retrieved_job) |
182 | |
183 | def test_default_values(self): |
184 | - # A build farm job defaults to the NEEDSBUILD status. |
185 | # We flush the database updates to ensure sql defaults |
186 | # are set for various attributes. |
187 | flush_database_updates() |
188 | @@ -172,7 +180,7 @@ |
189 | InconsistentBuildFarmJobError, self.build_farm_job.getSpecificJob) |
190 | |
191 | |
192 | -class TestBuildFarmJobSecurity(TestBuildFarmJobBase): |
193 | +class TestBuildFarmJobSecurity(TestBuildFarmJobMixin, TestCaseWithFactory): |
194 | |
195 | def test_view_build_farm_job(self): |
196 | # Anonymous access can read public builds, but not edit. |
197 | @@ -190,100 +198,102 @@ |
198 | BuildStatus.FULLYBUILT, self.build_farm_job.status) |
199 | |
200 | |
201 | -class TestBuildFarmJobSet(TestBuildFarmJobBase): |
202 | +class TestBuildFarmJobSet(TestBuildFarmJobMixin, TestCaseWithFactory): |
203 | |
204 | layer = LaunchpadFunctionalLayer |
205 | |
206 | def setUp(self): |
207 | super(TestBuildFarmJobSet, self).setUp() |
208 | self.builder = self.factory.makeBuilder() |
209 | - self.build_farm_jobs = [] |
210 | - self.build_farm_jobs.append( |
211 | - self.makeBuildFarmJob(builder=self.builder)) |
212 | - self.build_farm_jobs.append(self.makeBuildFarmJob( |
213 | - builder=self.builder, |
214 | - job_type=BuildFarmJobType.RECIPEBRANCHBUILD)) |
215 | - self.build_farm_jobs.append(self.makeBuildFarmJob( |
216 | - builder=self.builder, status=BuildStatus.BUILDING)) |
217 | - |
218 | - # For good measure, create a different type of build that will |
219 | - # also have an associated PackageBuild. |
220 | - owning_team = self.factory.makeTeam() |
221 | - archive = self.factory.makeArchive(owner=owning_team) |
222 | - self.binary_package_build = self.factory.makeBinaryPackageBuild( |
223 | - archive=archive, builder=self.builder) |
224 | - self.build_farm_jobs.append(self.binary_package_build.build_farm_job) |
225 | - |
226 | self.build_farm_job_set = getUtility(IBuildFarmJobSet) |
227 | |
228 | def test_getBuildsForBuilder_all(self): |
229 | # The default call without arguments returns all builds for the |
230 | # builder, and not those for other builders. |
231 | + build1 = self.makeBuildFarmJob(builder=self.builder) |
232 | + build2 = self.makeBuildFarmJob(builder=self.builder) |
233 | self.makeBuildFarmJob(builder=self.factory.makeBuilder()) |
234 | - self.assertContentEqual( |
235 | - self.build_farm_jobs, self.build_farm_job_set.getBuildsForBuilder( |
236 | - self.builder)) |
237 | + result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
238 | + |
239 | + self.assertContentEqual([build1, build2], result) |
240 | |
241 | def test_getBuildsForBuilder_by_status(self): |
242 | # If the status arg is used, the results will be filtered by |
243 | # status. |
244 | - self.assertContentEqual( |
245 | - self.build_farm_jobs[:2], |
246 | - self.build_farm_job_set.getBuildsForBuilder( |
247 | - self.builder, status=BuildStatus.FULLYBUILT)) |
248 | - |
249 | - def makeBuildPrivate(self, build): |
250 | - """Helper to privatise a package build.""" |
251 | - removeSecurityProxy(build.archive).buildd_secret = "blah" |
252 | - removeSecurityProxy(build.archive).private = True |
253 | + successful_builds = [ |
254 | + self.makeBuildFarmJob( |
255 | + builder=self.builder, status=BuildStatus.FULLYBUILT), |
256 | + self.makeBuildFarmJob( |
257 | + builder=self.builder, status=BuildStatus.FULLYBUILT), |
258 | + ] |
259 | + self.makeBuildFarmJob(builder=self.builder) |
260 | + |
261 | + query_by_status = self.build_farm_job_set.getBuildsForBuilder( |
262 | + self.builder, status=BuildStatus.FULLYBUILT) |
263 | + |
264 | + self.assertContentEqual(successful_builds, query_by_status) |
265 | + |
266 | + def _makePrivateAndNonPrivateBuilds(self, owning_team=None): |
267 | + """Return a tuple of a private and non-private build farm job.""" |
268 | + if owning_team is None: |
269 | + owning_team = self.factory.makeTeam() |
270 | + archive = self.factory.makeArchive(owner=owning_team, private=True) |
271 | + login_person(owning_team.teamowner) |
272 | + private_build = self.factory.makeBinaryPackageBuild( |
273 | + archive=archive, builder=self.builder) |
274 | + private_build = private_build.build_farm_job |
275 | + login(ANONYMOUS) |
276 | + other_build = self.makeBuildFarmJob(builder=self.builder) |
277 | + return (private_build, other_build) |
278 | |
279 | def test_getBuildsForBuilder_hides_private_from_anon(self): |
280 | # If no user is passed, all private builds are filtered out. |
281 | - |
282 | - # When private it is not included for anon requests. |
283 | - self.makeBuildPrivate(self.binary_package_build) |
284 | + private_build, other_build = self._makePrivateAndNonPrivateBuilds() |
285 | result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
286 | - self.assertTrue( |
287 | - self.binary_package_build.build_farm_job not in result) |
288 | + self.assertContentEqual([other_build], result) |
289 | |
290 | def test_getBuildsForBuilder_hides_private_other_users(self): |
291 | # Private builds are not returned for users without permission |
292 | # to view them. |
293 | - self.makeBuildPrivate(self.binary_package_build) |
294 | + private_build, other_build = self._makePrivateAndNonPrivateBuilds() |
295 | result = self.build_farm_job_set.getBuildsForBuilder( |
296 | self.builder, user=self.factory.makePerson()) |
297 | - self.assertTrue( |
298 | - self.binary_package_build.build_farm_job not in result) |
299 | + self.assertContentEqual([other_build], result) |
300 | |
301 | def test_getBuildsForBuilder_shows_private_to_admin(self): |
302 | # Admin users can see private builds. |
303 | admin_team = getUtility(ILaunchpadCelebrities).admin |
304 | - self.makeBuildPrivate(self.binary_package_build) |
305 | + private_build, other_build = self._makePrivateAndNonPrivateBuilds() |
306 | result = self.build_farm_job_set.getBuildsForBuilder( |
307 | self.builder, user=admin_team.teamowner) |
308 | - self.assertTrue(self.binary_package_build.build_farm_job in result) |
309 | + self.assertContentEqual([private_build, other_build], result) |
310 | |
311 | def test_getBuildsForBuilder_shows_private_to_authorised(self): |
312 | # Similarly, if the user is in the owning team they can see it. |
313 | - self.makeBuildPrivate(self.binary_package_build) |
314 | + owning_team = self.factory.makeTeam() |
315 | + private_build, other_build = self._makePrivateAndNonPrivateBuilds( |
316 | + owning_team=owning_team) |
317 | result = self.build_farm_job_set.getBuildsForBuilder( |
318 | self.builder, |
319 | - user=self.binary_package_build.archive.owner.teamowner) |
320 | - self.assertTrue(self.binary_package_build.build_farm_job in result) |
321 | + user=owning_team.teamowner) |
322 | + self.assertContentEqual([private_build, other_build], result) |
323 | |
324 | def test_getBuildsForBuilder_ordered_by_date_finished(self): |
325 | # Results are returned with the oldest build last. |
326 | - naked_build_0 = removeSecurityProxy(self.build_farm_jobs[0]) |
327 | - naked_build_1 = removeSecurityProxy(self.build_farm_jobs[1]) |
328 | - |
329 | - naked_build_0.date_finished = datetime(2008, 10, 10, tzinfo=pytz.UTC) |
330 | - naked_build_1.date_finished = datetime(2008, 11, 10, tzinfo=pytz.UTC) |
331 | - result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
332 | - self.assertEqual(self.build_farm_jobs[0], result[3]) |
333 | - |
334 | - naked_build_0.date_finished = datetime(2008, 12, 10, tzinfo=pytz.UTC) |
335 | - result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
336 | - self.assertEqual(self.build_farm_jobs[1], result[3]) |
337 | + build_1 = self.makeBuildFarmJob( |
338 | + builder=self.builder, |
339 | + date_finished=datetime(2008, 10, 10, tzinfo=pytz.UTC)) |
340 | + build_2 = self.makeBuildFarmJob( |
341 | + builder=self.builder, |
342 | + date_finished=datetime(2008, 11, 10, tzinfo=pytz.UTC)) |
343 | + |
344 | + result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
345 | + self.assertEqual([build_2, build_1], list(result)) |
346 | + |
347 | + removeSecurityProxy(build_2).date_finished = ( |
348 | + datetime(2008, 8, 10, tzinfo=pytz.UTC)) |
349 | + result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
350 | + self.assertEqual([build_1, build_2], list(result)) |
351 | |
352 | |
353 | def test_suite(): |
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 removeSecurityP
* In test_binary_
Great shape now. Land that baby!
Quod subigo farinam,
Jeroen
Preview Diff
1 | === modified file 'lib/lp/buildmaster/configure.zcml' |
2 | --- lib/lp/buildmaster/configure.zcml 2010-06-21 07:32:35 +0000 |
3 | +++ lib/lp/buildmaster/configure.zcml 2010-06-21 07:32:36 +0000 |
4 | @@ -58,6 +58,12 @@ |
5 | <allow |
6 | interface="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJobSource" /> |
7 | </securedutility> |
8 | + <securedutility |
9 | + class="lp.buildmaster.model.buildfarmjob.BuildFarmJobSet" |
10 | + provides="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJobSet"> |
11 | + <allow |
12 | + interface="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJobSet" /> |
13 | + </securedutility> |
14 | |
15 | <!-- PackageBuild --> |
16 | <class |
17 | |
18 | === modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py' |
19 | --- lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-06-21 07:32:35 +0000 |
20 | +++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-06-21 07:32:36 +0000 |
21 | @@ -10,6 +10,7 @@ |
22 | __all__ = [ |
23 | 'IBuildFarmJob', |
24 | 'IBuildFarmJobOld', |
25 | + 'IBuildFarmJobSet', |
26 | 'IBuildFarmJobSource', |
27 | 'InconsistentBuildFarmJobError', |
28 | 'ISpecificBuildFarmJob', |
29 | @@ -290,3 +291,17 @@ |
30 | :param virtualized: An optional boolean indicating whether |
31 | this job should be run virtualized. |
32 | """ |
33 | + |
34 | + |
35 | +class IBuildFarmJobSet(Interface): |
36 | + """A utility representing a set of build farm jobs.""" |
37 | + |
38 | + def getBuildsForBuilder(builder_id, status=None, user=None): |
39 | + """Return `IBuildFarmJob` records touched by a builder. |
40 | + |
41 | + :param builder_id: The id of the builder for which to find builds. |
42 | + :param status: If given, limit the search to builds with this status. |
43 | + :param user: If given, this will be used to determine private builds |
44 | + that should be included. |
45 | + :return: a `ResultSet` representing the requested builds. |
46 | + """ |
47 | |
48 | === modified file 'lib/lp/buildmaster/model/builder.py' |
49 | --- lib/lp/buildmaster/model/builder.py 2010-06-21 07:32:35 +0000 |
50 | +++ lib/lp/buildmaster/model/builder.py 2010-06-21 07:32:36 +0000 |
51 | @@ -45,6 +45,7 @@ |
52 | BuildDaemonError, BuildSlaveFailure, CannotBuild, CannotFetchFile, |
53 | CannotResumeHost, CorruptBuildCookie, IBuilder, IBuilderSet, |
54 | ProtocolVersionMismatch) |
55 | +from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSet |
56 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
57 | BuildBehaviorMismatch) |
58 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
59 | @@ -59,7 +60,8 @@ |
60 | # These dependencies on soyuz will be removed when getBuildRecords() |
61 | # is moved. |
62 | from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet |
63 | -from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
64 | +from lp.soyuz.interfaces.buildrecords import ( |
65 | + IHasBuildRecords, IncompatibleArguments) |
66 | from lp.soyuz.model.buildpackagejob import BuildPackageJob |
67 | |
68 | |
69 | @@ -423,16 +425,19 @@ |
70 | self.builderok = False |
71 | self.failnotes = reason |
72 | |
73 | - # XXX Michael Nelson 20091202 bug=491330. The current UI assumes |
74 | - # that the builder history will display binary build records, as |
75 | - # returned by getBuildRecords() below. See the bug for a discussion |
76 | - # of the options. |
77 | - |
78 | def getBuildRecords(self, build_state=None, name=None, arch_tag=None, |
79 | user=None, binary_only=True): |
80 | """See IHasBuildRecords.""" |
81 | - return getUtility(IBinaryPackageBuildSet).getBuildsForBuilder( |
82 | - self.id, build_state, name, arch_tag, user) |
83 | + if binary_only: |
84 | + return getUtility(IBinaryPackageBuildSet).getBuildsForBuilder( |
85 | + self.id, build_state, name, arch_tag, user) |
86 | + else: |
87 | + if arch_tag is not None or name is not None: |
88 | + raise IncompatibleArguments( |
89 | + "The 'arch_tag' and 'name' parameters can be used only " |
90 | + "with binary_only=True.") |
91 | + return getUtility(IBuildFarmJobSet).getBuildsForBuilder( |
92 | + self, status=build_state, user=user) |
93 | |
94 | def slaveStatus(self): |
95 | """See IBuilder.""" |
96 | |
97 | === modified file 'lib/lp/buildmaster/model/buildfarmjob.py' |
98 | --- lib/lp/buildmaster/model/buildfarmjob.py 2010-06-21 07:32:35 +0000 |
99 | +++ lib/lp/buildmaster/model/buildfarmjob.py 2010-06-21 07:32:36 +0000 |
100 | @@ -16,6 +16,7 @@ |
101 | |
102 | import pytz |
103 | |
104 | +from storm.expr import And, Coalesce, Desc, Join, LeftJoin, Select |
105 | from storm.locals import Bool, DateTime, Int, Reference, Storm |
106 | from storm.store import Store |
107 | |
108 | @@ -26,15 +27,18 @@ |
109 | |
110 | from canonical.database.constants import UTC_NOW |
111 | from canonical.database.enumcol import DBEnum |
112 | -from canonical.launchpad.interfaces.lpstorm import IMasterStore |
113 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
114 | +from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore |
115 | from canonical.launchpad.webapp.interfaces import ( |
116 | DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE) |
117 | |
118 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
119 | from lp.buildmaster.interfaces.buildfarmjob import ( |
120 | BuildFarmJobType, IBuildFarmJob, IBuildFarmJobOld, |
121 | - IBuildFarmJobSource, InconsistentBuildFarmJobError, ISpecificBuildFarmJob) |
122 | + IBuildFarmJobSet, IBuildFarmJobSource, |
123 | + InconsistentBuildFarmJobError, ISpecificBuildFarmJob) |
124 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
125 | +from lp.registry.model.teammembership import TeamParticipation |
126 | |
127 | |
128 | class BuildFarmJobOld: |
129 | @@ -342,3 +346,68 @@ |
130 | class BuildFarmJobDerived: |
131 | implements(IBuildFarmJob) |
132 | delegates(IBuildFarmJob, context='build_farm_job') |
133 | + |
134 | + |
135 | +class BuildFarmJobSet: |
136 | + implements(IBuildFarmJobSet) |
137 | + |
138 | + def getBuildsForBuilder(self, builder_id, status=None, user=None): |
139 | + """See `IBuildFarmJobSet`.""" |
140 | + # Imported here to avoid circular imports. |
141 | + from lp.buildmaster.model.packagebuild import PackageBuild |
142 | + from lp.soyuz.model.archive import Archive |
143 | + |
144 | + extra_clauses = [BuildFarmJob.builder == builder_id] |
145 | + if status is not None: |
146 | + extra_clauses.append(BuildFarmJob.status == status) |
147 | + |
148 | + # We need to ensure that we don't include any private builds. |
149 | + # Currently only package builds can be private (via their |
150 | + # related archive), but not all build farm jobs will have a |
151 | + # related package build - hence the left join. |
152 | + left_join_pkg_builds = LeftJoin( |
153 | + BuildFarmJob, |
154 | + Join( |
155 | + PackageBuild, |
156 | + Archive, |
157 | + And(PackageBuild.archive == Archive.id)), |
158 | + PackageBuild.build_farm_job == BuildFarmJob.id) |
159 | + |
160 | + filtered_builds = IStore(BuildFarmJob).using( |
161 | + left_join_pkg_builds).find(BuildFarmJob, *extra_clauses) |
162 | + |
163 | + if user is None: |
164 | + # Anonymous requests don't get to see private builds at all. |
165 | + filtered_builds = filtered_builds.find( |
166 | + Coalesce(Archive.private, False) == False) |
167 | + |
168 | + elif user.inTeam(getUtility(ILaunchpadCelebrities).admin): |
169 | + # Admins get to see everything. |
170 | + pass |
171 | + else: |
172 | + # Everyone else sees a union of all public builds and the |
173 | + # specific private builds to which they have access. |
174 | + filtered_builds = filtered_builds.find( |
175 | + Coalesce(Archive.private, False) == False) |
176 | + |
177 | + user_teams_subselect = Select( |
178 | + TeamParticipation.teamID, |
179 | + where=And( |
180 | + TeamParticipation.personID == user.id, |
181 | + TeamParticipation.teamID == Archive.ownerID)) |
182 | + private_builds_for_user = IStore(BuildFarmJob).find( |
183 | + BuildFarmJob, |
184 | + PackageBuild.build_farm_job == BuildFarmJob.id, |
185 | + PackageBuild.archive == Archive.id, |
186 | + Archive.private == True, |
187 | + Archive.ownerID.is_in(user_teams_subselect), |
188 | + *extra_clauses) |
189 | + |
190 | + filtered_builds = filtered_builds.union( |
191 | + private_builds_for_user) |
192 | + |
193 | + filtered_builds.order_by( |
194 | + Desc(BuildFarmJob.date_finished), BuildFarmJob.id) |
195 | + |
196 | + return filtered_builds |
197 | + |
198 | |
199 | === modified file 'lib/lp/buildmaster/tests/test_buildfarmjob.py' |
200 | --- lib/lp/buildmaster/tests/test_buildfarmjob.py 2010-06-21 07:32:35 +0000 |
201 | +++ lib/lp/buildmaster/tests/test_buildfarmjob.py 2010-06-21 07:32:36 +0000 |
202 | @@ -15,31 +15,46 @@ |
203 | from zope.security.proxy import removeSecurityProxy |
204 | |
205 | from canonical.database.sqlbase import flush_database_updates |
206 | -from canonical.testing.layers import DatabaseFunctionalLayer |
207 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
208 | +from canonical.testing.layers import ( |
209 | + DatabaseFunctionalLayer, LaunchpadFunctionalLayer) |
210 | |
211 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
212 | from lp.buildmaster.interfaces.buildfarmjob import ( |
213 | - BuildFarmJobType, IBuildFarmJob, IBuildFarmJobSource, |
214 | + BuildFarmJobType, IBuildFarmJob, IBuildFarmJobSet, IBuildFarmJobSource, |
215 | InconsistentBuildFarmJobError) |
216 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
217 | from lp.testing import login, TestCaseWithFactory |
218 | |
219 | |
220 | -class TestBuildFarmJobBase(TestCaseWithFactory): |
221 | +class TestBuildFarmJobMixin: |
222 | |
223 | layer = DatabaseFunctionalLayer |
224 | |
225 | def setUp(self): |
226 | """Create a build farm job with which to test.""" |
227 | - super(TestBuildFarmJobBase, self).setUp() |
228 | + super(TestBuildFarmJobMixin, self).setUp() |
229 | self.build_farm_job = self.makeBuildFarmJob() |
230 | |
231 | - def makeBuildFarmJob(self): |
232 | - return getUtility(IBuildFarmJobSource).new( |
233 | - job_type=BuildFarmJobType.PACKAGEBUILD) |
234 | - |
235 | - |
236 | -class TestBuildFarmJob(TestBuildFarmJobBase): |
237 | + def makeBuildFarmJob(self, builder=None, |
238 | + job_type=BuildFarmJobType.PACKAGEBUILD, |
239 | + status=BuildStatus.NEEDSBUILD, |
240 | + date_finished=None): |
241 | + """A factory method for creating PackageBuilds. |
242 | + |
243 | + This is not included in the launchpad test factory because |
244 | + a build farm job should never be instantiated outside the |
245 | + context of a derived class (such as a BinaryPackageBuild |
246 | + or eventually a SPRecipeBuild). |
247 | + """ |
248 | + build_farm_job = getUtility(IBuildFarmJobSource).new( |
249 | + job_type=job_type, status=status) |
250 | + removeSecurityProxy(build_farm_job).builder = builder |
251 | + removeSecurityProxy(build_farm_job).date_finished = date_finished |
252 | + return build_farm_job |
253 | + |
254 | + |
255 | +class TestBuildFarmJob(TestBuildFarmJobMixin, TestCaseWithFactory): |
256 | """Tests for the build farm job object.""" |
257 | |
258 | def test_providesInterface(self): |
259 | @@ -56,7 +71,6 @@ |
260 | self.assertEqual(self.build_farm_job, retrieved_job) |
261 | |
262 | def test_default_values(self): |
263 | - # A build farm job defaults to the NEEDSBUILD status. |
264 | # We flush the database updates to ensure sql defaults |
265 | # are set for various attributes. |
266 | flush_database_updates() |
267 | @@ -166,7 +180,7 @@ |
268 | InconsistentBuildFarmJobError, self.build_farm_job.getSpecificJob) |
269 | |
270 | |
271 | -class TestBuildFarmJobSecurity(TestBuildFarmJobBase): |
272 | +class TestBuildFarmJobSecurity(TestBuildFarmJobMixin, TestCaseWithFactory): |
273 | |
274 | def test_view_build_farm_job(self): |
275 | # Anonymous access can read public builds, but not edit. |
276 | @@ -184,5 +198,111 @@ |
277 | BuildStatus.FULLYBUILT, self.build_farm_job.status) |
278 | |
279 | |
280 | +class TestBuildFarmJobSet(TestBuildFarmJobMixin, TestCaseWithFactory): |
281 | + |
282 | + layer = LaunchpadFunctionalLayer |
283 | + |
284 | + def setUp(self): |
285 | + super(TestBuildFarmJobSet, self).setUp() |
286 | + self.builder = self.factory.makeBuilder() |
287 | + self.build_farm_job_set = getUtility(IBuildFarmJobSet) |
288 | + |
289 | + def test_getBuildsForBuilder_all(self): |
290 | + # The default call without arguments returns all builds for the |
291 | + # builder, and not those for other builders. |
292 | + build1 = self.makeBuildFarmJob(builder=self.builder) |
293 | + build2 = self.makeBuildFarmJob(builder=self.builder) |
294 | + self.makeBuildFarmJob(builder=self.factory.makeBuilder()) |
295 | + |
296 | + result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
297 | + |
298 | + self.assertContentEqual([build1, build2], result) |
299 | + |
300 | + def test_getBuildsForBuilder_by_status(self): |
301 | + # If the status arg is used, the results will be filtered by |
302 | + # status. |
303 | + successful_builds = [ |
304 | + self.makeBuildFarmJob( |
305 | + builder=self.builder, status=BuildStatus.FULLYBUILT), |
306 | + self.makeBuildFarmJob( |
307 | + builder=self.builder, status=BuildStatus.FULLYBUILT), |
308 | + ] |
309 | + self.makeBuildFarmJob(builder=self.builder) |
310 | + |
311 | + query_by_status = self.build_farm_job_set.getBuildsForBuilder( |
312 | + self.builder, status=BuildStatus.FULLYBUILT) |
313 | + |
314 | + self.assertContentEqual(successful_builds, query_by_status) |
315 | + |
316 | + def _makePrivateAndNonPrivateBuilds(self, owning_team=None): |
317 | + """Return a tuple of a private and non-private build farm job.""" |
318 | + if owning_team is None: |
319 | + owning_team = self.factory.makeTeam() |
320 | + archive = self.factory.makeArchive(owner=owning_team, private=True) |
321 | + private_build = self.factory.makeBinaryPackageBuild( |
322 | + archive=archive, builder=self.builder) |
323 | + private_build = removeSecurityProxy(private_build).build_farm_job |
324 | + other_build = self.makeBuildFarmJob(builder=self.builder) |
325 | + return (private_build, other_build) |
326 | + |
327 | + def test_getBuildsForBuilder_hides_private_from_anon(self): |
328 | + # If no user is passed, all private builds are filtered out. |
329 | + private_build, other_build = self._makePrivateAndNonPrivateBuilds() |
330 | + |
331 | + result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
332 | + |
333 | + self.assertContentEqual([other_build], result) |
334 | + |
335 | + def test_getBuildsForBuilder_hides_private_other_users(self): |
336 | + # Private builds are not returned for users without permission |
337 | + # to view them. |
338 | + private_build, other_build = self._makePrivateAndNonPrivateBuilds() |
339 | + |
340 | + result = self.build_farm_job_set.getBuildsForBuilder( |
341 | + self.builder, user=self.factory.makePerson()) |
342 | + |
343 | + self.assertContentEqual([other_build], result) |
344 | + |
345 | + def test_getBuildsForBuilder_shows_private_to_admin(self): |
346 | + # Admin users can see private builds. |
347 | + admin_team = getUtility(ILaunchpadCelebrities).admin |
348 | + private_build, other_build = self._makePrivateAndNonPrivateBuilds() |
349 | + |
350 | + result = self.build_farm_job_set.getBuildsForBuilder( |
351 | + self.builder, user=admin_team.teamowner) |
352 | + |
353 | + self.assertContentEqual([private_build, other_build], result) |
354 | + |
355 | + def test_getBuildsForBuilder_shows_private_to_authorised(self): |
356 | + # Similarly, if the user is in the owning team they can see it. |
357 | + owning_team = self.factory.makeTeam() |
358 | + private_build, other_build = self._makePrivateAndNonPrivateBuilds( |
359 | + owning_team=owning_team) |
360 | + |
361 | + result = self.build_farm_job_set.getBuildsForBuilder( |
362 | + self.builder, |
363 | + user=owning_team.teamowner) |
364 | + |
365 | + self.assertContentEqual([private_build, other_build], result) |
366 | + |
367 | + def test_getBuildsForBuilder_ordered_by_date_finished(self): |
368 | + # Results are returned with the oldest build last. |
369 | + build_1 = self.makeBuildFarmJob( |
370 | + builder=self.builder, |
371 | + date_finished=datetime(2008, 10, 10, tzinfo=pytz.UTC)) |
372 | + build_2 = self.makeBuildFarmJob( |
373 | + builder=self.builder, |
374 | + date_finished=datetime(2008, 11, 10, tzinfo=pytz.UTC)) |
375 | + |
376 | + result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
377 | + self.assertEqual([build_2, build_1], list(result)) |
378 | + |
379 | + removeSecurityProxy(build_2).date_finished = ( |
380 | + datetime(2008, 8, 10, tzinfo=pytz.UTC)) |
381 | + result = self.build_farm_job_set.getBuildsForBuilder(self.builder) |
382 | + |
383 | + self.assertEqual([build_1, build_2], list(result)) |
384 | + |
385 | + |
386 | def test_suite(): |
387 | return unittest.TestLoader().loadTestsFromName(__name__) |
388 | |
389 | === modified file 'lib/lp/registry/model/distribution.py' |
390 | --- lib/lp/registry/model/distribution.py 2010-06-09 08:26:26 +0000 |
391 | +++ lib/lp/registry/model/distribution.py 2010-06-21 07:32:36 +0000 |
392 | @@ -797,11 +797,13 @@ |
393 | raise NotFoundError(filename) |
394 | |
395 | def getBuildRecords(self, build_state=None, name=None, pocket=None, |
396 | - arch_tag=None, user=None): |
397 | + arch_tag=None, user=None, binary_only=True): |
398 | """See `IHasBuildRecords`""" |
399 | # Ignore "user", since it would not make any difference to the |
400 | # records returned here (private builds are only in PPA right |
401 | # now). |
402 | + # The "binary_only" option is not yet supported for |
403 | + # IDistribution. |
404 | |
405 | # Find out the distroarchseries in question. |
406 | arch_ids = DistroArchSeriesSet().getIdsForArchitectures( |
407 | |
408 | === modified file 'lib/lp/soyuz/browser/build.py' |
409 | --- lib/lp/soyuz/browser/build.py 2010-06-21 07:32:35 +0000 |
410 | +++ lib/lp/soyuz/browser/build.py 2010-06-21 07:32:36 +0000 |
411 | @@ -376,10 +376,16 @@ |
412 | # build self.state & self.available_states structures |
413 | self._setupMappedStates(state_tag) |
414 | |
415 | + # By default, we use the binary_only class attribute, but we |
416 | + # ensure it is true if we are passed an arch tag or a name. |
417 | + binary_only = self.binary_only |
418 | + if self.text is not None or self.arch_tag is not None: |
419 | + binary_only = True |
420 | + |
421 | # request context build records according the selected state |
422 | builds = self.context.getBuildRecords( |
423 | build_state=self.state, name=self.text, arch_tag=self.arch_tag, |
424 | - user=self.user, binary_only=self.binary_only) |
425 | + user=self.user, binary_only=binary_only) |
426 | self.batchnav = BatchNavigator(builds, self.request) |
427 | # We perform this extra step because we don't what to issue one |
428 | # extra query to retrieve the BuildQueue for each Build (batch item) |
429 | |
430 | === modified file 'lib/lp/soyuz/browser/builder.py' |
431 | --- lib/lp/soyuz/browser/builder.py 2010-06-04 15:19:55 +0000 |
432 | +++ lib/lp/soyuz/browser/builder.py 2010-06-21 07:32:36 +0000 |
433 | @@ -265,6 +265,7 @@ |
434 | __used_for__ = IBuilder |
435 | |
436 | page_title = 'Build history' |
437 | + binary_only = False |
438 | |
439 | @property |
440 | def label(self): |
441 | |
442 | === modified file 'lib/lp/soyuz/interfaces/archive.py' |
443 | --- lib/lp/soyuz/interfaces/archive.py 2010-06-21 07:32:35 +0000 |
444 | +++ lib/lp/soyuz/interfaces/archive.py 2010-06-21 07:32:36 +0000 |
445 | @@ -29,7 +29,6 @@ |
446 | 'IArchivePublic', |
447 | 'IArchiveSet', |
448 | 'IDistributionArchive', |
449 | - 'IncompatibleArguments', |
450 | 'InsufficientUploadRights', |
451 | 'InvalidComponent', |
452 | 'InvalidPocketForPartnerArchive', |
453 | @@ -90,11 +89,6 @@ |
454 | webservice_error(400) #Bad request. |
455 | |
456 | |
457 | -class IncompatibleArguments(Exception): |
458 | - """Raised when incompatible arguments are passed to a method.""" |
459 | - webservice_error(400) # Bad request. |
460 | - |
461 | - |
462 | class CannotSwitchPrivacy(Exception): |
463 | """Raised when switching the privacy of an archive that has |
464 | publishing records.""" |
465 | @@ -168,7 +162,7 @@ |
466 | """Returned when a pocket is closed for uploads.""" |
467 | |
468 | def __init__(self, distroseries, pocket): |
469 | - Exception.__init__(self, |
470 | + Exception.__init__(self, |
471 | "Not permitted to upload to the %s pocket in a series in the " |
472 | "'%s' state." % (pocket.name, distroseries.status.name)) |
473 | |
474 | @@ -520,11 +514,11 @@ |
475 | ) |
476 | @export_operation_as("checkUpload") |
477 | @export_read_operation() |
478 | - def _checkUpload(person, distroseries, sourcepackagename, component, |
479 | + def _checkUpload(person, distroseries, sourcepackagename, component, |
480 | pocket, strict_component=True): |
481 | """Wrapper around checkUpload for the web service API.""" |
482 | |
483 | - def checkUpload(person, distroseries, sourcepackagename, component, |
484 | + def checkUpload(person, distroseries, sourcepackagename, component, |
485 | pocket, strict_component=True): |
486 | """Check if 'person' upload 'suitesourcepackage' to 'archive'. |
487 | |
488 | |
489 | === modified file 'lib/lp/soyuz/interfaces/buildrecords.py' |
490 | --- lib/lp/soyuz/interfaces/buildrecords.py 2010-06-21 07:32:35 +0000 |
491 | +++ lib/lp/soyuz/interfaces/buildrecords.py 2010-06-21 07:32:36 +0000 |
492 | @@ -12,6 +12,7 @@ |
493 | |
494 | __all__ = [ |
495 | 'IHasBuildRecords', |
496 | + 'IncompatibleArguments', |
497 | ] |
498 | |
499 | from zope.interface import Interface |
500 | @@ -21,7 +22,12 @@ |
501 | from canonical.launchpad import _ |
502 | from lazr.restful.declarations import ( |
503 | REQUEST_USER, call_with, export_read_operation, operation_parameters, |
504 | - operation_returns_collection_of, rename_parameters_as) |
505 | + operation_returns_collection_of, rename_parameters_as, webservice_error) |
506 | + |
507 | + |
508 | +class IncompatibleArguments(Exception): |
509 | + """Raised when incompatible arguments are passed to a method.""" |
510 | + webservice_error(400) # Bad request. |
511 | |
512 | |
513 | class IHasBuildRecords(Interface): |
514 | @@ -40,7 +46,7 @@ |
515 | description=_("The pocket into which this entry is published"), |
516 | # Really a PackagePublishingPocket see _schema_circular_imports. |
517 | vocabulary=DBEnumeratedType)) |
518 | - @call_with(user=REQUEST_USER) |
519 | + @call_with(user=REQUEST_USER, binary_only=True) |
520 | # Really a IBuild see _schema_circular_imports. |
521 | @operation_returns_collection_of(Interface) |
522 | @export_read_operation() |
523 | |
524 | === modified file 'lib/lp/soyuz/model/archive.py' |
525 | --- lib/lp/soyuz/model/archive.py 2010-06-21 07:32:35 +0000 |
526 | +++ lib/lp/soyuz/model/archive.py 2010-06-21 07:32:36 +0000 |
527 | @@ -66,7 +66,7 @@ |
528 | ArchiveNotPrivate, ArchivePurpose, ArchiveStatus, CannotCopy, |
529 | CannotSwitchPrivacy, CannotUploadToPPA, CannotUploadToPocket, |
530 | DistroSeriesNotFound, IArchive, IArchiveSet, IDistributionArchive, |
531 | - IncompatibleArguments, InsufficientUploadRights, InvalidPocketForPPA, |
532 | + InsufficientUploadRights, InvalidPocketForPPA, |
533 | InvalidPocketForPartnerArchive, InvalidComponent, IPPA, |
534 | MAIN_ARCHIVE_PURPOSES, NoRightsForArchive, NoRightsForComponent, |
535 | NoSuchPPA, NoTokensForTeams, PocketNotFound, VersionRequiresName, |
536 | @@ -79,7 +79,8 @@ |
537 | ArchiveSubscriberStatus, IArchiveSubscriberSet, ArchiveSubscriptionError) |
538 | from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFileType |
539 | from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet |
540 | -from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
541 | +from lp.soyuz.interfaces.buildrecords import ( |
542 | + IHasBuildRecords, IncompatibleArguments) |
543 | from lp.soyuz.interfaces.component import IComponent, IComponentSet |
544 | from lp.registry.interfaces.distroseries import IDistroSeriesSet |
545 | from lp.registry.interfaces.person import PersonVisibility |
546 | @@ -1044,7 +1045,7 @@ |
547 | if isinstance(sourcepackagename, basestring): |
548 | sourcepackagename = getUtility( |
549 | ISourcePackageNameSet)[sourcepackagename] |
550 | - reason = self.checkUpload(person, distroseries, sourcepackagename, |
551 | + reason = self.checkUpload(person, distroseries, sourcepackagename, |
552 | component, pocket, strict_component) |
553 | if reason is not None: |
554 | raise reason |
555 | |
556 | === modified file 'lib/lp/soyuz/model/distroarchseries.py' |
557 | --- lib/lp/soyuz/model/distroarchseries.py 2010-04-09 15:46:09 +0000 |
558 | +++ lib/lp/soyuz/model/distroarchseries.py 2010-06-21 07:32:36 +0000 |
559 | @@ -229,11 +229,13 @@ |
560 | self, name) |
561 | |
562 | def getBuildRecords(self, build_state=None, name=None, pocket=None, |
563 | - arch_tag=None, user=None): |
564 | + arch_tag=None, user=None, binary_only=True): |
565 | """See IHasBuildRecords""" |
566 | # Ignore "user", since it would not make any difference to the |
567 | # records returned here (private builds are only in PPA right |
568 | # now). |
569 | + # Ignore "binary_only" as for a distro arch series it is only |
570 | + # the binaries that are relevant. |
571 | |
572 | # For consistency we return an empty resultset if arch_tag |
573 | # is provided but doesn't match our architecture. |
574 | |
575 | === modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py' |
576 | --- lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-21 07:32:35 +0000 |
577 | +++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-21 07:32:36 +0000 |
578 | @@ -150,8 +150,8 @@ |
579 | # If getSpecificJob is called on the binary build it is a noop. |
580 | store = Store.of(self.build) |
581 | store.flush() |
582 | - build, statements = record_statements(self.build.getSpecificJob) |
583 | - self.assertEqual(0, len(statements)) |
584 | + self.assertStatementCount( |
585 | + 0, self.build.getSpecificJob) |
586 | |
587 | |
588 | class TestBuildUpdateDependencies(TestCaseWithFactory): |
589 | |
590 | === modified file 'lib/lp/soyuz/tests/test_hasbuildrecords.py' |
591 | --- lib/lp/soyuz/tests/test_hasbuildrecords.py 2010-06-21 07:32:35 +0000 |
592 | +++ lib/lp/soyuz/tests/test_hasbuildrecords.py 2010-06-21 07:32:36 +0000 |
593 | @@ -12,12 +12,14 @@ |
594 | |
595 | from lp.registry.model.sourcepackage import SourcePackage |
596 | from lp.buildmaster.interfaces.builder import IBuilderSet |
597 | -from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType |
598 | +from lp.buildmaster.interfaces.buildfarmjob import ( |
599 | + BuildFarmJobType, IBuildFarmJob) |
600 | from lp.buildmaster.interfaces.packagebuild import ( |
601 | IPackageBuildSource) |
602 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
603 | -from lp.soyuz.interfaces.archive import IncompatibleArguments |
604 | -from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
605 | +from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild |
606 | +from lp.soyuz.interfaces.buildrecords import ( |
607 | + IHasBuildRecords, IncompatibleArguments) |
608 | from lp.soyuz.model.processor import ProcessorFamilySet |
609 | from lp.soyuz.tests.test_binarypackagebuild import ( |
610 | BaseTestCaseWithThreeBuilds) |
611 | @@ -131,6 +133,43 @@ |
612 | for build in self.builds: |
613 | build.builder = self.context |
614 | |
615 | + def test_binary_only_false(self): |
616 | + # A builder can optionally return the more general |
617 | + # build farm job objects. |
618 | + |
619 | + # Until we have different IBuildFarmJob types implemented, we |
620 | + # can only test this by creating a lone IBuildFarmJob of a |
621 | + # different type. |
622 | + from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource |
623 | + from lp.buildmaster.interfaces.buildbase import BuildStatus |
624 | + build_farm_job = getUtility(IBuildFarmJobSource).new( |
625 | + job_type=BuildFarmJobType.RECIPEBRANCHBUILD, virtualized=True, |
626 | + status=BuildStatus.BUILDING) |
627 | + removeSecurityProxy(build_farm_job).builder = self.context |
628 | + |
629 | + builds = self.context.getBuildRecords(binary_only=True) |
630 | + binary_only_count = builds.count() |
631 | + |
632 | + self.assertTrue( |
633 | + all([IBinaryPackageBuild.providedBy(build) for build in builds])) |
634 | + |
635 | + builds = self.context.getBuildRecords(binary_only=False) |
636 | + all_count = builds.count() |
637 | + |
638 | + self.assertFalse( |
639 | + any([IBinaryPackageBuild.providedBy(build) for build in builds])) |
640 | + self.assertTrue( |
641 | + all([IBuildFarmJob.providedBy(build) for build in builds])) |
642 | + self.assertBetween(0, binary_only_count, all_count) |
643 | + |
644 | + def test_incompatible_arguments(self): |
645 | + # binary_only=False is incompatible with arch_tag and name. |
646 | + self.failUnlessRaises( |
647 | + IncompatibleArguments, self.context.getBuildRecords, |
648 | + binary_only=False, arch_tag="anything") |
649 | + self.failUnlessRaises( |
650 | + IncompatibleArguments, self.context.getBuildRecords, |
651 | + binary_only=False, name="anything") |
652 | |
653 | class TestSourcePackageHasBuildRecords(TestHasBuildRecordsInterface): |
654 | """Test the SourcePackage implementation of IHasBuildRecords.""" |
655 | |
656 | === modified file 'lib/lp/testing/factory.py' |
657 | --- lib/lp/testing/factory.py 2010-06-18 08:39:22 +0000 |
658 | +++ lib/lp/testing/factory.py 2010-06-21 07:32:36 +0000 |
659 | @@ -2183,18 +2183,22 @@ |
660 | source_package_recipe_build=source_package_recipe_build) |
661 | |
662 | def makeBinaryPackageBuild(self, source_package_release=None, |
663 | - distroarchseries=None): |
664 | + distroarchseries=None, archive=None, builder=None): |
665 | """Create a BinaryPackageBuild. |
666 | |
667 | - If supplied, the source_package_release is used to determine archive. |
668 | + If archive is not supplied, the source_package_release is used |
669 | + to determine archive. |
670 | :param source_package_release: The SourcePackageRelease this binary |
671 | build uses as its source. |
672 | :param distroarchseries: The DistroArchSeries to use. |
673 | + :param archive: The Archive to use. |
674 | + :param builder: An optional builder to assign. |
675 | """ |
676 | - if source_package_release is None: |
677 | - archive = self.makeArchive() |
678 | - else: |
679 | - archive = source_package_release.upload_archive |
680 | + if archive is None: |
681 | + if source_package_release is None: |
682 | + archive = self.makeArchive() |
683 | + else: |
684 | + archive = source_package_release.upload_archive |
685 | if source_package_release is None: |
686 | multiverse = self.makeComponent(name='multiverse') |
687 | source_package_release = self.makeSourcePackageRelease( |
688 | @@ -2212,7 +2216,9 @@ |
689 | archive=archive, |
690 | pocket=PackagePublishingPocket.RELEASE, |
691 | date_created=self.getUniqueDate()) |
692 | - binary_package_build_job = binary_package_build.makeJob() |
693 | + naked_build = removeSecurityProxy(binary_package_build) |
694 | + naked_build.builder = builder |
695 | + binary_package_build_job = naked_build.makeJob() |
696 | BuildQueue( |
697 | job=binary_package_build_job.job, |
698 | job_type=BuildFarmJobType.PACKAGEBUILD) |
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!