Merge lp:~wgrant/launchpad/slimmer-bfjo into lp:launchpad
- slimmer-bfjo
- Merge into devel
Proposed by
William Grant
Status: | Merged |
---|---|
Approved by: | Steve Kowalik |
Approved revision: | no longer in the source branch. |
Merged at revision: | 16823 |
Proposed branch: | lp:~wgrant/launchpad/slimmer-bfjo |
Merge into: | lp:launchpad |
Diff against target: |
938 lines (+248/-279) 16 files modified
lib/lp/buildmaster/interfaces/buildfarmjob.py (+36/-58) lib/lp/buildmaster/model/builder.py (+6/-6) lib/lp/buildmaster/model/buildfarmjob.py (+14/-26) lib/lp/buildmaster/model/buildqueue.py (+35/-6) lib/lp/buildmaster/model/packagebuild.py (+2/-3) lib/lp/buildmaster/tests/test_buildqueue.py (+4/-2) lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+2/-0) lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py (+4/-0) lib/lp/code/model/sourcepackagerecipebuild.py (+4/-10) lib/lp/code/model/tests/test_recipebuilder.py (+2/-3) lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+6/-0) lib/lp/soyuz/model/binarypackagebuild.py (+82/-3) lib/lp/soyuz/model/buildpackagejob.py (+0/-87) lib/lp/soyuz/tests/test_binarypackagebuild.py (+45/-1) lib/lp/soyuz/tests/test_buildpackagejob.py (+0/-72) lib/lp/translations/model/translationtemplatesbuild.py (+6/-2) |
To merge this branch: | bzr merge lp:~wgrant/launchpad/slimmer-bfjo |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Steve Kowalik (community) | code | Approve | |
Review via email: mp+194767@code.launchpad.net |
Commit message
Eliminate most of IBuildFarmJobOld.
Description of the change
Inline IBuildFarmJobOl
IBuildFarmJobOld will shortly perish.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py' |
2 | --- lib/lp/buildmaster/interfaces/buildfarmjob.py 2013-09-02 12:45:50 +0000 |
3 | +++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2013-11-11 23:47:32 +0000 |
4 | @@ -59,67 +59,9 @@ |
5 | BuildQueue have been transitioned to the new database schema. |
6 | """ |
7 | |
8 | - processor = Reference( |
9 | - IProcessor, title=_("Processor"), required=False, readonly=True, |
10 | - description=_( |
11 | - "The Processor required by this build farm job. " |
12 | - "This should be None for processor-independent job types.")) |
13 | - |
14 | - virtualized = Bool( |
15 | - title=_('Virtualized'), required=False, readonly=True, |
16 | - description=_( |
17 | - "The virtualization setting required by this build farm job. " |
18 | - "This should be None for job types that do not care whether " |
19 | - "they run virtualized.")) |
20 | - |
21 | def score(): |
22 | """Calculate a job score appropriate for the job type in question.""" |
23 | |
24 | - def jobStarted(): |
25 | - """'Job started' life cycle event, handle as appropriate.""" |
26 | - |
27 | - def jobReset(): |
28 | - """'Job reset' life cycle event, handle as appropriate.""" |
29 | - |
30 | - def jobCancel(): |
31 | - """'Job cancel' life cycle event.""" |
32 | - |
33 | - def addCandidateSelectionCriteria(processor, virtualized): |
34 | - """Provide a sub-query to refine the candidate job selection. |
35 | - |
36 | - Return a sub-query to narrow down the list of candidate jobs. |
37 | - The sub-query will become part of an "outer query" and is free to |
38 | - refer to the `BuildQueue` and `Job` tables already utilized in the |
39 | - latter. |
40 | - |
41 | - Example (please see the `BuildPackageJob` implementation for a |
42 | - complete example): |
43 | - |
44 | - SELECT TRUE |
45 | - FROM Archive, Build, BuildPackageJob, DistroArchSeries |
46 | - WHERE |
47 | - BuildPackageJob.job = Job.id AND |
48 | - .. |
49 | - |
50 | - :param processor: the type of processor that the candidate jobs are |
51 | - expected to run on. |
52 | - :param virtualized: whether the candidate jobs are expected to run on |
53 | - the `processor` natively or inside a virtual machine. |
54 | - :return: a string containing a sub-query that narrows down the list of |
55 | - candidate jobs. |
56 | - """ |
57 | - |
58 | - def postprocessCandidate(job, logger): |
59 | - """True if the candidate job is fine and should be dispatched |
60 | - to a builder, False otherwise. |
61 | - |
62 | - :param job: The `BuildQueue` instance to be scrutinized. |
63 | - :param logger: The logger to use. |
64 | - |
65 | - :return: True if the candidate job should be dispatched |
66 | - to a builder, False otherwise. |
67 | - """ |
68 | - |
69 | def getByJob(job): |
70 | """Get the specific `IBuildFarmJob` for the given `Job`. |
71 | |
72 | @@ -328,6 +270,42 @@ |
73 | job. |
74 | """ |
75 | |
76 | + def addCandidateSelectionCriteria(processor, virtualized): |
77 | + """Provide a sub-query to refine the candidate job selection. |
78 | + |
79 | + Return a sub-query to narrow down the list of candidate jobs. |
80 | + The sub-query will become part of an "outer query" and is free to |
81 | + refer to the `BuildQueue` and `Job` tables already utilized in the |
82 | + latter. |
83 | + |
84 | + Example (please see the `BuildPackageJob` implementation for a |
85 | + complete example): |
86 | + |
87 | + SELECT TRUE |
88 | + FROM Archive, Build, BuildPackageJob, DistroArchSeries |
89 | + WHERE |
90 | + BuildPackageJob.job = Job.id AND |
91 | + .. |
92 | + |
93 | + :param processor: the type of processor that the candidate jobs are |
94 | + expected to run on. |
95 | + :param virtualized: whether the candidate jobs are expected to run on |
96 | + the `processor` natively or inside a virtual machine. |
97 | + :return: a string containing a sub-query that narrows down the list of |
98 | + candidate jobs. |
99 | + """ |
100 | + |
101 | + def postprocessCandidate(job, logger): |
102 | + """True if the candidate job is fine and should be dispatched |
103 | + to a builder, False otherwise. |
104 | + |
105 | + :param job: The `BuildQueue` instance to be scrutinized. |
106 | + :param logger: The logger to use. |
107 | + |
108 | + :return: True if the candidate job should be dispatched |
109 | + to a builder, False otherwise. |
110 | + """ |
111 | + |
112 | |
113 | class IBuildFarmJobSource(Interface): |
114 | """A utility of BuildFarmJob used to create _things_.""" |
115 | |
116 | === modified file 'lib/lp/buildmaster/model/builder.py' |
117 | --- lib/lp/buildmaster/model/builder.py 2013-10-26 11:00:53 +0000 |
118 | +++ lib/lp/buildmaster/model/builder.py 2013-11-11 23:47:32 +0000 |
119 | @@ -35,7 +35,7 @@ |
120 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
121 | from lp.buildmaster.model.buildqueue import ( |
122 | BuildQueue, |
123 | - specific_job_classes, |
124 | + specific_build_farm_job_sources, |
125 | ) |
126 | from lp.registry.interfaces.person import validate_public_person |
127 | from lp.services.database.interfaces import ( |
128 | @@ -206,9 +206,9 @@ |
129 | order_clause = " ORDER BY buildqueue.lastscore DESC, buildqueue.id" |
130 | |
131 | extra_queries = [] |
132 | - job_classes = specific_job_classes() |
133 | - for job_type, job_class in job_classes.iteritems(): |
134 | - query = job_class.addCandidateSelectionCriteria( |
135 | + job_sources = specific_build_farm_job_sources() |
136 | + for job_type, job_source in job_sources.iteritems(): |
137 | + query = job_source.addCandidateSelectionCriteria( |
138 | self.processor, self.virtualized) |
139 | if query == '': |
140 | # This job class does not need to refine candidate jobs |
141 | @@ -224,8 +224,8 @@ |
142 | |
143 | for (candidate_id,) in candidate_jobs: |
144 | candidate = getUtility(IBuildQueueSet).get(candidate_id) |
145 | - job_class = job_classes[candidate.job_type] |
146 | - candidate_approved = job_class.postprocessCandidate( |
147 | + job_source = job_sources[candidate.job_type] |
148 | + candidate_approved = job_source.postprocessCandidate( |
149 | candidate, logger) |
150 | if candidate_approved: |
151 | return candidate |
152 | |
153 | === modified file 'lib/lp/buildmaster/model/buildfarmjob.py' |
154 | --- lib/lp/buildmaster/model/buildfarmjob.py 2013-09-02 12:45:50 +0000 |
155 | +++ lib/lp/buildmaster/model/buildfarmjob.py 2013-11-11 23:47:32 +0000 |
156 | @@ -6,6 +6,7 @@ |
157 | 'BuildFarmJob', |
158 | 'BuildFarmJobMixin', |
159 | 'BuildFarmJobOld', |
160 | + 'SpecificBuildFarmJobSourceMixin', |
161 | ] |
162 | |
163 | import datetime |
164 | @@ -50,9 +51,6 @@ |
165 | |
166 | implements(IBuildFarmJobOld) |
167 | |
168 | - processor = None |
169 | - virtualized = None |
170 | - |
171 | @staticmethod |
172 | def preloadBuildFarmJobs(jobs): |
173 | """Preload the build farm jobs to which the given jobs will delegate. |
174 | @@ -83,29 +81,6 @@ |
175 | """ |
176 | Store.of(self).remove(self) |
177 | |
178 | - @staticmethod |
179 | - def addCandidateSelectionCriteria(processor, virtualized): |
180 | - """See `IBuildFarmJobOld`.""" |
181 | - return ('') |
182 | - |
183 | - @staticmethod |
184 | - def postprocessCandidate(job, logger): |
185 | - """See `IBuildFarmJobOld`.""" |
186 | - return True |
187 | - |
188 | - def jobStarted(self): |
189 | - """See `IBuildFarmJobOld`.""" |
190 | - # XXX wgrant: builder should be set here. |
191 | - self.build.updateStatus(BuildStatus.BUILDING) |
192 | - |
193 | - def jobReset(self): |
194 | - """See `IBuildFarmJob`.""" |
195 | - self.build.updateStatus(BuildStatus.NEEDSBUILD) |
196 | - |
197 | - def jobCancel(self): |
198 | - """See `IBuildFarmJob`.""" |
199 | - self.build.updateStatus(BuildStatus.CANCELLED) |
200 | - |
201 | |
202 | class BuildFarmJob(Storm): |
203 | """A base implementation for `IBuildFarmJob` classes.""" |
204 | @@ -249,6 +224,19 @@ |
205 | self.failure_count += 1 |
206 | |
207 | |
208 | +class SpecificBuildFarmJobSourceMixin: |
209 | + |
210 | + @staticmethod |
211 | + def addCandidateSelectionCriteria(processor, virtualized): |
212 | + """See `ISpecificBuildFarmJobSource`.""" |
213 | + return ('') |
214 | + |
215 | + @staticmethod |
216 | + def postprocessCandidate(job, logger): |
217 | + """See `ISpecificBuildFarmJobSource`.""" |
218 | + return True |
219 | + |
220 | + |
221 | class BuildFarmJobSet: |
222 | implements(IBuildFarmJobSet) |
223 | |
224 | |
225 | === modified file 'lib/lp/buildmaster/model/buildqueue.py' |
226 | --- lib/lp/buildmaster/model/buildqueue.py 2013-11-10 23:56:44 +0000 |
227 | +++ lib/lp/buildmaster/model/buildqueue.py 2013-11-11 23:47:32 +0000 |
228 | @@ -7,6 +7,7 @@ |
229 | 'BuildQueue', |
230 | 'BuildQueueSet', |
231 | 'specific_job_classes', |
232 | + 'specific_build_farm_job_sources', |
233 | ] |
234 | |
235 | from datetime import datetime |
236 | @@ -21,11 +22,18 @@ |
237 | IntervalCol, |
238 | StringCol, |
239 | ) |
240 | +from storm.store import Store |
241 | from zope.component import getSiteManager |
242 | from zope.interface import implements |
243 | |
244 | -from lp.buildmaster.enums import BuildFarmJobType |
245 | -from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob |
246 | +from lp.buildmaster.enums import ( |
247 | + BuildFarmJobType, |
248 | + BuildStatus, |
249 | + ) |
250 | +from lp.buildmaster.interfaces.buildfarmjob import ( |
251 | + IBuildFarmJob, |
252 | + ISpecificBuildFarmJobSource, |
253 | + ) |
254 | from lp.buildmaster.interfaces.buildqueue import ( |
255 | IBuildQueue, |
256 | IBuildQueueSet, |
257 | @@ -58,6 +66,26 @@ |
258 | return job_classes |
259 | |
260 | |
261 | +def specific_build_farm_job_sources(): |
262 | + """Sources for specific jobs that may run on the build farm.""" |
263 | + job_sources = dict() |
264 | + # Get all components that implement the `ISpecificBuildFarmJobSource` |
265 | + # interface. |
266 | + components = getSiteManager() |
267 | + implementations = sorted( |
268 | + components.getUtilitiesFor(ISpecificBuildFarmJobSource)) |
269 | + # The above yields a collection of 2-tuples where the first element |
270 | + # is the name of the `BuildFarmJobType` enum and the second element |
271 | + # is the implementing class respectively. |
272 | + for job_enum_name, job_source in implementations: |
273 | + if not job_enum_name: |
274 | + continue |
275 | + job_enum = getattr(BuildFarmJobType, job_enum_name) |
276 | + job_sources[job_enum] = job_source |
277 | + |
278 | + return job_sources |
279 | + |
280 | + |
281 | class BuildQueue(SQLBase): |
282 | implements(IBuildQueue) |
283 | _table = "BuildQueue" |
284 | @@ -133,8 +161,9 @@ |
285 | job = self.job |
286 | specific_job = self.specific_job |
287 | builder = self.builder |
288 | - SQLBase.destroySelf(self) |
289 | + Store.of(self).remove(self) |
290 | specific_job.cleanUp() |
291 | + Store.of(self).flush() |
292 | job.destroySelf() |
293 | if builder is not None: |
294 | del get_property_cache(builder).currentjob |
295 | @@ -158,7 +187,7 @@ |
296 | self.builder = builder |
297 | if self.job.status != JobStatus.RUNNING: |
298 | self.job.start() |
299 | - self.specific_job.jobStarted() |
300 | + self.specific_job.build.updateStatus(BuildStatus.BUILDING) |
301 | if builder is not None: |
302 | del get_property_cache(builder).currentjob |
303 | |
304 | @@ -171,13 +200,13 @@ |
305 | self.job.date_started = None |
306 | self.job.date_finished = None |
307 | self.logtail = None |
308 | - self.specific_job.jobReset() |
309 | + self.specific_job.build.updateStatus(BuildStatus.NEEDSBUILD) |
310 | if builder is not None: |
311 | del get_property_cache(builder).currentjob |
312 | |
313 | def cancel(self): |
314 | """See `IBuildQueue`.""" |
315 | - self.specific_job.jobCancel() |
316 | + self.specific_job.build.updateStatus(BuildStatus.CANCELLED) |
317 | self.destroySelf() |
318 | |
319 | def getEstimatedJobStartTime(self, now=None): |
320 | |
321 | === modified file 'lib/lp/buildmaster/model/packagebuild.py' |
322 | --- lib/lp/buildmaster/model/packagebuild.py 2013-08-28 04:40:32 +0000 |
323 | +++ lib/lp/buildmaster/model/packagebuild.py 2013-11-11 23:47:32 +0000 |
324 | @@ -120,11 +120,10 @@ |
325 | |
326 | duration_estimate = self.estimateDuration() |
327 | job = specific_job.job |
328 | - processor = specific_job.processor |
329 | queue_entry = BuildQueue( |
330 | estimated_duration=duration_estimate, |
331 | job_type=self.job_type, |
332 | - job=job, processor=processor, |
333 | - virtualized=specific_job.virtualized) |
334 | + job=job, processor=self.processor, |
335 | + virtualized=self.virtualized) |
336 | Store.of(self).add(queue_entry) |
337 | return queue_entry |
338 | |
339 | === modified file 'lib/lp/buildmaster/tests/test_buildqueue.py' |
340 | --- lib/lp/buildmaster/tests/test_buildqueue.py 2013-10-31 06:29:13 +0000 |
341 | +++ lib/lp/buildmaster/tests/test_buildqueue.py 2013-11-11 23:47:32 +0000 |
342 | @@ -14,9 +14,11 @@ |
343 | BuildStatus, |
344 | ) |
345 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob |
346 | -from lp.buildmaster.model.builder import specific_job_classes |
347 | from lp.buildmaster.model.buildfarmjob import BuildFarmJobMixin |
348 | -from lp.buildmaster.model.buildqueue import BuildQueue |
349 | +from lp.buildmaster.model.buildqueue import ( |
350 | + BuildQueue, |
351 | + specific_job_classes, |
352 | + ) |
353 | from lp.services.database.interfaces import IStore |
354 | from lp.soyuz.enums import ( |
355 | ArchivePurpose, |
356 | |
357 | === modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py' |
358 | --- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2013-09-11 06:05:44 +0000 |
359 | +++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2013-11-11 23:47:32 +0000 |
360 | @@ -1614,6 +1614,8 @@ |
361 | owner=self.user, name=u'my-recipe') |
362 | distro_series = self.factory.makeDistroSeries( |
363 | name='squirrel', distribution=archive.distribution) |
364 | + removeSecurityProxy(distro_series).nominatedarchindep = ( |
365 | + self.factory.makeDistroArchSeries(distroseries=distro_series)) |
366 | build = self.factory.makeSourcePackageRecipeBuild( |
367 | requester=self.user, archive=archive, recipe=recipe, |
368 | distroseries=distro_series) |
369 | |
370 | === modified file 'lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py' |
371 | --- lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py 2013-01-23 10:16:18 +0000 |
372 | +++ lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py 2013-11-11 23:47:32 +0000 |
373 | @@ -60,6 +60,8 @@ |
374 | pantry_owner = self.factory.makePerson(name='archiveowner') |
375 | pantry = self.factory.makeArchive(name='ppa', owner=pantry_owner) |
376 | secret = self.factory.makeDistroSeries(name=u'distroseries') |
377 | + removeSecurityProxy(secret).nominatedarchindep = ( |
378 | + self.factory.makeDistroArchSeries(distroseries=secret)) |
379 | build = self.factory.makeSourcePackageRecipeBuild( |
380 | recipe=cake, distroseries=secret, archive=pantry, |
381 | status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=5)) |
382 | @@ -95,6 +97,8 @@ |
383 | pantry_owner = self.factory.makePerson(name='archiveowner') |
384 | pantry = self.factory.makeArchive(name='ppa', owner=pantry_owner) |
385 | secret = self.factory.makeDistroSeries(name=u'distroseries') |
386 | + removeSecurityProxy(secret).nominatedarchindep = ( |
387 | + self.factory.makeDistroArchSeries(distroseries=secret)) |
388 | build = self.factory.makeSourcePackageRecipeBuild( |
389 | recipe=cake, distroseries=secret, archive=pantry, |
390 | status=BuildStatus.SUPERSEDED) |
391 | |
392 | === modified file 'lib/lp/code/model/sourcepackagerecipebuild.py' |
393 | --- lib/lp/code/model/sourcepackagerecipebuild.py 2013-09-24 05:45:06 +0000 |
394 | +++ lib/lp/code/model/sourcepackagerecipebuild.py 2013-11-11 23:47:32 +0000 |
395 | @@ -43,6 +43,7 @@ |
396 | from lp.buildmaster.model.buildfarmjob import ( |
397 | BuildFarmJob, |
398 | BuildFarmJobOld, |
399 | + SpecificBuildFarmJobSourceMixin, |
400 | ) |
401 | from lp.buildmaster.model.buildqueue import BuildQueue |
402 | from lp.buildmaster.model.packagebuild import PackageBuildMixin |
403 | @@ -78,7 +79,8 @@ |
404 | from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease |
405 | |
406 | |
407 | -class SourcePackageRecipeBuild(PackageBuildMixin, Storm): |
408 | +class SourcePackageRecipeBuild(SpecificBuildFarmJobSourceMixin, |
409 | + PackageBuildMixin, Storm): |
410 | |
411 | __storm_table__ = 'SourcePackageRecipeBuild' |
412 | |
413 | @@ -212,6 +214,7 @@ |
414 | self.archive = archive |
415 | self.pocket = pocket |
416 | self.status = BuildStatus.NEEDSBUILD |
417 | + self.processor = self.distroseries.nominatedarchindep.processor |
418 | self.virtualized = True |
419 | if date_created is not None: |
420 | self.date_created = date_created |
421 | @@ -431,15 +434,6 @@ |
422 | build = Reference( |
423 | build_id, 'SourcePackageRecipeBuild.id') |
424 | |
425 | - @property |
426 | - def processor(self): |
427 | - return self.build.distroseries.nominatedarchindep.processor |
428 | - |
429 | - @property |
430 | - def virtualized(self): |
431 | - """See `IBuildFarmJob`.""" |
432 | - return self.build.is_virtualized |
433 | - |
434 | def __init__(self, build, job): |
435 | self.build = build |
436 | self.job = job |
437 | |
438 | === modified file 'lib/lp/code/model/tests/test_recipebuilder.py' |
439 | --- lib/lp/code/model/tests/test_recipebuilder.py 2013-10-01 00:32:26 +0000 |
440 | +++ lib/lp/code/model/tests/test_recipebuilder.py 2013-11-11 23:47:32 +0000 |
441 | @@ -9,8 +9,6 @@ |
442 | import tempfile |
443 | from textwrap import dedent |
444 | |
445 | -from zope.component import getUtility |
446 | - |
447 | from testtools import run_test_with |
448 | from testtools.deferredruntest import ( |
449 | assert_fails_with, |
450 | @@ -20,6 +18,7 @@ |
451 | import transaction |
452 | from twisted.internet import defer |
453 | from twisted.trial.unittest import TestCase as TrialTestCase |
454 | +from zope.component import getUtility |
455 | from zope.security.proxy import removeSecurityProxy |
456 | |
457 | from lp.buildmaster.enums import ( |
458 | @@ -72,7 +71,7 @@ |
459 | distroseries = self.factory.makeDistroSeries(name="mydistro", |
460 | distribution=distro) |
461 | processor = getUtility(IProcessorSet).getByName('386') |
462 | - distroseries.newArch( |
463 | + distroseries.nominatedarchindep = distroseries.newArch( |
464 | 'i386', processor, True, self.factory.makePerson()) |
465 | sourcepackage = self.factory.makeSourcePackage(spn, distroseries) |
466 | if recipe_registrant is None: |
467 | |
468 | === modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py' |
469 | --- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2013-09-24 05:45:06 +0000 |
470 | +++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2013-11-11 23:47:32 +0000 |
471 | @@ -423,6 +423,8 @@ |
472 | requester = self.factory.makePerson() |
473 | recipe = self.factory.makeSourcePackageRecipe() |
474 | series = self.factory.makeDistroSeries() |
475 | + removeSecurityProxy(series).nominatedarchindep = ( |
476 | + self.factory.makeDistroArchSeries(distroseries=series)) |
477 | now = self.factory.getUniqueDate() |
478 | build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe, |
479 | requester=requester) |
480 | @@ -526,6 +528,8 @@ |
481 | name=u'recipe', owner=person) |
482 | pantry = self.factory.makeArchive(name='ppa') |
483 | secret = self.factory.makeDistroSeries(name=u'distroseries') |
484 | + secret.nominatedarchindep = self.factory.makeDistroArchSeries( |
485 | + distroseries=secret) |
486 | build = self.factory.makeSourcePackageRecipeBuild( |
487 | recipe=cake, distroseries=secret, archive=pantry) |
488 | build.updateStatus(BuildStatus.FULLYBUILT) |
489 | @@ -556,6 +560,8 @@ |
490 | name=u'recipe', owner=person) |
491 | pantry = self.factory.makeArchive(name='ppa') |
492 | secret = self.factory.makeDistroSeries(name=u'distroseries') |
493 | + secret.nominatedarchindep = self.factory.makeDistroArchSeries( |
494 | + distroseries=secret) |
495 | build = self.factory.makeSourcePackageRecipeBuild( |
496 | recipe=cake, distroseries=secret, archive=pantry) |
497 | build.updateStatus(BuildStatus.FULLYBUILT) |
498 | |
499 | === modified file 'lib/lp/soyuz/model/binarypackagebuild.py' |
500 | --- lib/lp/soyuz/model/binarypackagebuild.py 2013-08-28 12:03:57 +0000 |
501 | +++ lib/lp/soyuz/model/binarypackagebuild.py 2013-11-11 23:47:32 +0000 |
502 | @@ -44,12 +44,16 @@ |
503 | ) |
504 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource |
505 | from lp.buildmaster.model.builder import Builder |
506 | -from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
507 | +from lp.buildmaster.model.buildfarmjob import ( |
508 | + BuildFarmJob, |
509 | + SpecificBuildFarmJobSourceMixin, |
510 | + ) |
511 | from lp.buildmaster.model.buildqueue import BuildQueue |
512 | from lp.buildmaster.model.packagebuild import PackageBuildMixin |
513 | from lp.registry.interfaces.distribution import IDistribution |
514 | from lp.registry.interfaces.distroseries import IDistroSeries |
515 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
516 | +from lp.registry.interfaces.series import SeriesStatus |
517 | from lp.registry.model.sourcepackagename import SourcePackageName |
518 | from lp.services.config import config |
519 | from lp.services.database.bulk import load_related |
520 | @@ -75,7 +79,10 @@ |
521 | simple_sendmail, |
522 | ) |
523 | from lp.services.webapp import canonical_url |
524 | -from lp.soyuz.enums import ArchivePurpose |
525 | +from lp.soyuz.enums import ( |
526 | + ArchivePurpose, |
527 | + PackagePublishingStatus, |
528 | + ) |
529 | from lp.soyuz.interfaces.binarypackagebuild import ( |
530 | BuildSetStatus, |
531 | CannotBeRescored, |
532 | @@ -825,7 +832,7 @@ |
533 | return changes.signer |
534 | |
535 | |
536 | -class BinaryPackageBuildSet: |
537 | +class BinaryPackageBuildSet(SpecificBuildFarmJobSourceMixin): |
538 | implements(IBinaryPackageBuildSet) |
539 | |
540 | def new(self, distro_arch_series, source_package_release, processor, |
541 | @@ -1204,3 +1211,75 @@ |
542 | return IStore(BinaryPackageBuild).using(*origin).find( |
543 | (BuildQueue, Builder, BuildPackageJob), |
544 | BinaryPackageBuild.id.is_in(build_ids)) |
545 | + |
546 | + @staticmethod |
547 | + def addCandidateSelectionCriteria(processor, virtualized): |
548 | + """See `ISpecificBuildFarmJobSource`.""" |
549 | + private_statuses = ( |
550 | + PackagePublishingStatus.PUBLISHED, |
551 | + PackagePublishingStatus.SUPERSEDED, |
552 | + PackagePublishingStatus.DELETED, |
553 | + ) |
554 | + return """ |
555 | + SELECT TRUE FROM Archive, BinaryPackageBuild, BuildPackageJob, |
556 | + DistroArchSeries |
557 | + WHERE |
558 | + BuildPackageJob.job = Job.id AND |
559 | + BuildPackageJob.build = BinaryPackageBuild.id AND |
560 | + BinaryPackageBuild.distro_arch_series = |
561 | + DistroArchSeries.id AND |
562 | + BinaryPackageBuild.archive = Archive.id AND |
563 | + ((Archive.private IS TRUE AND |
564 | + EXISTS ( |
565 | + SELECT SourcePackagePublishingHistory.id |
566 | + FROM SourcePackagePublishingHistory |
567 | + WHERE |
568 | + SourcePackagePublishingHistory.distroseries = |
569 | + DistroArchSeries.distroseries AND |
570 | + SourcePackagePublishingHistory.sourcepackagerelease = |
571 | + BinaryPackageBuild.source_package_release AND |
572 | + SourcePackagePublishingHistory.archive = Archive.id AND |
573 | + SourcePackagePublishingHistory.status IN %s)) |
574 | + OR |
575 | + archive.private IS FALSE) AND |
576 | + BinaryPackageBuild.status = %s |
577 | + """ % sqlvalues(private_statuses, BuildStatus.NEEDSBUILD) |
578 | + |
579 | + @staticmethod |
580 | + def postprocessCandidate(job, logger): |
581 | + """See `ISpecificBuildFarmJobSource`.""" |
582 | + # Mark build records targeted to old source versions as SUPERSEDED |
583 | + # and build records target to SECURITY pocket or against an OBSOLETE |
584 | + # distroseries without a flag as FAILEDTOBUILD. |
585 | + # Builds in those situation should not be built because they will |
586 | + # be wasting build-time. In the former case, there is already a |
587 | + # newer source; the latter case needs an overhaul of the way |
588 | + # security builds are handled (by copying from a PPA) to avoid |
589 | + # creating duplicate builds. |
590 | + build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
591 | + distroseries = build.distro_arch_series.distroseries |
592 | + if ( |
593 | + build.pocket == PackagePublishingPocket.SECURITY or |
594 | + (distroseries.status == SeriesStatus.OBSOLETE and |
595 | + not build.archive.permit_obsolete_series_uploads)): |
596 | + # We never build anything in the security pocket, or for obsolete |
597 | + # series without the flag set. |
598 | + logger.debug( |
599 | + "Build %s FAILEDTOBUILD, queue item %s REMOVED" |
600 | + % (build.id, job.id)) |
601 | + build.updateStatus(BuildStatus.FAILEDTOBUILD) |
602 | + job.destroySelf() |
603 | + return False |
604 | + |
605 | + publication = build.current_source_publication |
606 | + if publication is None: |
607 | + # The build should be superseded if it no longer has a |
608 | + # current publishing record. |
609 | + logger.debug( |
610 | + "Build %s SUPERSEDED, queue item %s REMOVED" |
611 | + % (build.id, job.id)) |
612 | + build.updateStatus(BuildStatus.SUPERSEDED) |
613 | + job.destroySelf() |
614 | + return False |
615 | + |
616 | + return True |
617 | |
618 | === modified file 'lib/lp/soyuz/model/buildpackagejob.py' |
619 | --- lib/lp/soyuz/model/buildpackagejob.py 2013-09-04 08:04:25 +0000 |
620 | +++ lib/lp/soyuz/model/buildpackagejob.py 2013-11-11 23:47:32 +0000 |
621 | @@ -15,14 +15,9 @@ |
622 | from zope.component import getUtility |
623 | from zope.interface import implements |
624 | |
625 | -from lp.buildmaster.enums import BuildStatus |
626 | from lp.buildmaster.model.buildfarmjob import BuildFarmJobOld |
627 | -from lp.registry.interfaces.pocket import PackagePublishingPocket |
628 | -from lp.registry.interfaces.series import SeriesStatus |
629 | from lp.services.database.bulk import load_related |
630 | from lp.services.database.interfaces import IStore |
631 | -from lp.services.database.sqlbase import sqlvalues |
632 | -from lp.soyuz.enums import PackagePublishingStatus |
633 | from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet |
634 | from lp.soyuz.interfaces.buildpackagejob import ( |
635 | COPY_ARCHIVE_SCORE_PENALTY, |
636 | @@ -99,16 +94,6 @@ |
637 | |
638 | return score |
639 | |
640 | - @property |
641 | - def processor(self): |
642 | - """See `IBuildFarmJob`.""" |
643 | - return self.build.processor |
644 | - |
645 | - @property |
646 | - def virtualized(self): |
647 | - """See `IBuildFarmJob`.""" |
648 | - return self.build.is_virtualized |
649 | - |
650 | @classmethod |
651 | def preloadJobsData(cls, jobs): |
652 | from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild |
653 | @@ -116,75 +101,3 @@ |
654 | load_related(Job, jobs, ['job_id']) |
655 | builds = load_related(BinaryPackageBuild, jobs, ['build_id']) |
656 | getUtility(IBinaryPackageBuildSet).preloadBuildsData(list(builds)) |
657 | - |
658 | - @staticmethod |
659 | - def addCandidateSelectionCriteria(processor, virtualized): |
660 | - """See `IBuildFarmJob`.""" |
661 | - private_statuses = ( |
662 | - PackagePublishingStatus.PUBLISHED, |
663 | - PackagePublishingStatus.SUPERSEDED, |
664 | - PackagePublishingStatus.DELETED, |
665 | - ) |
666 | - return """ |
667 | - SELECT TRUE FROM Archive, BinaryPackageBuild, BuildPackageJob, |
668 | - DistroArchSeries |
669 | - WHERE |
670 | - BuildPackageJob.job = Job.id AND |
671 | - BuildPackageJob.build = BinaryPackageBuild.id AND |
672 | - BinaryPackageBuild.distro_arch_series = |
673 | - DistroArchSeries.id AND |
674 | - BinaryPackageBuild.archive = Archive.id AND |
675 | - ((Archive.private IS TRUE AND |
676 | - EXISTS ( |
677 | - SELECT SourcePackagePublishingHistory.id |
678 | - FROM SourcePackagePublishingHistory |
679 | - WHERE |
680 | - SourcePackagePublishingHistory.distroseries = |
681 | - DistroArchSeries.distroseries AND |
682 | - SourcePackagePublishingHistory.sourcepackagerelease = |
683 | - BinaryPackageBuild.source_package_release AND |
684 | - SourcePackagePublishingHistory.archive = Archive.id AND |
685 | - SourcePackagePublishingHistory.status IN %s)) |
686 | - OR |
687 | - archive.private IS FALSE) AND |
688 | - BinaryPackageBuild.status = %s |
689 | - """ % sqlvalues(private_statuses, BuildStatus.NEEDSBUILD) |
690 | - |
691 | - @staticmethod |
692 | - def postprocessCandidate(job, logger): |
693 | - """See `IBuildFarmJob`.""" |
694 | - # Mark build records targeted to old source versions as SUPERSEDED |
695 | - # and build records target to SECURITY pocket or against an OBSOLETE |
696 | - # distroseries without a flag as FAILEDTOBUILD. |
697 | - # Builds in those situation should not be built because they will |
698 | - # be wasting build-time. In the former case, there is already a |
699 | - # newer source; the latter case needs an overhaul of the way |
700 | - # security builds are handled (by copying from a PPA) to avoid |
701 | - # creating duplicate builds. |
702 | - build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
703 | - distroseries = build.distro_arch_series.distroseries |
704 | - if ( |
705 | - build.pocket == PackagePublishingPocket.SECURITY or |
706 | - (distroseries.status == SeriesStatus.OBSOLETE and |
707 | - not build.archive.permit_obsolete_series_uploads)): |
708 | - # We never build anything in the security pocket, or for obsolete |
709 | - # series without the flag set. |
710 | - logger.debug( |
711 | - "Build %s FAILEDTOBUILD, queue item %s REMOVED" |
712 | - % (build.id, job.id)) |
713 | - build.updateStatus(BuildStatus.FAILEDTOBUILD) |
714 | - job.destroySelf() |
715 | - return False |
716 | - |
717 | - publication = build.current_source_publication |
718 | - if publication is None: |
719 | - # The build should be superseded if it no longer has a |
720 | - # current publishing record. |
721 | - logger.debug( |
722 | - "Build %s SUPERSEDED, queue item %s REMOVED" |
723 | - % (build.id, job.id)) |
724 | - build.updateStatus(BuildStatus.SUPERSEDED) |
725 | - job.destroySelf() |
726 | - return False |
727 | - |
728 | - return True |
729 | |
730 | === modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py' |
731 | --- lib/lp/soyuz/tests/test_binarypackagebuild.py 2013-09-10 06:28:26 +0000 |
732 | +++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2013-11-11 23:47:32 +0000 |
733 | @@ -17,7 +17,9 @@ |
734 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue |
735 | from lp.buildmaster.interfaces.packagebuild import IPackageBuild |
736 | from lp.buildmaster.model.buildqueue import BuildQueue |
737 | +from lp.registry.interfaces.series import SeriesStatus |
738 | from lp.services.job.model.job import Job |
739 | +from lp.services.log.logger import DevNullLogger |
740 | from lp.services.webapp.interaction import ANONYMOUS |
741 | from lp.services.webapp.interfaces import OAuthPermission |
742 | from lp.soyuz.enums import ( |
743 | @@ -31,7 +33,10 @@ |
744 | ) |
745 | from lp.soyuz.interfaces.buildpackagejob import IBuildPackageJob |
746 | from lp.soyuz.interfaces.component import IComponentSet |
747 | -from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild |
748 | +from lp.soyuz.model.binarypackagebuild import ( |
749 | + BinaryPackageBuild, |
750 | + BinaryPackageBuildSet, |
751 | + ) |
752 | from lp.soyuz.model.buildpackagejob import BuildPackageJob |
753 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
754 | from lp.testing import ( |
755 | @@ -497,3 +502,42 @@ |
756 | logout() |
757 | entry = self.webservice.get(build_url, api_version='devel').jsonBody() |
758 | self.assertEndsWith(entry['builder_link'], builder_url) |
759 | + |
760 | + |
761 | +class TestPostprocessCandidate(TestCaseWithFactory): |
762 | + |
763 | + layer = DatabaseFunctionalLayer |
764 | + |
765 | + def makeBuildJob(self, pocket="RELEASE"): |
766 | + build = self.factory.makeBinaryPackageBuild(pocket=pocket) |
767 | + return build.queueBuild() |
768 | + |
769 | + def test_release_job(self): |
770 | + job = self.makeBuildJob() |
771 | + build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
772 | + self.assertTrue(BinaryPackageBuildSet.postprocessCandidate(job, None)) |
773 | + self.assertEqual(BuildStatus.NEEDSBUILD, build.status) |
774 | + |
775 | + def test_security_job_is_failed(self): |
776 | + job = self.makeBuildJob(pocket="SECURITY") |
777 | + build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
778 | + BinaryPackageBuildSet.postprocessCandidate(job, DevNullLogger()) |
779 | + self.assertEqual(BuildStatus.FAILEDTOBUILD, build.status) |
780 | + |
781 | + def test_obsolete_job_without_flag_is_failed(self): |
782 | + job = self.makeBuildJob() |
783 | + build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
784 | + distroseries = build.distro_arch_series.distroseries |
785 | + removeSecurityProxy(distroseries).status = SeriesStatus.OBSOLETE |
786 | + BinaryPackageBuildSet.postprocessCandidate(job, DevNullLogger()) |
787 | + self.assertEqual(BuildStatus.FAILEDTOBUILD, build.status) |
788 | + |
789 | + def test_obsolete_job_with_flag_is_not_failed(self): |
790 | + job = self.makeBuildJob() |
791 | + build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
792 | + distroseries = build.distro_arch_series.distroseries |
793 | + archive = build.archive |
794 | + removeSecurityProxy(distroseries).status = SeriesStatus.OBSOLETE |
795 | + removeSecurityProxy(archive).permit_obsolete_series_uploads = True |
796 | + BinaryPackageBuildSet.postprocessCandidate(job, DevNullLogger()) |
797 | + self.assertEqual(BuildStatus.NEEDSBUILD, build.status) |
798 | |
799 | === modified file 'lib/lp/soyuz/tests/test_buildpackagejob.py' |
800 | --- lib/lp/soyuz/tests/test_buildpackagejob.py 2013-09-10 06:28:26 +0000 |
801 | +++ lib/lp/soyuz/tests/test_buildpackagejob.py 2013-11-11 23:47:32 +0000 |
802 | @@ -13,16 +13,13 @@ |
803 | from lp.buildmaster.enums import BuildStatus |
804 | from lp.buildmaster.interfaces.builder import IBuilderSet |
805 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
806 | -from lp.registry.interfaces.series import SeriesStatus |
807 | from lp.registry.interfaces.sourcepackage import SourcePackageUrgency |
808 | from lp.services.database.interfaces import IStore |
809 | -from lp.services.log.logger import DevNullLogger |
810 | from lp.services.webapp.interfaces import OAuthPermission |
811 | from lp.soyuz.enums import ( |
812 | ArchivePurpose, |
813 | PackagePublishingStatus, |
814 | ) |
815 | -from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet |
816 | from lp.soyuz.interfaces.buildfarmbuildjob import IBuildFarmBuildJob |
817 | from lp.soyuz.interfaces.buildpackagejob import ( |
818 | COPY_ARCHIVE_SCORE_PENALTY, |
819 | @@ -34,7 +31,6 @@ |
820 | ) |
821 | from lp.soyuz.interfaces.processor import IProcessorSet |
822 | from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild |
823 | -from lp.soyuz.model.buildpackagejob import BuildPackageJob |
824 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
825 | from lp.testing import ( |
826 | anonymous_logged_in, |
827 | @@ -197,24 +193,6 @@ |
828 | removeSecurityProxy(bq).estimated_duration = timedelta( |
829 | seconds=duration) |
830 | |
831 | - def test_processor(self): |
832 | - # Test that BuildPackageJob returns the correct processor. |
833 | - build, bq = find_job(self, 'gcc', '386') |
834 | - bpj = bq.specific_job |
835 | - self.assertEqual(bpj.processor.id, 1) |
836 | - build, bq = find_job(self, 'bison', 'hppa') |
837 | - bpj = bq.specific_job |
838 | - self.assertEqual(bpj.processor.id, 3) |
839 | - |
840 | - def test_virtualized(self): |
841 | - # Test that BuildPackageJob returns the correct virtualized flag. |
842 | - build, bq = find_job(self, 'apg', '386') |
843 | - bpj = bq.specific_job |
844 | - self.assertEqual(bpj.virtualized, False) |
845 | - build, bq = find_job(self, 'flex', 'hppa') |
846 | - bpj = bq.specific_job |
847 | - self.assertEqual(bpj.virtualized, False) |
848 | - |
849 | def test_providesInterfaces(self): |
850 | # Ensure that a BuildPackageJob generates an appropriate cookie. |
851 | build, bq = find_job(self, 'gcc', '386') |
852 | @@ -222,17 +200,6 @@ |
853 | self.assertProvides(build_farm_job, IBuildPackageJob) |
854 | self.assertProvides(build_farm_job, IBuildFarmBuildJob) |
855 | |
856 | - def test_jobStarted(self): |
857 | - # Starting a build updates the status. |
858 | - build, bq = find_job(self, 'gcc', '386') |
859 | - build_package_job = bq.specific_job |
860 | - build_package_job.jobStarted() |
861 | - self.assertEqual( |
862 | - BuildStatus.BUILDING, build_package_job.build.status) |
863 | - self.assertIsNot(None, build_package_job.build.date_started) |
864 | - self.assertIsNot(None, build_package_job.build.date_first_dispatched) |
865 | - self.assertIs(None, build_package_job.build.date_finished) |
866 | - |
867 | |
868 | class TestBuildPackageJobScore(TestCaseWithFactory): |
869 | |
870 | @@ -469,42 +436,3 @@ |
871 | with anonymous_logged_in(): |
872 | self.assertScoreWriteableByTeam( |
873 | archive, getUtility(ILaunchpadCelebrities).commercial_admin) |
874 | - |
875 | - |
876 | -class TestBuildPackageJobPostProcess(TestCaseWithFactory): |
877 | - |
878 | - layer = DatabaseFunctionalLayer |
879 | - |
880 | - def makeBuildJob(self, pocket="RELEASE"): |
881 | - build = self.factory.makeBinaryPackageBuild(pocket=pocket) |
882 | - return build.queueBuild() |
883 | - |
884 | - def test_release_job(self): |
885 | - job = self.makeBuildJob() |
886 | - build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
887 | - self.assertTrue(BuildPackageJob.postprocessCandidate(job, None)) |
888 | - self.assertEqual(BuildStatus.NEEDSBUILD, build.status) |
889 | - |
890 | - def test_security_job_is_failed(self): |
891 | - job = self.makeBuildJob(pocket="SECURITY") |
892 | - build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
893 | - BuildPackageJob.postprocessCandidate(job, DevNullLogger()) |
894 | - self.assertEqual(BuildStatus.FAILEDTOBUILD, build.status) |
895 | - |
896 | - def test_obsolete_job_without_flag_is_failed(self): |
897 | - job = self.makeBuildJob() |
898 | - build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
899 | - distroseries = build.distro_arch_series.distroseries |
900 | - removeSecurityProxy(distroseries).status = SeriesStatus.OBSOLETE |
901 | - BuildPackageJob.postprocessCandidate(job, DevNullLogger()) |
902 | - self.assertEqual(BuildStatus.FAILEDTOBUILD, build.status) |
903 | - |
904 | - def test_obsolete_job_with_flag_is_not_failed(self): |
905 | - job = self.makeBuildJob() |
906 | - build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job) |
907 | - distroseries = build.distro_arch_series.distroseries |
908 | - archive = build.archive |
909 | - removeSecurityProxy(distroseries).status = SeriesStatus.OBSOLETE |
910 | - removeSecurityProxy(archive).permit_obsolete_series_uploads = True |
911 | - BuildPackageJob.postprocessCandidate(job, DevNullLogger()) |
912 | - self.assertEqual(BuildStatus.NEEDSBUILD, build.status) |
913 | |
914 | === modified file 'lib/lp/translations/model/translationtemplatesbuild.py' |
915 | --- lib/lp/translations/model/translationtemplatesbuild.py 2013-09-24 05:45:06 +0000 |
916 | +++ lib/lp/translations/model/translationtemplatesbuild.py 2013-11-11 23:47:32 +0000 |
917 | @@ -28,7 +28,10 @@ |
918 | BuildStatus, |
919 | ) |
920 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource |
921 | -from lp.buildmaster.model.buildfarmjob import BuildFarmJobMixin |
922 | +from lp.buildmaster.model.buildfarmjob import ( |
923 | + BuildFarmJobMixin, |
924 | + SpecificBuildFarmJobSourceMixin, |
925 | + ) |
926 | from lp.code.model.branch import Branch |
927 | from lp.code.model.branchcollection import GenericBranchCollection |
928 | from lp.code.model.branchjob import ( |
929 | @@ -49,7 +52,8 @@ |
930 | ) |
931 | |
932 | |
933 | -class TranslationTemplatesBuild(BuildFarmJobMixin, Storm): |
934 | +class TranslationTemplatesBuild(SpecificBuildFarmJobSourceMixin, |
935 | + BuildFarmJobMixin, Storm): |
936 | """A `BuildFarmJob` extension for translation templates builds.""" |
937 | |
938 | implements(ITranslationTemplatesBuild) |
213 + return ('')
Isn't that just empty string? If you want to return a tuple, you need a comma, no?
I also expect this branch impacts on bug #1233145.