Code review comment for lp:~michael.nelson/launchpad/594492-present-bfjs-in-builder-history
- 594492-present-bfjs-in-builder-history
- Merge into devel
Revision history for this message
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(): |
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!